PowerShell Problem Solver: Find Installed Software Using PowerShell

aOne question that I see often in PowerShell forums is how to find out what applications are installed on a given computer. Naturally the solution should be done with PowerShell. This is a great choice because as you will see, identifying installed applications is pretty easy and once you have the information you can do much with it from exporting to a CSV file to creating an HTML report.

One thing I wouldn’t do though is to rely on scripting techniques, PowerShell or otherwise to service as a license management mechanism. If you have that type of business requirement then you should be investing in a license management product. My other caveat is that you can really only find applications that were installed via an MSI package. It is very hard to find any stand-alone executables unless you know what you are looking for. But let’s see what we can figure out.

The primary tool will be WMI, and specifically the Win32_Product class. You can query this class with Get-WMIObject. But if you are running PowerShell 3.0 or later, I recommend you use Get-CimInstance. You will be querying the same WMI class, but Get-WMIObject relies on RPC and DCOM connections whereas Get-CimInstance uses the WSMan protocol, which makes it much more firewall friendly. Although there may be situations where you want to take advantage of both. For the most part you can interchange the cmdlets in my examples, except where noted.

First, let’s look at the class.

Sponsored Content

Passwords Haven’t Disappeared Yet

123456. Qwerty. Iloveyou. No, these are not exercises for people who are brand new to typing. Shockingly, they are among the most common passwords that end users choose in 2021. Research has found that the average business user must manually type out, or copy/paste, the credentials to 154 websites per month. We repeatedly got one question that surprised us: “Why would I ever trust a third party with control of my network?

[wmiclass]$class = "Win32_Product"
$class.properties | out-gridview -Title "Win32_Product properties"

I’m using the [wmiclass] type accelerator to define a variable to hold information about the class. I can use that to examine the properties.

Win32_Product properties. (Image Credit: Jeff Hicks)
Win32_Product properties. (Image Credit: Jeff Hicks)

I can do something similar to discover methods.

$class.methods | out-gridview -Title "Win32_Product methods"
Win32_Product methods. (Image Credit: Jeff Hicks)
Win32_Product methods. (Image Credit: Jeff Hicks)

But it is a bit harder I think to really understand these methods. Intead, let’s use the CIM cmdlets to look at the class.

$class2 = Get-CimClass Win32_Product
$class2.CimClassProperties | out-gridview -Title "Win32_Product properties"

Not too much difference when it comes to displaying properties. But identifying class methods is much easier.

$class2.CimClassMethods | format-table
Identifying class methods with PowerShell. (Image Credit: Jeff Hicks)
Identifying class methods with PowerShell. (Image Credit: Jeff Hicks)

If you want to use Out-Gridview, you need to take an extra step to get the parameters formatted properly.

$class2.CimClassMethods | Select Name,ReturnType,
Out-GridView -Title "Win32_Product methods"
Formatting parameters. (Image Credit: Jeff Hicks)
Formatting parameters. (Image Credit: Jeff Hicks)

Now that we know what we have to work with let’s see what is installed. I’m going to test locally and begin with a simple query.

$job = Get-WMIObject -class Win32_product -asjob

This command will return instance of the Win32_Product class on my computer. The reason I’m using a job is because it has always been my experience that querying this class takes a long time. If you want to do something similar with Get-CIMInstance, then you will need to use Start-Job because Get-CimInstance doesn’t have an –AsJob parameter.

Start-Job { get-ciminstance win32_product}

If you have to query multiple remote computers, one approach might be to use remote PSSessions in combination with these cmdlets. In the two examples above, the job is running on my computer. Here’s an alternative. First, create your remoting sessions.

$sessions = New-PSSession –computername Client1,Client2,Client3

Then you can use Invoke-Command to start a background job on the remote computers.

Invoke-Command –scriptblock { $job = get-wmiobject win32_Product –asJob} –session $sessions

Now each computer is running its own WMI job, and I can come back later to retrieve results. In PowerShell v3 and later I could also take advantage of disconnected sessions. But back to my local job. It has finished, so I’ll get the results.

$products = $job | Receive-job -keep

Looking at the count property, I see 200 installed products.

PS C:\Scripts> $products.count

I should point out that what gets registered with Win32_Product is more than just a product like Microsoft Outlook. A number of applications, especially Microsoft Office, install a number of other related “products” that you may never interact with directly.

But what type of information did I get? Since we’re exploring something new, I’ll look at a single object.

