Public/system/Get-InstalledSoftware.ps1
|
#Requires -Version 5.1 function Get-InstalledSoftware { <# .SYNOPSIS Retrieves installed software from local or remote Windows computers .DESCRIPTION Queries the Windows registry Uninstall keys to enumerate installed software. Both 64-bit and 32-bit (WOW6432Node) registry hives are queried to provide a complete inventory. Supports wildcard filtering by display name. .PARAMETER ComputerName One or more computer names to query. Defaults to the local computer. Accepts pipeline input by value and by property name. .PARAMETER Name Optional wildcard filter for the software display name. Uses -like matching. For example, 'Microsoft*' returns all Microsoft products. .PARAMETER Credential Optional PSCredential object for authenticating to remote computers. Not used for local queries. .EXAMPLE Get-InstalledSoftware Retrieves all installed software on the local computer. .EXAMPLE Get-InstalledSoftware -ComputerName 'SRV01' -Name 'Microsoft SQL*' -Credential (Get-Credential) Retrieves SQL Server related software from SRV01 using alternate credentials. .EXAMPLE 'SRV01', 'SRV02' | Get-InstalledSoftware -Name '7-Zip*' Retrieves 7-Zip installations from multiple servers via pipeline. .OUTPUTS PSWinOps.InstalledSoftware Returns objects with ComputerName, DisplayName, DisplayVersion, Publisher, InstallDate, InstallLocation, UninstallString, Architecture, EstimatedSizeMB, and Timestamp properties. Output is sorted by DisplayName. .NOTES Author: Franck SALLET Version: 1.0.0 Last Modified: 2026-03-25 Requires: PowerShell 5.1+ / Windows only Requires: Remote registry or WinRM for remote computers .LINK https://github.com/k9fr4n/PSWinOps .LINK https://learn.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key #> [CmdletBinding()] [OutputType('PSWinOps.InstalledSoftware')] param( [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias('CN', 'DNSHostName')] [string[]]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$Name, [Parameter(Mandatory = $false)] [ValidateNotNull()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty ) begin { Write-Verbose "[$($MyInvocation.MyCommand)] Starting" $localNames = @($env:COMPUTERNAME, 'localhost', '.') $registryPaths = @{ '64-bit' = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*' '32-bit' = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' } $scriptBlock = { param( [hashtable]$Paths ) $softwareList = [System.Collections.Generic.List[object]]::new() foreach ($archKey in $Paths.GetEnumerator()) { try { $entries = Get-ItemProperty -Path $archKey.Value -ErrorAction SilentlyContinue foreach ($entry in $entries) { if (-not [string]::IsNullOrWhiteSpace($entry.DisplayName)) { $softwareList.Add([PSCustomObject]@{ DisplayName = $entry.DisplayName DisplayVersion = $entry.DisplayVersion Publisher = $entry.Publisher InstallDate = $entry.InstallDate InstallLocation = $entry.InstallLocation UninstallString = $entry.UninstallString EstimatedSize = $entry.EstimatedSize Architecture = $archKey.Key }) } } } catch { Write-Warning "Failed to read registry path $($archKey.Value): $_" } } $softwareList } } process { foreach ($computer in $ComputerName) { Write-Verbose "[$($MyInvocation.MyCommand)] Processing $computer" try { $isLocal = $localNames -contains $computer if ($isLocal) { $rawEntries = & $scriptBlock -Paths $registryPaths } else { $invokeParams = @{ ComputerName = $computer ScriptBlock = $scriptBlock ArgumentList = @(, $registryPaths) ErrorAction = 'Stop' } if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) { $invokeParams['Credential'] = $Credential } $rawEntries = Invoke-Command @invokeParams } $resultList = [System.Collections.Generic.List[object]]::new() foreach ($entry in $rawEntries) { if ($Name -and ($entry.DisplayName -notlike $Name)) { continue } $installDate = $null if (-not [string]::IsNullOrWhiteSpace($entry.InstallDate)) { try { $installDate = [datetime]::ParseExact( $entry.InstallDate, 'yyyyMMdd', [System.Globalization.CultureInfo]::InvariantCulture ) } catch { Write-Verbose "[$($MyInvocation.MyCommand)] Could not parse InstallDate '$($entry.InstallDate)' for '$($entry.DisplayName)'" } } $estimatedSizeMB = $null if ($entry.EstimatedSize) { $estimatedSizeMB = [math]::Round($entry.EstimatedSize / 1024, 2) } $resultList.Add([PSCustomObject]@{ PSTypeName = 'PSWinOps.InstalledSoftware' ComputerName = $computer DisplayName = $entry.DisplayName DisplayVersion = $entry.DisplayVersion Publisher = $entry.Publisher InstallDate = $installDate InstallLocation = $entry.InstallLocation UninstallString = $entry.UninstallString Architecture = $entry.Architecture EstimatedSizeMB = $estimatedSizeMB Timestamp = Get-Date -Format 'o' }) } $resultList | Sort-Object -Property DisplayName } catch { Write-Error "[$($MyInvocation.MyCommand)] Failed to query software on ${computer}: $_" continue } } } end { Write-Verbose "[$($MyInvocation.MyCommand)] Completed" } } |