Use PowerShell to Find Installed Software

Summary: Guest blogger, Marc Carter, reprises his popular blog post about locating installed software.

Microsoft Scripting Guy, Ed Wilson, is here. Marc Carter is joining us again today with another guest blog post...

Looking back a couple years ago to my previous post, Use PowerShell to Quickly Find Installed Software, I find it interesting to reflect on common issues shared amongst the IT pro community. In our underlying goal to control our environment, whether that environment consists of a desktop computer, a development server, or production data center, we must first discover and understand before we can effectively attempt to control. Such is the case for sys admins when determining what software is currently configuring a server.

But first, let’s have a quick refresher on what initially prompted this discussion…

Win32_Product: The Good, the Bad, and the Ugly

 [Good] The Win32_Product WMI class represents products as they are installed by Windows Installer.

If you choose to query Win32_Product class by using Get-WmiObject, you’ll find yourself [Bad] waiting for your query (or application) to return [Ugly] a consistency check of packages that are installed as it attempts to verify and repair installs. (For more information, see Event log message indicates that the Windows Installer reconfigured all installed applications).

Problem #1: Um, is there a problem, officer?

Querying the Win32_Product class to determine installed software is more than likely not your “best” option. Unfortunately, not everyone knows this.

Solution: (Understanding) Do your part and help spread the word.

Problem #2: Identify better alternatives

I really like some of the refinements and suggestions within comments that were mentioned by others on my previous post. One of the things I take a lot of pride in is my association with the men and women of US Army and their core values (The Army Values).

I see that similar mindset and participation reflected in the esprit de corps (or cohesion) of the Windows PowerShell community. As others have pointed out, there are a lot better and easier ways to gather information without invoking the Win32_Product class. One of my favorite alternatives involved suggestions from Knut Johansen and Mike Crowley: use the PS Registry Provider.

The Windows PowerShell Registry provider lets you get, add, change, clear, and delete registry keys, entries, and values in Windows PowerShell. The Registry provider lets you access a hierarchical namespace that consists of registry keys and subkeys. Registry entries and values are not components of that hierarchy. Instead, they are properties of each of the keys. The Registry provider supports all the cmdlets that contain the “item” noun—that is, the Item cmdlets (except Invoke-Item) such as Get-Item, Copy-Item, and Rename-Item. Use the Item cmdlets when you work with registry keys and subkeys.
For more information, see Registry Provider.

In the following example, I use the Get-ItemProperty cmdlet to return values from the Uninstall Registry Key within the HKEY LOCAL MACHINE (HKLM) Registry Provider, selecting specific properties and then formatting output.

Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* |  Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |
Format-Table –AutoSize

The Get-ItemProperty cmdlet is a great tool because it’s designed to work with data that is exposed by any provider. To get a better idea of the various providers that are available in your session, simply execute the Get-PSProvider cmdlet.  

And of course, depending on my needs, I could have also used alternative output methods like Out-GridView or Export-Csv. Either way, we’ve now reduced the process to a one-liner that can be used in 64-bit and 32-bit environments:

Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |
Format-Table –AutoSize

Problem #3: Can we make it even more useful?

Absolutely! We are talking Windows PowerShell after all…

One way that comes to mind (and again, visible within the comments from the previous post), is addressing the issue of how to query multiple remote devices. My solution (or a number of reasons) is to rely on using the Invoke-Command cmdlet. In the following example, I query both of my SharePoint Web Front End (WFE) servers by using Invoke-Command to execute the same Get-ItemProperty on the remote system’s HKLM PS Registry Provider:

Invoke-Command -cn wfe0, wfe1 -ScriptBlock {Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | select DisplayName, Publisher, InstallDate }

The output now includes the PSComputerName column, which will help when I want to sort results down the road. And there we have it…an easy method to report installed software!

I look forward to reading comments from the Windows PowerShell community on other refinements and ways to improve this task. In many ways, I relate our efforts to that of a symphony or band. Each of us plays a different note in that we all hear and see things differently. Put us all together on the same sheet of music, and we have the potential for some awesome melodies.


Thank you, Marc, for another awesome blog.

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

Ed Wilson, Microsoft Scripting Guy

