PowerShellでインストールされているソフトウェアのバージョン情報を取得する方法を現在の環境で使うなら「classic desktop appは64/32bit Uninstall registryを読み、packaged appはGet-AppxPackage、package manager管理はwinget listで補います。一つのsourceで全softwareを網羅できないことを明記します。」が基本方針です。対象はWin32_ProductはMSI consistency checkを誘発し得るためinventoryの既定にしないケースです。inventory取得はread-onlyに限定し、導入・削除操作を結果判定から切り離します。
machine・WOW6432・user scopeを分離する
ソフトウェア版数調査では、マシン全体とユーザー単位、32-bitと64-bitの登録場所を分けて読み、Name、Publisher、製品IDで対象を一意化します。Win32_Productは再構成を誘発する可能性があるため棚卸しには使いません。レジストリやパッケージ管理情報の出典を残し、複数版の共存を欠損扱いしません。
- OS architectureとuser/system scopeを確認する
- Uninstall registryの64bit/32bit両pathを読む
- MSIX/AppXとportable appを別sourceとして扱う
- product名、publisher、versionの正規化ruleを決める
Uninstall registryから表示情報を読む
registryとpackage一覧の読取は通常変更しません。Win32_Product query、uninstall string実行、COM application起動をinventoryに使わず、本番appへrepairや初期化を誘発しません。結果にはinstall path、publisher等が含まれるのでaccessを限定します。変更やuninstallはcurrent version、installer source、user data backup、rollback packageを確認する別作業にします。
Appx packageを別inventoryとして取得する
- Win32_Productを全software一覧に使う
- HKLM 64bit pathだけを見る
- per-userとmachine-wideを混同する
- DisplayVersionを数値として単純比較する
- portable appを未導入と断定する
winget listは補助sourceとして扱う
machine-wide Uninstall keysを読む
$paths='HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*','HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
Get-ItemProperty $paths -ErrorAction SilentlyContinue | Where-Object DisplayName |
Select-Object DisplayName,DisplayVersion,Publisher,InstallLocation,PSPath
registry読取はinstallerを実行しません。重複keyをproduct IDで整理します。
current user scopeを確認
Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' -ErrorAction SilentlyContinue |
Where-Object DisplayName | Select-Object DisplayName,DisplayVersion,Publisher
別userのper-user appは見えません。scopeをinventory結果へ記録します。
packaged appを取得
Get-AppxPackage | Select-Object Name,PackageFullName,Version,Publisher
current user分です。AllUsersは権限と共有範囲を確認します。
winget inventoryを補助
winget list --accept-source-agreements
source合意やnetwork policyを確認します。すべてのappが一意にmappingされる保証はありません。
特定publisherをexactに絞る
Get-ItemProperty $paths -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -eq 'Target Application' -and $_.Publisher -eq 'Expected Publisher' } |
Select-Object DisplayName,DisplayVersion,Publisher,PSChildName
-likeで似たproductを混ぜず、publisherとproduct codeも見ます。
DisplayNameとPublisherをexactに絞る
DisplayVersionはvendor文字列でsemantic versionとは限りません。更新helperやlanguage packが別entryとして出る場合があり、同じDisplayNameの32/64bitもあります。registryに無いportable app、per-user app、Store appを「未導入」と断定しません。Win32_ProductはMSI製品だけで副作用があるため避けます。
Win32_Productをinventoryへ使わない
Settings Installed apps、vendor applicationのAbout、winget list、registryをsample数件で照合します。inventoryには取得host、user scope、source、timeを付け、重複排除前後の件数を記録します。version判定はvendorのsupport表と比較し、文字列の大小だけで新旧を決めません。
Unique・Ambiguous・NotFoundを判定する
classic desktop appはHKLM/HKCUのUninstall key、MSIXはGet-AppxPackage、package manager管理分はwinget listで補います。DisplayVersionはvendor文字列なのでsemantic versionへ無条件変換せず、sourceとscopeを保ったinventoryにします。
三つのUninstall scopeを一つの表へ統合する
$roots='HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*','HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*','HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
Get-ItemProperty $roots -ErrorAction SilentlyContinue | Where-Object DisplayName | Select-Object @{n='Source';e={'UninstallRegistry'}},DisplayName,DisplayVersion,Publisher,PSChildName,PSPath
一意・複数・registry未検出を分ける
- productを一意化:product名・publisher・IDをexactに絞り、scope/sourceごとにversionを一意に取得する
- registry未検出:一sourceで0件でもportable/MSIX/別userの可能性を残しNotFoundInSourceとする
- scope読取error:registry access、AppX service、winget source errorは各sourceのError状態として集計する
Win32_ProductはMSI consistency checkを誘発し得るためinventoryに使いません。DisplayNameのlike検索だけで製品を決めず、32/64bit key、Publisher、PSChildName/package IDを合わせます。
同名別publisherとuser別導入を試す
$r=@(Get-ItemProperty $roots -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -eq 'Target Application' -and $_.Publisher -eq 'Expected Publisher' })
[pscustomobject]@{State=if($r.Count -eq 1){'Unique'}elseif($r.Count){'Ambiguous'}else{'NotFoundInRegistry'};Count=$r.Count}
machine/user、32/64bit、MSIX、portable、重複entry、source failureをtestします。product ID、publisher、scope、source、raw version、path、取得host/timeを保存します。
registryにないことは未導入の確定ではありません。MSI/EXE、Appx、portable、winget sourceを分け、製品名だけでなくpublisherとscopeを合わせて判定します。

コメント