Installed Software

Retrieve information about installed software and find out versions, uninstall strings, and more.

Windows keeps track of installed software, and to manually list, change, or uninstall, simply run this command:

appwiz.cpl


This opens the graphical wizard, and you can click the task.

But how would you automate this, and where do you find the listed information, i.e. to create software reports, or test whether a given software is installed or not?

The information about installed software is stored in the Windows Registry in four different places, and it is almost trivial for PowerShell to read this:

# read all child keys (*) from all four locations and do not emit
# errors if one of these keys does not exist:
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKCU:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction Ignore |
# list only items with a displayname:
Where-Object DisplayName |
# show these registry values per item:
Select-Object -Property DisplayName, DisplayVersion, UninstallString, InstallDate |
# sort by displayname:
Sort-Object -Property DisplayName


The result looks similar to this:

DisplayName                                            DisplayVersion UninstallString                                      InstallDate
-----------                                            -------------- ---------------                                      -----------
64 Bit HP CIO Components Installer                     22.2.1         MsiExec.exe /I{50229C72-539F-4E65-BEB5-F0491C5074B7} 20190907
7-Zip 19.00 (x64)                                      19.00          C:\Program Files\7-Zip\Uninstall.exe
Actipro WPF Controls 2017.2                            17.2.0661      MsiExec.exe /I{DB4F94C0-D62F-41ED-81B9-078CDF246C5B} 20190909
Active Directory Authentication Library für SQL Server 15.0.1300.359  MsiExec.exe /I{088DDE47-955D-406C-848F-C1531DF2E049} 20190903

...


The bulk load is done by Get-ItemProperty: this cmdlet can read from multiple registry keys, and when you specify a “*”, it automatically traverses all child keys.

Create Excel Report: Installed Software

If you’d rather like to produce an excel list, first make sure you have installed the awesome (and free) module ImportExcel:

Install-Module -Name ImportExcel -Scope CurrentUser


Next, pipe the results to Export-Excel, and you are done:

# read all child keys (*) from all four locations and do not emit
# errors if one of these keys does not exist:
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
'HKCU:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction Ignore |
# list only items with a displayname:
Where-Object DisplayName |
# show these registry values per item:
Select-Object -Property DisplayName, DisplayVersion, UninstallString, InstallDate |
# sort by displayname:
Sort-Object -Property DisplayName |
# export results to excel
Export-Excel


Implementation: Get-Software

Let’s bake this finding into a PowerShell function and add all the bells and whistles that make it a really useful and reusable command:

• Add a built-in filter so you can quickly search for a given software
• Add information about the architecture (64-Bit or 32-Bit), and the scope (installed for current user or all users)
• Add a property filter so PowerShell shows only a few most commonly used properties by default
function Get-Software
{
<#
.SYNOPSIS

.PARAMETER DisplayName
Name or part of name of the software you are looking for

.EXAMPLE
Get-Software -DisplayName *Office*
returns all software with "Office" anywhere in its name
#>

param
(
# emit only software that matches the value you submit:
[string]
$DisplayName = '*' ) #region define friendly texts:$Scopes = @{
HKLM = 'All Users'
HKCU = 'Current User'
}

$Architectures = @{$true = '32-Bit'
$false = '64-Bit' } #endregion #region define calculated custom properties: # add the scope of the software based on whether the key is located # in HKLM: or HKCU:$Scope = @{
Name = 'Scope'
Expression = {
$Scopes[$_.PSDrive.Name]
}
}

# add architecture (32- or 64-bit) based on whether the registry key
# contains the parent key WOW6432Node:
$Architecture = @{ Name = 'Architecture' Expression = {$Architectures[$_.PSParentPath -like '*\WOW6432Node\*']} } #endregion #region define the properties (registry values) we are after # define the registry values that you want to include into the result:$Values = 'AuthorizedCDFPrefix',
'Contact',
'DisplayName',
'DisplayVersion',
'EstimatedSize',
'HelpTelephone',
'InstallDate',
'InstallLocation',
'InstallSource',
'Language',
'ModifyPath',
'NoModify',
'PSChildName',
'PSDrive',
'PSParentPath',
'PSPath',
'PSProvider',
'Publisher',
'Size',
'SystemComponent',
'UninstallString',
'URLUpdateInfo',
'Version',
'VersionMajor',
'VersionMinor',
'WindowsInstaller',
'Scope',
'Architecture'
#endregion

