Reporting On Installed Windows Programs Via The Registry
Quite a common request for working with Windows machines is to report the software installed on them. If you don’t have a centralised system for reporting on client software (many places don’t) then you may turn to some form of scripted method to obtain this information.
Most people tend to head to Add / Remove Programs when thinking about what software is installed in Windows. However, not all applications will always populate information in there, depending on how they have been installed. Additionally, to query that information you would typically query the WMI class Win32_Product, however this can lead to performance issues.
A better approach if going down this route is to look in the registry and the key HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall.
The following function illustrates how to access this data by using this .NET approach to retrieve registry data. Note: this also works with remote computers.
\[Microsoft.Win32.RegistryKey\]::OpenRemoteBaseKey('RegistryHive','ComputerName')
We check for the DisplayName on each subkey before adding the result to the output, since for some reason some keys are empty.
$DisplayName = $SubKeyValues.GetValue('DisplayName')<br />if ($DisplayName){........
Here’s the function which you could flesh out further for your own requirements.
Update 03/02/2014: As Peter points out in the notes, if you have 32bit software installed you have an extra place to check when using this method, so you will also need to make the same checks in the following key:
HKLM\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
function Get-InstalledSoftware { <# .SYNOPSIS Retrieve installed software from the registry
.DESCRIPTION Retrieve installed software from the registry
.PARAMETER ComputerName Name of the computer to check
.INPUTS System.String
.OUTPUTS System.Management.Automation.PSObject.
.EXAMPLE PS> Get-InstalledSoftware -Computer Server01
.EXAMPLE PS> "Server01","Server02" | Get-InstalledSoftware
#> \[CmdletBinding()\]\[OutputType('System.Management.Automation.PSObject')\]
Param (
\[parameter(Mandatory=$true,ValueFromPipeline=$true)\] \[ValidateNotNullOrEmpty()\] \[String\[\]\]$ComputerName
)
begin {
$OutputObject = @() }
process {
try { foreach ($Computer in $ComputerName){
$Registry = \[Microsoft.Win32.RegistryKey\]::OpenRemoteBaseKey('Localmachine',$Computer) $UninstallTopKey = $Registry.OpensubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall",$false) $UninstallSubKeys = $UninstallTopKey.GetSubKeyNames()
ForEach ($SubKey in $UninstallSubKeys){
$Path = "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$SubKey" $SubKeyValues = $Registry.OpensubKey($Path,$false)
$DisplayName = $SubKeyValues.GetValue('DisplayName')
if ($DisplayName){
$Object = \[pscustomobject\]@{
ComputerName = $Computer DisplayName = $DisplayName DisplayVersion = $SubKeyValues.GetValue('DisplayVersion') UninstallString = $SubKeyValues.GetValue('UninstallString') Publisher = $SubKeyValues.GetValue('Publisher') InstallDate = $SubKeyValues.GetValue('InstallDate')
} }
$OutputObject += $Object
} } } catch \[Exception\]{
throw "Unable to get registry data....." } } end { Write-Output $OutputObject } }