Comments (34)

  1. Anonymous says:

    Thanks, for sharing but I found it missing a lot of apps. command I used Get-ItemProperty HKLM:SoftwareMicrosoftWindowsCurrentVersionUninstall* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | ft –AutoSize

    I know for sure it missed WinRAR, notepad ++, adobe photoshop,

  2. Anonymous says:

    Mike, the assumption here is that you know your way around some PowerShell. To find specific software you would pipe the Select-Object cmdlet into a Where-Object cmdlet such as:

    Where-Object {$_.DisplayName -eq ‘Microsoft Visual C++ 2005 Redistributable’}

  3. Anonymous says:

    Thank you so much for sharing this post. I’d been researching and attempting several ways to gather the information, but this one bubbled to the top. Especially when adding the Wow6432Node — provided me with exactly what I needed in preparation of a huge transition. Thank you again.

  4. SCCM Admin says:

    This is great information. Would there be a way to know what version of Java these applications are tied to?

  5. Vern_Anderson says:

    WMI may be slower but it doesn't show security updates and hotfixes

  6. I'd add 2 things,

    win32_product has the additional drawback of not reporting on x86 apps for x64 systems.

    The script repository has a couple functions for download that people can download in case anyone wants to add this to one of their modules or profile.  I keep get-applicationinfo in my profile because I end up using it so much.

  7. jrv says:

    @Jason – Win32_Product reports both x*^ and x64 products on all systems I have tested on.  It only reports products installed by Windows installer and  does not report O365 components.  They are not really installed locally but are web delivered and use a different tool to report.

    Older 'Setup'  installs are not reported and XCOPY self installers are not reported  "Store Apps" are not reported.

  8. jrv,

    I was tracking on it missing installs that weren't pushed with msi's.  If you're confident it catches the x86 apps on x64 systems I'll double check my statement.

    Does it make a difference when you run it from a x86 or x64 shell?

  9. jrv says:

    @Jason – just to be sure I ran it on both x86 and x64 PowerShell and both report the exact same thing.

    You are thinking of the registry install keys. You have to query both the 32 and 64 bit registry on a 64 bit system to get all installed programs and that still will not get XCOPY installs.  Java is frequently a copy install local to the app that uses it.  There is no way to know about that automatically. You must know in advance that the program sues Java and then look for the Java file.  It can be anywhere.  It is usually in a  subdir of the program called JRE.

  10. i225d says:

    $app1 = Get-WmiObject Win32reg_AddRemovePrograms | Select-Object DisplayName,Version $app2 = Get-ItemProperty HKLM:SOFTWAREMicrosoftWindowsCurrentVersionUninstall* | Select-Object -Property DisplayName,DisplayVersion Compare-Object -ReferenceObject
    $app1 -DifferenceObject $app2

  11. dfladam says:

    I found I had to change to HKLM:Softwarewow6432node to get 32 bit apps

  12. sowmiya says:

    how to find whether the trend mcro antivirus is installed or not

  13. Daniel Heuberger says:

    Interesting.. but could we go on with this and uninstall a software? Thats actually why I started to look into Win32_Product class. I could search all systems in the domain for a software and start a deinstallation. Really nice if you need to remove a specific software from all systems.

  14. secureMe says:

    Can you use a similar technique to identify what account/user installed an application and when? I need this for my forensics research.

  15. Sys Admin says:

    I have a 64-bit system and this script does not output all installed applications. It only outputs a portion of all installed applications. Isn’t there a script which output ALL installed applications?

  16. chethan says:

    I had a small query, can i get information about who installed the app , who has access to the app, it would help me all lot, and please note that i have very little knowledge about powershells

  17. AllenHW says:

    When you use the script;

    Get-ItemProperty HKLM:SoftwareMicrosoftWindowsCurrentVersionUninstall* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |

    Format-Table –AutoSize

    It gives an error:

    Get-ItemProperty : Specified cast is not valid.
    At line:1 char:1
    + Get-ItemProperty HKLM:SoftwareMicrosoftWindowsCurrentVersionUninstall* | …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Get-ItemProperty], InvalidCastException
    + FullyQualifiedErrorId : System.InvalidCastException,Microsoft.PowerShell.Commands.GetItemPro

    What does InvalidCastException mean?

  18. Matthew says:

    This is a nice little command. I was looking for something simple to check that a particular piece of software was installed on a system before having a script start the quiet install for it and this does the trick by piping Where-Object { $_.DisplayName
    -eq "Name of Software" } after the get-itemproperty command.

  19. Sudharsan Sundararajan says:

    Hi Scripting Guy,
    I’d like to get a list of installed softwares like .net framework, SQL Server,etc(selective applications) with its version for a list of servers(input server list) to an excel file. Could you please help?

  20. buy dolls online india says:

    So nice blog thanks for sharing">buy dolls online india">online sale on baby products">Online Shopping for Boys">Online shopping for teenager

  21. soccerguru says:

    Hi guys,
    thanks for the script it was very helpful. however i have a little probleme. i adapted the script to check if a s pecifique software is installed ( to not reinstall it again). my script looks like :

    foreach($i in $SoftList)
    $x = Get-ItemProperty HKLM:SoftwareMicrosoftWindowsCurrentVersionUninstall* |

    select DisplayName, Publisher, InstallDate | Where-Object {$_.DisplayName -like $i}

    $x.DisplayName >> ListOf.txt # to display it on screen
    if ( $x.DisplayName )
    # Do some Stuff Here …

    but i face a probleme when the Display name is " Microsoft Visual C++ 2005 Redistributable" , is is not displayed by the instructio " $x.DisplayName" , and the test "if ( $x.DisplayName )" fails .
    any one can Help Please ?

  22. IF says:

    Is there a particular version of PowerShell I need to be running in? I’m getting an error indicating ‘winrm quickconfig’ needs to be run on the remote system. After running the command, I get a result but the data is not formatted correctly as shown in
    your screenshot, i.e. I see a long list rather than a table with headers. How can I effectively run the winrm command on all my remote systems (starting the winrm service which is default to manual, does not suffice), and how can I export the list to a formatted

  23. Error NetworkPathNotFound,PSSessionStateBroken says:

    When I run this on a machine that I know is up and running:
    Invoke-Command -cn XXXX -ScriptBlock {Get-ItemProperty HKLM:SoftwareMicrosoftWindowsCurrentVersionUninstall* | select DisplayName, Publisher, InstallDate }

    I get this error message:
    [XXXX] Connecting to remote server v0vps1 failed with the following error message : WinRM cannot process the request. The following error occurred while using Kerberos authentication: Cannot find the computer XXXX. Verify that the computer exists on the network
    and that the name provided is spelled correctly. For more information, see the about_Remote_Troubleshooting Help topic.
    + CategoryInfo : OpenError: (XXXX:String) [], PSRemotingTransportException
    + FullyQualifiedErrorId : NetworkPathNotFound,PSSessionStateBroken

  24. nakaedu says:

    Hi, would wmic qfe use win32_product class? I am using that command to check installed hotfixes and I don’t want the "[Ugly] a consistency check of packages"

  25. BrianS says:

    This doesn’t work for me. I tried it on Windows Vista, Windows 7, and even on Windows 10. I get no errors and no output. I can’t even get the listing of my local PC. I can do this in VBS and that works fine. Is this another PS annoyance?

  26. Errol says:

    How can I use this with a list of servers so that the installed software is listed under each server or in a separate tab?
    Tried it with get-content, but no luck

  27. mike says:

    This is confusing to someone new with pweershell/scripting. I didn’t see anywhere in the story where it said. This is how you search for specific software installations. All it says is "In the following example, I use the Get-ItemProperty cmdlet to return
    values from the Uninstall Registry Key within the HKEY LOCAL MACHINE (HKLM) Registry Provider, selecting specific properties and then formatting output."

    So… does this help me? This says nothing about it searching for software. Why is this so difficult? I thought this would be easily explained, I thought wrong.

  28. Mwilliam says:

    I am like Errol, I want to use a specific list of servers. Any direction on that?

  29. vXav says:

    Function Check-InstalledSoft {

    [String[]]$SoftToCheck = "*",

    [ValidateScript({Test-Connection -Quiet $_})]

    $params = @{ScriptBlock = {Get-ItemProperty HKLM:SoftwareMicrosoftWindowsCurrentVersionUninstall*}}

    IF ($Computer) { $params += @{ComputerName = $Computer} }

    $CheckInstalled = Invoke-Command @params

    $SoftToCheck | ForEach-Object {
    $soft = $_

    $CheckInstalled | ForEach-Object {

    IF ($_.DisplayName -like "*$soft*") {

    Computer = $_.PSComputerName
    Software = $_.DisplayName
    Version = $_.DisplayVersion
    } # IF
    } #$CheckInstalled | ForEach-Object
    } #$SoftToCheck | ForEach-Object
    } #Function

  30. vXav says:

    Sorry double post.

    To use the function do something like : Check-InstalledSoft -computer (Get-content MyListOfServers.txt) -SoftToCheck (get-content MyListOfSoft.txt)

    If you don’t specify computer it’ll run on the host you run it on.
    If you don’t specify SoftToCheck it’ll retrieve all the softwares.

    But as stated previously it’s not 100% reliable as I get incomplete outputs compared to the actual softs installed… I haven’t dug yet but probably something to do with 64bits or the character limits in the registry keys.
    If you know please share 🙂

  31. Greg Lambert says:

    Thanks, very helpful information.

Skip to main content