システムに接続されているUSBデバイスをWindows上で列挙する方法は、本連載でも以前の回(「WindowsのコマンドラインからUSBデバイスについて調べる方法」)で解説した。
しかし、この方法はUSBデバイスを列挙するだけで、その接続関係などを表示するわけてはない。レジストリには、デバイスの親子関係や接続位置に関する情報がある。これを利用することで、USBデバイスに限らず、Windowsが管理しているデバイスの親子関係を得ることが可能だ。しかし、レジストリの情報もデバイスの情報の一部のみで、WMI(Windows Management Instrumentation)/CIM(Common Information Model)で得られたデバイスの情報と組み合わせる必要がある。
そのためには、デバイスを列挙するWin32_PNPEntryなどで扱う「DeviceID」の構造を理解する必要がある。
WindowsにおけるDeviceIDとは?
上記の記事でも解説したようにデバイス関連の情報はPowerShellのGet-CIMInstanceコマンドで得ることができる。このとき、個々のデバイスは「DeviceID」を持つ。DeviceIDは、逆スラッシュと「&」を区切りとして複数の情報をまとめた文字列である。たとえば、USBデバイスの場合、
USB\VID_05E3&PID_0608\5&3A692CE1&0&9
のような形式になっている。
このうち、最初の「\」の前にある「USB」は、USBデバイスであることを示す。「\」の後の「VID_05E3&」は、デバイスのメーカー(ベンダー)IDが「05E3」であり、「PID_0608」は、ベンダーが決めたプロダクトIDは「0608」であることを示す。
2つ目の「\」の後ろにある「5&3A692CE1&0&9」は、特定のUSBデバイス同士を区別するためのものだ。USBなどWindowsのデバイスには、同一メーカーの同一種のデバイスが同時に複数使われることがある。デバイスIDの前半部分は、デバイスのメーカーや製品を区別するための情報で、後半は実在のデバイスを区別するための情報だ。ここでは、この部分を借りに「インスタンスID」と呼ぶことにしておく。
インスタンスIDには、親デバイスが持つIDと、個々のデバイスが持つ値(USBならアドレスやエンドポイント番号など)を組み合わせた値が使われる。このようにUSBのデバイスIDは、「\」で3つの部分に分かれる。
レジストリのデバイス情報
デバイスに関する情報は、以下のレジストリキーの下にある。
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum
この直下は、「USB」や「PCI」など種別を表すキーがある。USBデバイスは、USBやHID、USBSTORキーの下に登録されている。さらに下を見ると「VID_XXXX&PID_XXXX」というキーがあり、さらに、その下に16進数と「&」を組み合わせたキーがある。ここまでのキーの並びは、前記のデバイスIDに一致する。
レジストリでは、キーのパスをファイル同様に「\」で区切る。デバイスIDは、前記のキーのサブキーと一致するようになっている。簡易には、前記のレジストリキーの後に「\」を置いてデバイスIDをつなげると、該当のデバイスを示すレジストリキーと同じになる。この仕組みが理解できれば、デバイスIDを使って、レジストリから該当のデバイスに関する情報を得ることができる。
多くの情報は、前記のインスタンスIDに相当するキーの部分にある。重要な情報としては、デバイスの親子関係(USBハブとそこに装着されたデバイスのような関係)を示す「ParentIDPrefix」と、デバイスの接続位置を示す「LocationInfomation」がある。
ParentIDPrefixは、親デバイスが持つ情報で、この親デバイスに接続している子デバイスのインスタンスID前半部分と一致する(だからPrefixと呼ばれる)。逆にいうとParentIDPrefixを持つデバイスは、子デバイスを持つことができる(親デバイスになる可能性がある)デバイスである。たとえば、通常のマウスやUSBメモリなどは、親デバイスになる可能性はないが、USBハブやUSBコントローラーなどは、子デバイスを接続することができる。
デバイスの親子関係は、このParentIDPrefixとインスタンスIDから推測することが可能だ。子デバイスを列挙するには、ParentIDPrefixをインスタンスIDの一部に持つデバイスを探せばよい。
これに対して、LocationInfomationは、デバイスの論理的な接続位置などを示す情報だ。USBハブなどに複数のポート(ダウンストリームポート)があれば、それぞれを区別する情報などがこのLocationInfomationに記録される。ただし、必ずしも情報が提供されるとは限らないこと、USB複合デバイス内のエンドポイントの接続位置を示す場合もあることなどから、必ずしも使いやすい情報ではない。
デバイスIDとレジストリ情報を対応させる
まずは、USBデバイスを全部列挙してみる。それには「Win32_USBControllerDevice」クラスを使い、以下のようなPowerShellコマンドを実行する。なお、結果を利用するため、変数$USBに保存する。以下、コマンドラインをコピーしてPowerShellのコンソールに貼り付けて実行できる。
$USB=(Get-CimInstance Win32_USBControllerDevice).Dependent.DeviceID
このリストには、DeviceIDしか記録されていない。というのもWin32_USBControllerDeviceクラスは、親であるUSBコントローラーのDeviceIDを持つAntecedentプロパティと、そこに接続するデバイスのDeviceIDを記録したDependentプロパティにしか有効な値がないからである。
なお、BluetoothコントローラーがUSB接続の場合、ペアリングしたBluetoothデバイスなどもこのコマンドの出力に含まれるが、この段階ではそのままにしておく。結果を見たい場合には、
$USB | Out-GridView
とする。
個々のデバイスの情報は、Win32_PnPEntityクラスでしか得られない。そこで、この結果($USB)を使って、Win32_PnPEntityクラスの実行結果からUSBデバイスだけを抜き出す。
$PNPUSB=Get-CimInstance Win32_PnPEntity | ? DeviceID -in $USB
ここでも同じく結果を$PNPUSBという変数に記憶させている。結果を見たいなら、$USBと同じくOut-GridViewを使う。
この$PNPUSBの結果と前述のレジストリの結果を組み合わせる。まずは、HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enumキー以下にあるレジストリキーから、ParentIDPrefixとLocationInfomationプロパティを全部探してDeviceIDと対にしたオブジェクトを作る。
$x=Get-ChildItem HKLM:\SYSTEM\CurrentControlSet\Enum -Recurse -ErrorAction SilentlyContinue -Depth 2 | %{ [pscustomobject]@{ ID=($_.Name -replace "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum\\",""); Parent=(Get-ItemProperty -Path $_.PSPath).ParentIDprefix; Location=(Get-ItemProperty -Path $_.PSPath).LocationInformation; } } | ? {$_.Parent -ne $null -or $_.Location -ne $null }
レジストリの値をインスタンスID相当のキーがある2階層目まですべて列挙し、それぞれに対してParentIDprefix、LocationInformationをプロパティに入れている。Childプロパティはあとで利用するためのもの。全部終わったら、ParentIDprefix、LocationInformationがともに、ヌル値であるものをWhere-Objecr(“?”がエイリアス)で除外して変数$xに入れている。
最後に全部を合わせたオブジェクトを作る。
$r=$USB | %{ [pscustomobject]@{ Id=$_; Parent=$(($x | ? ID -eq $_).Parent);Location=$(($x | ? ID -eq $_).Location); child=@(); Entry=($PNPUSB | ? DeviceID -eq $_)}}
これは、USBデバイスのリスト($USB)を元に新規にオブジェクトを作成するもの。すべてのUSBデバイスに対して、DeviceID、ParentIDPrefix、LocationInfomationおよびPNPEntryの内容を持つオブジェクト(PSCustomObject)を作成している。
親子関係を判定するには、デバイスIDである$r.IdのインスタンスID部分が、$r.Parentと先頭一致するものを探せばよい。
$r | ? Parent -ne $null | %{ $temp=($PNPUSB | ? DeviceId -like "*$($_.Parent)*"); Write-Output "$($_.id) ----- ParentID: $($_.Parent)`n`t$($temp.DeviceID -join "`n`t")" } | Out-GridView
これで親子関係はわかるが、このコマンドでは孫デバイスがあることは考慮されていない。親子関係をたどってツリー構造を表示させるには、再帰的な処理が必要で、PowerShellのコマンドラインではちょっと難しそうだ。なのでプログラム(関数)を書く必要がある。次回はこのあたりを考えることにする。
この連載の記事
-
第460回
PC
Windowsでsftpを使う -
第459回
PC
WSL 2.4.4ではtar形式でのディストリビューションが配布でき、企業での利用が容易になってきた -
第458回
PC
Windows上でhostsファイルを活用する -
第457回
PC
IPv6アドレスは先頭を見ればどんな種類かわかる -
第456回
PC
あらためてIPv6基本のキ -
第455回
PC
Windowsで現在どのネットワークアダプタがインターネット接続に使われているかを調べる方法 -
第454回
PC
Windows 11 24H2では「デバイスの暗号化」の条件が変わり、より多くのPCでドライブが暗号化される -
第453回
PC
Windows 11 24H2の配布開始後もすぐにはやってこない Windows UpdateとSafeguard Holds -
第452回
PC
Windows 11 Ver.24H2が登場 Copilot+ PCとそうでないPCで実質Windowsが2つに分かれる -
第451回
PC
新しいWindowsサンドボックスではコマンドラインからの制御が可能に -
第450回
PC
ユニコードで文字数を数える方法 - この連載の一覧へ