Windows/Get-InstalledSoftware.ps1
|
<#
.SYNOPSIS Inventories installed software from the Windows registry. .DESCRIPTION Reads the Uninstall registry keys to enumerate installed software on local or remote computers. More reliable than Win32_Product (which triggers MSI reconfiguration). Checks both 64-bit and 32-bit registry hives. No external modules required. Uses remote registry or CIM for remote queries. .PARAMETER ComputerName One or more computer names to query. Defaults to the local computer. Accepts pipeline input. .PARAMETER Credential Credential for remote computer access. Not needed for the local computer. .PARAMETER OutputPath Optional path to export results as CSV. .EXAMPLE PS> .\Windows\Get-InstalledSoftware.ps1 Lists all installed software on the local computer. .EXAMPLE PS> .\Windows\Get-InstalledSoftware.ps1 -ComputerName 'SERVER01','SERVER02' Lists installed software on multiple remote computers. .EXAMPLE PS> Get-Content .\servers.txt | .\Windows\Get-InstalledSoftware.ps1 -OutputPath '.\software-inventory.csv' Reads server names from a file and exports a full software inventory to CSV. #> [CmdletBinding()] param( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('CN')] [string[]]$ComputerName = @($env:COMPUTERNAME), [Parameter()] [PSCredential]$Credential, [Parameter()] [string]$OutputPath ) begin { $allResults = [System.Collections.Generic.List[PSCustomObject]]::new() $registryPaths = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*' 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' ) } process { foreach ($computer in $ComputerName) { Write-Verbose "Querying installed software on $computer" try { if ($computer -eq $env:COMPUTERNAME -and -not $Credential) { # Local computer - read registry directly foreach ($path in $registryPaths) { $items = Get-ItemProperty -Path $path -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -and $_.DisplayName.Trim() -ne '' } foreach ($item in $items) { $allResults.Add([PSCustomObject]@{ ComputerName = $computer DisplayName = $item.DisplayName DisplayVersion = $item.DisplayVersion Publisher = $item.Publisher InstallDate = $item.InstallDate InstallLocation = $item.InstallLocation UninstallString = $item.UninstallString Architecture = if ($path -match 'WOW6432Node') { '32-bit' } else { '64-bit' } }) } } } else { # Remote computer - use Invoke-Command $invokeParams = @{ ComputerName = $computer ScriptBlock = { $paths = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*' 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' ) foreach ($path in $paths) { $items = Get-ItemProperty -Path $path -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -and $_.DisplayName.Trim() -ne '' } foreach ($item in $items) { [PSCustomObject]@{ DisplayName = $item.DisplayName DisplayVersion = $item.DisplayVersion Publisher = $item.Publisher InstallDate = $item.InstallDate InstallLocation = $item.InstallLocation UninstallString = $item.UninstallString Architecture = if ($path -match 'WOW6432Node') { '32-bit' } else { '64-bit' } } } } } ErrorAction = 'Stop' } if ($Credential) { $invokeParams['Credential'] = $Credential } $remoteResults = Invoke-Command @invokeParams foreach ($item in $remoteResults) { $allResults.Add([PSCustomObject]@{ ComputerName = $computer DisplayName = $item.DisplayName DisplayVersion = $item.DisplayVersion Publisher = $item.Publisher InstallDate = $item.InstallDate InstallLocation = $item.InstallLocation UninstallString = $item.UninstallString Architecture = $item.Architecture }) } } Write-Verbose "${computer}: Found $($allResults.Count) software entries" } catch { Write-Warning "Failed to query $computer`: $_" $allResults.Add([PSCustomObject]@{ ComputerName = $computer DisplayName = "ERROR: $_" DisplayVersion = $null Publisher = $null InstallDate = $null InstallLocation = $null UninstallString = $null Architecture = $null }) } } } end { $allResults = @($allResults) | Sort-Object -Property ComputerName, DisplayName if ($OutputPath) { $allResults | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 Write-Output "Exported $($allResults.Count) software entries to $OutputPath" } else { Write-Output $allResults } } |