WMIコネタ2008年06月18日 03時12分05秒

chkconfigモドキとかpsモドキとかで久々にWMIに触ったらなんだか面白かったのでちょろちょろといじくっているのだが、そのときに見つけたりしたコネタをば。正確には前も調べたけどすっかり忘れてたってとこなんだが。

まずはEnumeratorを拡張するが吉

WQLで問い合わせた結果はいわゆる「コレクション」が返ってくる。VBSの場合は何も考えずFor Eachを使えるのだが、JScriptの場合はEnumeratorでくるんでやる必要がある。まぁWMIに限らずWSH/HTAでCOMを触る場合はそうなるのだが。

んで好みの問題だと思うけど、WMIみたいなメタデータ中心のオブジェクトを扱う場合はコレクションのループ処理を多用するハメになるので、Enumeratorでeachなんかが使えるように拡張しといたほうが便利かな、と。

最低限のサンプル。

Enumerator.prototype = function(iterator) {
	var i = 0;
	for(this.moveFirst(); ! this.atEnd(); this.moveNext()) {
		iterator( this.item(), i++ );
	}
};

このままだとprototype.jsみたいにcontinue/breakの制御ができないけど、まぁコレクションを一通りまわすことはできるのでこれだけでも結構快適になる。

後で触れるけど、WMIのデータそのままだとちょっと具合が悪い型もあるので、これにmapとtoArrayでも加えればいいんじゃないかと。

Enumerator.prototype.map = function(iterator) {
	var results = [];
	this.each( function(item, index) {
		results[ results.length ] = iterator( item, index );
	} );
	return results;
};
Enumerator.prototype.toArray = function() {
	return this.map( function(item) { return item; } );
};

datetime型

例えばWin32_ProcessクラスのCreationDateプロパティなんかがこの「datetime」型なのだが、JScriptで取り回すと次のような文字列に暗黙で変換される。

var wmi = new ActiveXObject("WbemScripting.SWbemLocator").ConnectServer();
var query = "SELECT * FROM Win32_Process WHERE Name = 'firefox.exe'";

var firefox = new Enumerator(wmi.ExecQuery(query)).toArray()[0];
if( firefox ) {
  WSH.Echo( firefox.CreationDate ); // '20080618004435.514465+540'
}

要するに「年月日時分秒.マイクロ秒GMT時差」という形式。なので、こんな感じでDateにすり合わせてやるとよいかと。
var reg = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d+)(.\d+)$/;
var parts = reg.exec( firefox.CreationDate );
WSH.Echo( new Date(
	parts[1], parts[2], parts[3],
	parts[4], parts[5], parts[6],
	parseInt( parts[7] / 1000 )
) );
// → 'Fri Jul 18 00:44:35 UTC+0900 2008'
こんなゴミゴミした正規表現じゃなくてsubstrとかでもいいんだけども。

値が配列なプロパティの扱い方

例えばWin32_NetworkAdapterConfigurationクラスのDefaultIPGatewayプロパティやIPAddressプロパティなんかがそうなのだが、値が配列の場合がある。

こういったWMIの配列は、そのままJScriptのArrayに変換されないらしい。

query = "SELECT IPAddress FROM Win32_NetworkAdapterConfiguration";
var nics = new Enumerator(wmi.ExecQuery(query)).toArray();
// 先頭のNICにIPアドレスが割りあたってるとして、
WSH.Echo( typeof(nics[0].IPAddress) ); // → unknown

しかもこの unknownオブジェクト、Enumeratorに食わせることもできない
new Enumerator( nics[0].IPAddress );
// → 'オブジェクトがコレクションではありません'例外

こういう場合はVBArrayを使うんだそうな。

VBArrayってのはEnumerator同様JScript独特のオブジェクトで、

Visual Basic のセーフ配列へアクセスする方法を提供します。
ってなオブジェクトだそうな。幸いなことに(というか、目的考えると当たり前か)toArray()でJScriptのArrayに変換することができる。

ってわけで、

WSH.Echo( new VBArray( nics[0].IPAddress ).toArray().join("\n") );
// → IPアドレスがバインドされてる数だけ出力される

VBArrayをEnumerableにするってのもよいかも。今度やってみよう。

ちなみにProperty_で動的列挙をする場合はIsArrayプロパティがtrueになっているので、それで判別できるかと。

query = "SELECT * FROM Win32_NetworkAdapterConfiguration";
nic = new Enumerator(wmi.ExecQuery(query)).toArray()[0];

new Enumerator(nic.Properties_).each( function(prop) {
	WSH.Echo( [
		"プロパティ名:", prop.Name, "\t",
		"データ型:", prop.CIMType, "\t",
		"配列?:", prop.IsArray
	].join( "" ) );
	// 例(一部)
	// プロパティ名:MACAddress  データ型:8  配列?:false
	// プロパティ名:IPAddress   データ型:8 配列?:true
} );