December 14th, 2011

Use PowerShell to Find and Uninstall Software

Doctor Scripto
Scripter

Summary: Learn how to use Windows PowerShell to get software installation locations, and to uninstall software from remote computers.

Hey, Scripting Guy! QuestionHey, Scripting Guy! We have a dumb application that we have to use at work. The company has released a new version of this application, and I am trying to write a Windows PowerShell script to uninstall the old application—the problem is that I need to find the application first. I tried looking in the registry, but the install key is empty…figures. Like I said, this is a really dumb application. I read the guest blog written by Marc Carter about problems using the Win32_Product WMI class, but it looks like I am going to be stuck using this anyway. The problem is that it is really slow. Is there any way to speed this thing up? I have to query over a thousand computers, and in our testing, this query takes nearly five minutes to complete—that would be three and a half days for only one query.

—BT

Hey, Scripting Guy! Answer Hello BT,

Microsoft Scripting Guy, Ed Wilson, is here. The Scripting Wife and I were in Texas for the Corpus Christi Windows PowerShell User Group meeting when Marc Carter told me about the problem with the MSI installer reconfiguring applications when the Win32_Product WMI class is queried. I immediately encouraged him to write a guest blog about this issue.

BT, there is a way to use the Win32_Product WMI class in a more efficient manner. It relies on using the [WMI] type accelerator, instead of doing a generic WMI query. The problem is that the [WMI] type accelerator returns a specific instance of a WMI class. To connect to a specific instance, I must use the Key property of a WMI class.

I can use the Get-WMIKey function from my HSGWMImoduleV6 module. In the following code, I first import my HSGWMImoduleV6 module, and then I use the Get-WMIKey function to return the key to the Win32_Product WMI class. The commands and the output from the commands are shown here.

PS C:\> Import-Module hsg*6

PS C:\> Get-WmiKey win32_product

IdentifyingNumber

Name

Version

The Key property for Win32_Product is a composite key comprised of IdentifyingNumber, Name, and Version. The easy way to get this information is to use the Get-WmiObject cmdlet to query for the information. I only need to do this once, and I will have the three pieces of information. A table is a nice way to display the information. In the code shown here, I use the Get-WmiObject cmdlet (gwmi is an alias) to return product information, and then I pipe the management objects to the Format-Table (ft is an alias) cmdlet for display.

gwmi win32_product | ft name, version, ident*

In the image that follows, I import the HSGWMIModuleV6 module, use the Get-WMIKey function to retrieve the Key property of the Win32_Product WMI class. I then use the Get-WmiObject cmdlet (gwmi is an alias) to query the Win32_Product WMI class, and I output the management objects to a table via the Format-Table (ft is alias) cmdlet. The following image displays the commands and the output from the commands.

Image of command output

BT, you may ask, “What about Marc Carter’s warning about using the Win32_Product WMI class? “ Well as seen in the results from querying the event log, it is a concern. Shortly after querying the Win32_Product WMI class, I used the Get-EventLog cmdlet to query the application log for MSIInstaller events. The following image shows massive product reconfiguring going on.

Image of command output

The query to return the three parts of the composite key only needs to run once; the values do not change. It is also possible to use the Get-WmiObject cmdlet and a filter to improve the performance of the command a little bit. The nice thing about this command is that it returns the information that is required by the [WMI] type accelerator. The command and associated output are shown here.

PS C:\> gwmi win32_product -filter “Name LIKE ‘%Silverlight%'”

IdentifyingNumber : {89F4137D-6C26-4A84-BDB8-2E5A4BB71E00}

Name              : Microsoft Silverlight

Vendor            : Microsoft Corporation

Version           : 4.0.60831.0

Caption           : Microsoft Silverlight

When I have the three pieces of information (the IdentifyingNumber, the Name, and the Version), it is time to create the key. This is where quite a bit of experimentation could be required. I have to use the back tick (grave) character to escape inside quotation marks. I have to escape the quotation mark and the opening curly bracket for the IdentifyingNumber property. I also have to escape the closing curly bracket and the closing quotation mark. I then have to escape the quotation marks that surround Microsoft Silverlight, in addition to the quotation marks for the Version property. There are also two quotation marks at the end of the ClassKey.

Here is the key I derived for Microsoft Silverlight on my computer. (This is a single line command. A space between Microsoft and Silverlight exists, but other than that, there are no spaces).