PS C:\Scripts> $products[0]
IdentifyingNumber : {F17C3DC2-2ACA-4B0E-BDBF-ACE61B14E7CD}
Name : Citrix Online Launcher
Vendor : Citrix
Version : 1.0.183
Caption : Citrix Online Launcher

If you use Get—CimInstance, then you will get a slightly different display. But as with most things in PowerShell, there is always more than meets the eye so we should look at all properties.

$products[0] | select *

Or a variation to omit the system properties:

$properties = $products[0].Properties.Name
$products[0] | Select $properties

If you use Get-CIMInstance, the system properties are suppressed by default. But in any event you should see something like this:

Using Get-CIMInstance in PowerShell. (Image Credit: Jeff Hicks)
Using Get-CIMInstance in PowerShell. (Image Credit: Jeff Hicks)

Once I know what kind of data I can get, it becomes much easier to display relevant results.

$products | Select Name,Version,InstallDate,Vendor,InstallSource,PackageName,LocalPackage,InstallLocation | Out-GridView -Title "Installed"

I’m only displaying some key properties.

Displaying key properties. (Image Credit: Jeff Hicks)
Displaying key properties. (Image Credit: Jeff Hicks)

With Out-Gridview I could do further filtering and sorting. Remember, we have PowerShell objects here so I can do just about anything I want with them. Perhaps export all data to a CSV file.

$products | Select $properties | export-csv c:\work\installed.csv -NoTypeInformation
Exporting data to a CSV file. (Image Credit: Jeff Hicks)
Exporting data to a CSV file. (Image Credit: Jeff Hicks)

One change I made was to add the PSComputername property to my list, so I would know where these products are installed. I also used –NoTypeInformation because I intend to use the CSV file outside of PowerShell.

Or perhaps I’d like to group.

$products | Group Vendor | Sort Count -Descending
Grouping data by vendor. (Image Credit: Jeff Hicks)
Grouping data by vendor. (Image Credit: Jeff Hicks)

Perhaps I want to identify all non-Microsoft products. I can do that with regular expressions.

$products | where {$_.vendor -notmatch "^Microsoft"} | sort Vendor | Format-Table -GroupBy Vendor -Property Name,Version,InstallDate
Identifying non-Microsoft products. (Image Credit: Jeff Hicks)
Identifying non-Microsoft products. (Image Credit: Jeff Hicks)

If you know what you are looking for from the very beginning, then you can use a WMI filter. For example, perhaps all I want to know from the start are non-Microsoft products. Here’s how I could accomplish that:

get-ciminstance win32_product -filter "NOT Vendor like '%Microsoft%'"

Or I want to see if Google Chrome is installed:

get-ciminstance win32_product -filter "Name='Google Chrome'"

WMI filters use the legacy operators. You should always try to filter this way instead of getting everything and then filtering with Where-Object.

Although it is perfectly acceptable to query multiple machines and then filter the aggregated results.

One final note is that if you filter for something and nothing matches, you won’t get anything back, nor will you get an error. You will need to use something like an IF statement in your expression. There are so many possibilities with this class. I’ve tried to highlight a few key ones. But if there is something you are curious about, please post in the PowerShell forum here on Petri.com. I will be revisiting this topic in future articles.

Related Topics:


Don't have a login but want to join the conversation? Sign up for a Petri Account

Comments (3)

3 responses to “PowerShell Problem Solver: Find Installed Software Using PowerShell”

  1. Would it be too much just to use WMIC?
    something like (wmic product list brief /format:htable)
    Or to dump to HTML
    (wmic /output:InstalledSoftware.html product get /all /format:htabl.xsl)
    just use the /node:targetComputer to get a remote list

  2. i also wrote this which does something similar
    @echo off
    set tmpfile=”C:MSI_Uninstallers.txt”
    if exist %tmpfile% del %tmpfile%
    echo Listing all MSI GUI files that are installed on %target%
    echo Please wait……….
    set regkey=”reg query HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionUninstall | find /i “{“”
    FOR /F %%A IN (‘%regkey%’) DO reg query %%A >> %tmpfile%
    echo Searching %tmpfile% for Display Names
    type %tmpfile% | find /i “displayname”
    notepad %tmpfile%

  3. Jammer, there is a follow up article where I essentially do the same thing as your batch file with PowerShell. And you could use WMIC, but that is really the same as using WMI or the CIM cmdlets to query the Win32_Product class.

Leave a Reply