param(
[ValidateSet('list','find','by_product_code')][string]$Action = 'list',
[string]$Name,
[string]$ProductCode
)
$ErrorActionPreference = 'Stop'
$roots = @(
'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'
)
function Read-AppsFromRoot([string]$root) {
if (-not (Test-Path -LiteralPath $root)) { return @() }
$items = Get-ChildItem -LiteralPath $root -ErrorAction SilentlyContinue
$rows = @()
foreach ($it in $items) {
try {
$p = Get-ItemProperty -LiteralPath $it.PSPath -ErrorAction Stop
if (-not $p.DisplayName) { continue }
$uninst = $p.UninstallString
$quiet = $p.QuietUninstallString
$guid = $null
if ($uninst -match '\{[0-9A-Fa-f\-]{36}\}') { $guid = $Matches[0] }
$rows += [pscustomobject]@{
KeyPath = $it.PSPath
DisplayName = $p.DisplayName
DisplayVersion = $p.DisplayVersion
Publisher = $p.Publisher
InstallLocation = $p.InstallLocation
UninstallString = $uninst
QuietUninstallString = $quiet
ProductCode = $guid
}
} catch { }
}
return $rows
}
$all = @()
foreach ($r in $roots) { $all += Read-AppsFromRoot -root $r }
switch ($Action) {
'list' {
$all | ConvertTo-Json -Compress
break
}
'find' {
if (-not $Name) { throw 'Name is required for Action=find' }
$needle = $Name.ToLower()
$all | Where-Object { $_.DisplayName -and $_.DisplayName.ToLower().Contains($needle) } | ConvertTo-Json -Compress
break
}
'by_product_code' {
if (-not $ProductCode) { throw 'ProductCode is required for Action=by_product_code' }
$pc = $ProductCode.ToLower()
$all | Where-Object { $_.ProductCode -and $_.ProductCode.ToLower() -eq $pc } | ConvertTo-Json -Compress
break
}
default { throw "Unknown action: $Action" }
}