<# .SYNOPSIS Lists installed software on one or more Windows computers, straight from the registry. .DESCRIPTION Reads the Uninstall keys (64-bit, 32-bit, and optionally the current user's) and returns one object per application with name, version, publisher, install date and architecture. Works locally, or remotely over PowerShell remoting. Doesn't touch Win32_Product. .PARAMETER ComputerName One or more computers. Defaults to this one. Remote computers need PowerShell remoting enabled. .PARAMETER Name Wildcard filter on the application name, for example '*Chrome*'. Default: everything. .PARAMETER IncludeUser Also read HKEY_CURRENT_USER, for per-user installs. Only covers the account running the query. .PARAMETER IncludeSystemComponents Include entries flagged as system components or updates, which Programs and Features hides. .PARAMETER Credential Credentials for the remote connection. .EXAMPLE .\Get-InstalledSoftware.ps1 -Name '*Java*' .EXAMPLE .\Get-InstalledSoftware.ps1 -ComputerName PC-0142, PC-0187 | Export-Csv .\software.csv -NoTypeInformation #> [CmdletBinding()] param( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('CN', 'DNSHostName')] [string[]]$ComputerName = $env:COMPUTERNAME, [string]$Name = '*', [switch]$IncludeUser, [switch]$IncludeSystemComponents, [pscredential]$Credential ) begin { # This block runs on the target machine, so it can't use anything from outside it except its arguments. $collector = { param([string]$NameFilter, [bool]$User, [bool]$System) $keys = @( @{ Path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'; Arch = '64-bit' } @{ Path = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'; Arch = '32-bit' } ) if (-not [Environment]::Is64BitOperatingSystem) { $keys[0].Arch = '32-bit'; $keys = @($keys[0]) } if ($User) { $keys += @{ Path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'; Arch = 'User' } } foreach ($key in $keys) { Get-ItemProperty -Path $key.Path -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -and $_.DisplayName -like $NameFilter } | Where-Object { $System -or (-not $_.SystemComponent -and -not $_.ParentKeyName) } | ForEach-Object { $installed = $null if ($_.InstallDate -match '^\d{8}$') { $installed = [datetime]::ParseExact($_.InstallDate, 'yyyyMMdd', $null) } [pscustomobject]@{ ComputerName = $env:COMPUTERNAME Name = $_.DisplayName.Trim() Version = $_.DisplayVersion Publisher = $_.Publisher InstallDate = $installed Architecture = $key.Arch UninstallString = $_.UninstallString } } } } } process { foreach ($computer in $ComputerName) { $isLocal = $computer -in @('.', 'localhost', $env:COMPUTERNAME) try { $argsList = @($Name, $IncludeUser.IsPresent, $IncludeSystemComponents.IsPresent) $apps = if ($isLocal) { & $collector @argsList } else { $remote = @{ ComputerName = $computer; ScriptBlock = $collector; ArgumentList = $argsList; ErrorAction = 'Stop' } if ($Credential) { $remote.Credential = $Credential } Invoke-Command @remote | Select-Object * -ExcludeProperty PSComputerName, RunspaceId, PSShowComputerName } Write-Verbose "$computer : $(@($apps).Count) application(s)" $apps | Sort-Object Name, Architecture } catch { Write-Warning "$computer : $($_.Exception.Message)" } } }