#region Define the VISIBLE properties
# define the properties that should be visible by default
# keep this below 5 to produce table output:
[string[]]$visible = 'DisplayName','DisplayVersion','Scope','Architecture' [Management.Automation.PSMemberInfo[]]$visibleProperties = [System.Management.Automation.PSPropertySet]::new('DefaultDisplayPropertySet',$visible) #endregion #region read software from all four keys in Windows Registry: # read all four locations where software can be registered, and ignore non-existing keys: Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKCU:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction Ignore | # exclude items with no DisplayName: Where-Object DisplayName | # include only items that match the user filter: Where-Object {$_.DisplayName -like $DisplayName } | # add the two calculated properties defined earlier: Select-Object -Property *,$Scope, $Architecture | # create final objects with all properties we want: Select-Object -Property$values |
# sort by name, then scope, then architecture:
Sort-Object -Property DisplayName, Scope, Architecture |
# add the property PSStandardMembers so PowerShell knows which properties to
# display by default:
Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $visibleProperties -PassThru #endregion }  There are plenty of learning points: Showing Only Few Default Properties When you run Get-Software, only four properties are displayed by default, producing a nice and clean table output: Get-Software  DisplayName DisplayVersion Scope Architecture ----------- -------------- ----- ------------ 64 Bit HP CIO Components Installer 22.2.1 All Users 64-Bit 7-Zip 19.00 (x64) 19.00 All Users 64-Bit Actipro WPF Controls 2017.2 17.2.0661 All Users 32-Bit Active Directory Authentication Library für SQL Server 15.0.1300.359 All Users 64-Bit Adobe Acrobat Reader DC - Deutsch 20.006.20042 All Users 32-Bit  Still, you can display any property you want when you add Select-Object: Get-Software | Select-Object -Property *  AuthorizedCDFPrefix : Comments : Contact : DisplayName : 64 Bit HP CIO Components Installer DisplayVersion : 22.2.1 EstimatedSize : 845 HelpLink : HelpTelephone : InstallDate : 20190907 InstallLocation : InstallSource : C:\Windows\system32\spool\DRIVERS\x64\3\ Language : 1033 ModifyPath : MsiExec.exe /I{50229C72-539F-4E65-BEB5-F0491C5074B7} NoModify : PSChildName : {50229C72-539F-4E65-BEB5-F0491C5074B7} PSDrive : HKLM PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{50229C72-539F-4E65-BEB5-F0491C5074B7} PSProvider : Microsoft.PowerShell.Core\Registry Publisher : HP Inc. Readme : Size : SystemComponent : 1 UninstallString : MsiExec.exe /I{50229C72-539F-4E65-BEB5-F0491C5074B7} URLInfoAbout : URLUpdateInfo : Version : 369229825 VersionMajor : 22 VersionMinor : 2 WindowsInstaller : 1 Scope : All Users Architecture : 64-Bit ...  If you’d like to get more information on the techniques used to define the default properties, here are all the details. Custom Filtering Rather than leaving it to the user to add Where-Object for custom filtering, the function sports the filter property -DisplayName that is named after the property it filters, so now it is simply to list exactly the software you are after: Get-Software -DisplayName *Office*  DisplayName DisplayVersion Scope Architecture ----------- -------------- ----- ------------ Microsoft Office Professional Plus 2016 - de-de 16.0.12527.20278 All Users 64-Bit Microsoft Office Professional Plus 2016 - en-us 16.0.12527.20278 All Users 64-Bit Microsoft Visual Studio 2010 Tools for Office Runtime (x64) 10.0.60724 All Users 64-Bit Microsoft Visual Studio 2010 Tools for Office Runtime (x64) 10.0.60729 All Users 64-Bit Office 16 Click-to-Run Extensibility Component 16.0.12527.20278 All Users 32-Bit Office 16 Click-to-Run Extensibility Component 64-bit Registration 16.0.12527.20278 All Users 64-Bit Office 16 Click-to-Run Licensing Component 16.0.12527.20278 All Users 64-Bit Office 16 Click-to-Run Localization Component 16.0.12527.20278 All Users 32-Bit Office 16 Click-to-Run Localization Component 16.0.12527.20278 All Users 32-Bit  And since the output still contains all properties, you can create custom lists and include non-default properties when needed: Get-Software -DisplayName *Office* | Select-Object -Property DisplayName, InstallSource  DisplayName InstallSource ----------- ------------- Microsoft Office Professional Plus 2016 - de-de Microsoft Office Professional Plus 2016 - en-us Microsoft Visual Studio 2010 Tools for Office Runtime (x64) Microsoft Visual Studio 2010 Tools for Office Runtime (x64) c:\f3c1e4cb1e6fd583dbd7441e\ Office 16 Click-to-Run Extensibility Component c:\program files (x86)\microsoft office\root\integration\ Office 16 Click-to-Run Extensibility Component 64-bit Registration c:\program files (x86)\microsoft office\root\integration\ Office 16 Click-to-Run Licensing Component c:\program files (x86)\microsoft office\root\integration\ Office 16 Click-to-Run Localization Component c:\program files (x86)\microsoft office\root\integration\ Office 16 Click-to-Run Localization Component c:\program files (x86)\microsoft office\root\integration\  Calculated Properties Thanks to calculated properties, the output includes important information about architecture and scope. These properties weren’t available in the registry and have been calculated based on the location of the registry keys. Fine-Tuning Returned Properties Get-Softwarereturns all registry values available. You can look up the details of the registry values here. If you don’t need some of the information, i.e. AuthorizedCDFPrefix, or find that this information is always empty for you, simply remove the property from the list in $values and tailor the output to your needs.

Maybe you are wondering why Get-Software uses Select-Object twice, and why I chose to hard-code the properties. Here is why:

The first instance of Select-Object includes all properties, plus it adds the new calculated properties:

# add the two calculated properties defined earlier:
Select-Object -Property *, $Scope,$Architecture |


The second instance of Select-Object defines the final properties returned. This is crucial because not every registry key contains all of these values. PowerShell uses the properties of the first returned object to calculate tabular headers, so if by accident the first read software item had defined no or not all of the common registry values, PowerShell would not show the remaining values for any of the software items.

By hard-coding the property names you are after, you guarantee that they be visible because Select-Object actually always clones objects and produces new objects with the properties you chose. This way, these new objects are guaranteed to have these properties, even if they are empty.