🦔
【winax】node.js で Windows に接続されている USB メモリのデバイスインスタンスパスを取得するまで
動機
node.js から Windows に接続されている USB メモリのデバイスインスタンスパスを取得したいが、child_process
の結果を正規表現で解析したくない。どうしたらよいだろう?
結論
- winax を利用する
- WMI オブジェクトの実態は SWbemServices オブジェクトのため、シグネチャはここを参照する
- ただし、Visual Basic 向けなので適宜読み替える必要がある😭
- WMI クラス名 (
Win32_USBHub
など) の一覧は CIMWin32 WMI プロバイダーから閲覧できる- 必要そうなものは大体あるが、一部はこのページのツリーの外にも存在すると思う😭
- WQL (WMI 用の SQL 方言) の仕様は WQL を使用したクエリから参照できるが、自分は
Select * from ... where ...
程度しか利用していないので特に方言だとは意識しなかった
import * as ActiveX from 'winax'
const wmi = new ActiveX.Object('WinMgmts:', { getobject: true })
const collection = wmi.InstancesOf('Win32_USBHub')
for (let index = 0; index < collection.Count; ++index) {
console.log(collection.ItemIndex(index).DeviceID)
}
実装に至るまでの過程
- 実装のみ知りたい方は読み飛ばして構わない
- 結構な長旅だったため、今後難しい課題にぶつかったときに、同様に解決できるとよいなと思う
libusb アプローチ
- python 向けではあるものの、pyusb を発見した
- node.js にも C/C++ API は存在するので、libusb の node.js 向けバインディングが実装されているはず
- node-usb を発見
- pyusb の方を
$ pip install
して少し実装してみるが、デバイスインスタンスパスを取得できず- libusb の Wiki にたどり着く
- Windows がデバイスマネージャにおける「USB 大容量記憶装置」向けにデフォルトでインストールするデバイスドライバは USBSTOR のようである
- このドライバは libusb と互換性がなく、デバイスインスタンスパスの取得を含むほとんどの機能が利用できない
- デバイスドライバを WinUSB に置き換えれば確かに取得できるが、今度はエクスプローラから USB メモリにアクセスできなくなる
- USB メモリへの GUI ベースアクセスは USBSTOR に依存しているのであろう
- この方法は諦めて、別の方法を探すことにした
WMI アプローチ
- デバイスインスタンスパスはデバイスマネージャに表示される以上、Windows API により取得できるはず
- ChatGPT が pywin32 を利用するサンプルコードを提示する
- とはいえ、Windows API を直接利用するのはだいぶ面倒
- Windows API よりもう少し楽な方法を探したい
- pywin32 の中でも、
win32com.client
を利用しているページを発見
wmi = win32com.client.GetObject("WinMgmts:") wmi.InstancesOf("Win32_USBHub")
- どうやら WMI (Windows Management Instrumentation) というサービスを利用しているらしい
- WMI は COM オブジェクトの一種であり、また、Microsoft は COM 技術の呼び名を頻繁に変えた歴史があるようだ
- OLE, COM, (悪名高き?) ActiveX など…
- この辺の単語と node.js で検索して、winax を発見
- インターフェースは
new ActiveX.Object
1つだけのようだ
- インターフェースは
- pywin32 の中でも、
- WMI サービスオブジェクトをどうにか取得したい
-
win32com.client.GetObject
相当をどのように呼べばよいか - GitHub の右上の検索窓に
repo:durs/node-activex GetObject
と入れてみると、以下がヒットする
global.GetObject = function GetObject(strMoniker) { return new g_winax.Object(strMoniker, {getobject:true}); }
- 第2引数の
{ getobject: true }
が肝のようだ
-
- WMI サービスオブジェクトを使う方法を知りたい
- スタブがないので型がわからない
- ソースも C++ なのであまり読み解くことができない
- WMI GetObject で検索すると、Microsoft の VBScript のサンプルコードが出てくる
- インターフェースが全く同じであることから、これを JavaScript に変換すれば動作しそう
-
new ActiveX.Object('WinMgmts:', { getobject: true })
で返ってくるのは SWbemServices オブジェクト のようである- この通りにスタブファイルを書いてみることで、(ある程度は) 型安全になった
- 以下は型定義ファイル(一部抜粋)
type SWbemClassName = 'Win32_USBHub' type SWbemServices = { InstancesOf<T extends SWbemClassName>(className: T): SWbemObjectSet<T> }
- 高速にデバッグをしたい
- プロジェクトに書き込んでいると、TypeScript + webpack (esbuild に移行したい) という構成のため、それなりに1イテレーションが長め
- このため、JScript を記述する
- 実は Windows にはデフォルトで使用できるスクリプトランタイムがある
- そのフロントエンドは JavaScript 風の JScript と、Visual Basic 風の VBScript が存在する
- Visual Basic は残念ながらよくわからないので JScript を記述してデバッグする
- どこかで JScript は ES3 程度だという記述を見たことがあるので、そこには気を付ける
-
const
やlet
、for ... of
ループは存在しない - ブラウザとは環境がだいぶ違って、
console
は存在しないのでWScript.Echo
する - 一応
Enumerator
という便利なクラスも存在するようだが、TypeScript に移植すると動作しないので使用しないでおく
var wmi = GetObject('WinMgmts:') var collection = wmi.InstancesOf('Win32_USBHub') for (var index = 0; index < collection.Count; ++index) { console.log(collection.ItemIndex(index).DeviceID) }
- このように printf デバッグをしながら型定義ファイルを詰めていき完成
Discussion