PowerShell - Get List of Installed Applications

Written on November 5, 2017

Getting a list of installed applications seems like something a lot of Windows admins would like to do.
Unfortunately, there isn't an Out-of-the-Box way to do this with PowerShell.

Common ways of listing applications


Win32_Product

The most common method that I have seen is a simple WMI query to the Win_Product class.

gwmi Win32_Product

The first thing you will notice about this method, is that it takes a very long time to populate the list. Also, this will only retreive MSI installed applications. Anything installed by another method (like exe) will not show up here.

Win32Reg_AddRemoveProduct

This class is much better than the above; but, it will only exist if your machine or the target machine has an SCCM/SMS client installed on it.
This class is much faster at retrieve this info, although I have not used it very much because of this prerequisite.

gwmi Win32Reg_AddRemovePrograms

The great thing about these methods is that they use the Get-WmiObject command. This command makes it incredibly easy to query other computers, and use alternate credentials.


The Better Way


The better way to get this information would be to use the registry. When an application is installed (the Windows way), It creates a key in 1 or 2 locations in the registry depending on its architecture.

While it's not as easy as a one line WMI call, it is not too difficult to get this information with Get-ChildItem.

The 2 locations are as follows:

  1. HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
    • For 32-bit applications
  2. HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
    • For 64-bit applications
$paths=@(
  'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\',
  'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'
)
foreach($path in $paths){
  Get-ChildItem -Path $path | 
    Get-ItemProperty | 
      Select DisplayName, Publisher, InstallDate, DisplayVersion
}

You may want to use Format-List to view the data nicely.

As you can see, this is both fast and simple. There are many other properties you can select as well. If your environment is setup to use PowerShell remoting, you can simply pass the example above to the Invoke-Command command.

In environments that do not have PowerShell remoting setup, there is a way to connect to a remote registry on other machines. It isn't as straightforward, so I have made it into a nice function that should take out that complexity.

Function Get-InstalledApplication {
  [CmdletBinding()]
  Param(
    [Parameter(
      Position=0,
      ValueFromPipeline=$true,
      ValueFromPipelineByPropertyName=$true
    )]
    [String[]]$ComputerName=$ENV:COMPUTERNAME,

    [Parameter(Position=1)]
    [String[]]$Properties,

    [Parameter(Position=2)]
    [String]$IdentifyingNumber,

    [Parameter(Position=3)]
    [String]$Name,

    [Parameter(Position=4)]
    [String]$Publisher
  )
  Begin{
    Function IsCpuX86 ([Microsoft.Win32.RegistryKey]$hklmHive){
      $regPath='SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
      $key=$hklmHive.OpenSubKey($regPath)

      $cpuArch=$key.GetValue('PROCESSOR_ARCHITECTURE')

      if($cpuArch -eq 'x86'){
        return $true
      }else{
        return $false
      }
    }
  }
  Process{
    foreach($computer in $computerName){
      $regPath = @(
        'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
        'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
      )

      Try{
        $hive=[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey(
          [Microsoft.Win32.RegistryHive]::LocalMachine, 
          $computer
        )
        if(!$hive){
          continue
        }
        
        # if CPU is x86 do not query for Wow6432Node
        if($IsCpuX86){
          $regPath=$regPath[0]
        }

        foreach($path in $regPath){
          $key=$hive.OpenSubKey($path)
          if(!$key){
            continue
          }
          foreach($subKey in $key.GetSubKeyNames()){
            $subKeyObj=$null
            if($PSBoundParameters.ContainsKey('IdentifyingNumber')){
              if($subKey -ne $IdentifyingNumber -and 
                $subkey.TrimStart('{').TrimEnd('}') -ne $IdentifyingNumber){
                continue
              }
            }
            $subKeyObj=$key.OpenSubKey($subKey)
            if(!$subKeyObj){
              continue
            }
            $outHash=New-Object -TypeName Collections.Hashtable
            $appName=[String]::Empty
            $appName=($subKeyObj.GetValue('DisplayName'))
            if($PSBoundParameters.ContainsKey('Name')){
              if($appName -notlike $name){
                continue
              }
            }
            if($appName){
              if($PSBoundParameters.ContainsKey('Properties')){
                if($Properties -eq '*'){
                  foreach($keyName in ($hive.OpenSubKey("$path\$subKey")).GetValueNames()){
                    Try{
                      $value=$subKeyObj.GetValue($keyName)
                      if($value){
                        $outHash.$keyName=$value
                      }
                    }Catch{
                      Write-Warning "Subkey: [$subkey]: $($_.Exception.Message)"
                      continue
                    }
                  }
                }else{
                  foreach ($prop in $Properties){
                    $outHash.$prop=($hive.OpenSubKey("$path\$subKey")).GetValue($prop)
                  }
                }
              }
              $outHash.Name=$appName
              $outHash.IdentifyingNumber=$subKey
              $outHash.Publisher=$subKeyObj.GetValue('Publisher')
              if($PSBoundParameters.ContainsKey('Publisher')){
                if($outHash.Publisher -notlike $Publisher){
                  continue
                }
              }
              $outHash.ComputerName=$computer
              $outHash.Path=$subKeyObj.ToString()
              New-Object -TypeName PSObject -Property $outHash
            }
          }
        }
      }Catch{
        Write-Error $_
      }
    }
  }
  End{}
}

This is a fair bit of code, but using it is pretty straight forward. You can also grab this code (help included) from GitHub

Example 1

Get-InstalledApplication

This will give you all the installed apps on the current computer (assuming you have the necessary permissions).

Example 2

Get-InstalledApplication -ComputerName Computer1 -Name "Google Chrome"

This will seach Computer1 for an application named Google Chrome. Wildcards are also accepted.

Example 3

Get-InstalledApplication -ComputerName Server1, Server2 -Publisher 'Microsoft*'

As you might expect, this will list all applications installed on both server1 and server2 that are published my Microsoft.

Example 4

Get-InstalledApplication -ComputerName Server1, Server2 `
  -Name '7-zip*' `
  -Properties DisplayVersion, UninstallString, InstallDate

This will query Server1&2 for 7-zip, and include the listed properties, in the result.

Example 5

Get-InstalledApplication -ComputerName server1,server2,server3 `
  -IdentifyingNumber {5FCE6D76-F5DC-37AB-B2B8-22AB8CEDB1D4} `
  -Properties *

If you know the guid of an application, you can specify IdentifyingNumber.


Thanks for reading

PS> exit