WindowsでのWin32アプリケーションのインストール状態を調べる方法
文●塩田紳二 編集● ASCII
2020年02月02日 10時00分
以前は無法地帯だったWindowsにおけるアプリインストール
過去の本連載で、WindowsでのUWPアプリのインストール状態を調べる方法を解説したが(「Windows 10のUWPアプリのインストール状態をコマンドラインで調べる」)、Win32アプリケーション(デスクトップアプリケーション)のインストールに関してはどうだろうか?
少なくとも、コントロールパネルの「プログラムと機能」には、インストールされているアプリケーションのリストが表示されるのでどこかに情報が記録されていることは間違いない。また、現在では、Win32アプリケーションのインストールは、原則マイクロソフトインストーラーを使うことになっている。
昔のWindowsでは、アプリケーションが個別にインストーラーを持っており、アンインストール処理なども同様で、一種の無法地帯になっていた。しかし現在では、正しい方法を使わないとアプリのインストールができないようになっている。とはいえ例外もあり、単に実行ファイルをコピーすれば動くという場合、インストール作業が不要で、Windows自体もこれを管理しない。
でもまったく管理しないのではなく、以前紹介したように、インターネットからダウンロードした実行ファイルに関しては、実行ファイルに「代替データストリーム」でZone.Identifierという情報が付けられ、実行前に警告できるようにはなっている(「インターネットからダウンロードしたファイルはZone.Identifierでセキュリティ管理をする」)。
インストール状態を調べる方法について探ってみた
具体的にインストール状態を調べる方法があるか、まずはググってみたが、とりあえずマイクロソフトのウェブサイトに以下のようなページがあった。
●ソフトウェア インストールの操作
https://docs.microsoft.com/ja-jp/powershell/scripting/samples/working-with-software-installations?view=powershell-7
このページによれば、2つの方法でインストールしたWin32アプリケーションのリストを得ることができるという。1つは、WMI(Windows Management Instrumentation)を使う方法で、もう1つはレジストリを見る方法である。前者は、PowerShellから
Get-CimInstance -Class Win32_Product
というコマンドを使う。
なお、Cim(Common Information Model)は、WIMの元になった仕様の名称で、最近はこちらを使うようだ。PowerShellにも「Get-WmiObject」というコマンドレットがあったのだが、このようなWmiという名称を使うコマンドに対して、Cimという名称を持つ新しいコマンドが作られた。
機能的にはほとんど同じだが、Cim系コマンドのほうが後から作られたため、改良が加えられている。もちろん、手慣れているならWmi系のコマンドを使ってもかまわない。
もう1つの方法は、以下のレジストリ以下を調べる方法だ。
\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
とりあえずマイクロソフトが主張する公式の方法なので、この2つで得られた情報を見てみたが、どうも見当たらないアプリケーションがいくつかある。コントロールパネルのプログラムと機能には表示されるのに、前記2つから得られたオブジェクトには、該当のアプリが見当たらないのである。これは、何かが足らないのだと思われる。
しかし、前者はWmiなので、細かく調べるわけにもいかない。実際にはWmiの下でWin32APIが動作しているのだが、機械語コードの中なので、簡単に見るというわけにもいかないのである。
そこでレジストリのほうをもう少し探してみることにした。まずは「Uninstall」というキーをレジストリ中から検索した。そして見つかったもののうち、前記レジストリとは中身が違いそうなキーとして、以下の2つがあった。
\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall
これ以外にも、いくつか「Uninstall」キーを持つレジストリ項目があったのだが、中身を見た感じ、上記3つのキーとは雰囲気が違っていたり、サブキーが何もなったり、などどうも違う感じがする。そんなわけで、まずはWMIとレジストリ3つの情報を合わせて、インストールされているアプリケーションの情報を取り出して見ることにした。
以下、話を簡単にするため、前記3つのレジストリをそれぞれ順に「HKLM」「WOW」「HKCU」と略す。
PowerShellでスクリプトを作った
マイクロソフトのページがPowerShellだったので、PowerShellでスクリプト(といってもコマンドを並べたもの)を作った。ところがこれが少しハマってしまった。本質的な部分ではなく、CSVのエクスポートという部分だ。こういう情報は、CSVなどにしてExcelに放り込むに限る。重複の検出や削除、並べ替え、検索などが自由にできる。こういう大量の情報を吐くAPIの結果を分析するのにExcelは最適である。
ところが、get-CimInstanceコマンドが出力するオブジェクトと、レジストリを読み出してできるレジストリは同一ではない。PowerShellのExport-CSVコマンドは最初に出力したオブジェクトを覚えていて、追加するときにオブジェクトが違うとエラーになる。
「-Force」オプションで強制的に違うオブジェクトを出力させることもできるのだが、出力できるのは、最初のオブジェクトのプロパティと同じプロパティ名の情報だけだった。そういうわけで、なんだか出力がおかしいけど、Export-CSV使わずにコンソールで見ると何もおかしくないという状態で、問題はExport-CSVにあるということに気がつくまで半日かかった。
とりあえず解決方法として自分でカンマ区切りの出力としてCSVファイルを作成した。
$f="C:\temp\win32appInstall-ListFile.csv"
$p= "`"{0}`",`"{1}`",`"{2}`",`"{3}`",`"{4}`",`"{5}`",`"{6}`""
$o1=(Get-ChildItem -Path 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' | ForEach-Object { Get-ItemProperty $_.PsPath})
$o2=(Get-ChildItem -Path 'HKLM:SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall' | ForEach-Object { Get-ItemProperty $_.PsPath})
$o3=(Get-ChildItem -Path 'HKCU:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' | ForEach-Object { Get-ItemProperty $_.PsPath})
$o4=(Get-WmiObject Win32_Product)
#
$o1 | ForEach-Object {($p+",`"HKLMMS`"") -f $_.PSChildName,$_.Publisher,$_.DisplayName,$_.DisplayVersion,$_.installDate,$_.installlocation,$_.installsource } | Out-File -Encoding default $f
$o2 | ForEach-Object {($p+",`"HKLMWOW`"") -f $_.PSChildName,$_.Publisher,$_.DisplayName,$_.DisplayVersion,$_.installDate,$_.installlocation,$_.installsource } | Out-File -append -Encoding default $f
$o3 | ForEach-Object {($p+",`"HKCUMS`"") -f $_.PSChildName,$_.Publisher,$_.DisplayName,$_.DisplayVersion,$_.installDate,$_.installlocation,$_.installsource } | Out-File -append -Encoding default $f
$o4 | ForEach-Object {($p+",`"WMI`"") -f $_.IdentifyingNumber,$_.vendor,$_.name,$_.version,$_.installdate,$_.installlocation,$_.installsource } | Out-File -append -Encoding default $f
なお、これは常識かもしれないが、ExcelにCSVを読み込ませるとき、シフトJISコードにしないと、区切り位置がおかしくなることがある。仕様としてはBOM付きのUTF8をサポートしているはずなのだが、ときどき正しく読み込んでくれないことがある。タブや他の区切り文字を使うという手もあるのだが、こっちもExcelが読み込み時に勝手にセルに分割することもあれば、そうでないこともあって、面倒である。特に日本語が含まれる可能性がある場合、シフトJISのCSVにするのが一番確実な感じである。
CIMとレジストリで共通にあり、インストールされているアプリケーションを知るのに必要なプロパティの対応は、以下の表のようになる。リストのプログラムは、これを出力するようにした。なお、CSVの最後の欄は、情報ソースを示す。
出力をExcelで調べたところ、Cimから得た情報と「WOW」から得た情報は大半が重複しており、違いはわずかに3つ。そのうち1つは、何も情報が記録されておらず、エントリーだけだった。また、残り2つは、「Microsoft .NET Framework 3.5 Targeting Pack」の英語版と日本語版。これは、Visual Studioがインストールする開発時に利用するもので通常利用するプログラムではない。なので、実用的には、レジストリの値だけを調べれば十分そうだ。
CIMコマンド(Get-CimInstance)は、実行にかなり時間がかかる。前記のMicrosoftのページにも、「Win32_Product クラスはクエリ用に最適化されていません」との記述がある。このコマンドを実行すると、インストールされているパッケージの整合性のチェックが発生し、場合によっては修復も行なわれるらしい。ということで、面倒な感じである。
となると、前記の3つのレジストリだけを調べれば、Win32アプリケーションの情報が得られそうだ。たとえば、以下のリストのようにすれば、Publisherが「株式会社」で始まるアプリケーションだけを表示できる。
(Get-ChildItem -Path ('HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','HKLM:SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall','HKCU:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall') | ForEach-Object { Get-ItemProperty $_.PsPath}) | Where-Object { $_.Publisher -like "*株式会社*" } | Sort-Object Publisher | Format-Table -AutoSize displayversion,displayName -GroupBy Publisher
■関連記事