$classKey=”IdentifyingNumber=`”`{89F4137D-6C26-4A84-BDB8-2E5A4BB71E00`}`”,Name=`”Microsoft Silverlight`”,version=`”4.0.60831.0`””

The reason for all the escaping in the ClassKey, is that WMI expects the quotation marks and the curly brackets in the key itself. To see what WMI expects to receive via the command, I use the Windows Management Instrumentation Tester (WbemTest) command, and I view the instances of the class. The following image illustrates the instances of Win32_Product on my computer.

Image of query results

When I have the ClassKey, I can use the [WMI] type accelerator to connect to the specific software package (Microsoft Silverlight in this example). In fact, using the [WMI] type accelerator is very easy. Here is the command. (The command is [WMI], the class name, and the key).

[wmi]”Win32_Product.$classkey”

I can also include the WMI namespace (really important if the class resides in a namespace other than the default root\cimv2). In the command that follows, notice that there is a backslash that precedes the word root. One other thing to notice is that a colon separates the WMI namespace and the WMI class name.

[wmi]”\root\cimv2:Win32_Product.$classkey”

If I need to connect to a WMI class on a remote computer, I use a double backslash and the name of the computer, then the WMI namespace, the WMI class, and the WMI ClassKey. The command that follows illustrates this.

[wmi]\\remotehost\root\cimv2:Win32_Product.$classkey

In the image that follows, I illustrate the different ways of querying WMI for Microsoft Silverlight software. I then compare the speed of using the Get-WmiObject cmdlet against the speed of using the [WMI] type accelerator. As shown in the following image, the Get-WmiObject cmdlet, using the filter to find Microsoft Silverlight, takes over five seconds on my laptop. Using the [WMI] type accelerator takes less than one-half of a second. This is more than 10 times faster.  

Image of results

By the way, there was not much difference between using the filter to look for Microsoft Silverlight or using the Where-Object. In the following output, I use the Measure-Object cmdlet to determine the performance of using the Where-Object (the ? is an alias for Where-Object).

PS C:\> measure-command {gwmi win32_product | ? {$_.name -match ‘silverlight’}}

Days              : 0

Hours             : 0

Minutes           : 0

Seconds           : 5

Milliseconds      : 311

Ticks             : 53112518

TotalDays         : 6.14728217592593E-05

TotalHours        : 0.00147534772222222

TotalMinutes      : 0.0885208633333333

TotalSeconds      : 5.3112518

TotalMilliseconds : 5311.2518

If you suspect that the problem with the filter is that I used the like operator as opposed to the equality operator, that is not the case. Here are the results from using the equality operator.

PS C:\> measure-command {gwmi win32_product | ? {$_.name -match ‘silverlight’}}

Days              : 0

Hours             : 0

Minutes           : 0

Seconds           : 5

Milliseconds      : 311

Ticks             : 53112518

TotalDays         : 6.14728217592593E-05

TotalHours        : 0.00147534772222222

TotalMinutes      : 0.0885208633333333

TotalSeconds      : 5.3112518

TotalMilliseconds : 5311.2518

When using the [WMI] type accelerator, a complete instance of the WMI class instance returns. The properties and their associated values are shown in the following image. Notice two properties: the __Path (that is, double underscore Path) property is the key to the WMI class instance. The InstallLocation property points to the location where the software installs.

Image of results

BT, you did not ask, but there is an Uninstall method available from the Win32_Product WMI class. It appears only on instances of the class. Therefore, it is possible to uninstall software by using the command that is shown here. (If I want to uninstall from a large collection of servers, I use the foreach statement ($servers is an array of server names).

foreach($server in $servers)

{ ([wmi]”\\$server\root\cimv2:Win32_Product.$classKey”).uninstall() }

BT, that is all there is to using the Win32_Product WMI class to detect or to uninstall software. Join me tomorrow when I will have a guest blog written by Raymond Mitchel as he talks about Windows PowerShell and SharePoint.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Author

The "Scripting Guys" is a historical title passed from scripter to scripter. The current revision has morphed into our good friend Doctor Scripto who has been with us since the very beginning.

1 comment

Discussion is closed. Login to edit/delete existing comments.

  • Luis Martínez Ancheta

    Is there a way you can send parameters to the uninstall method ? I used this and the computer restarted without warning.
    Thanks.