IronRuby-WMI (1)

cuzic です。

JavaScript の柔軟な使い方の勉強として、WMI (Windows Management Instrument)のラッパーを、Windows Script Host で実行する環境向けに作ってみようと思ってみました。

PowerShell と似たノリで、

var klass = get_wmiobject("Win32_Process");
var instances = klass.GetInstances();
forEach(instances, function(instance){ // forEach の実装は省略
  WScript.Echo(instance.Caption); // 実行中のプロセス名称を表示
});

というようなインタフェースで使えるようにできたらいいな、と思ったのだが、いろいろ調べてみるとなかなか大変であることが分かった。

例を使って説明すると、

  var klass = get_wmiobject("StdRegProv");
  HKEY_LOCAL_MACHINE = 0x80000002;
  var arrValueNames, arrValueTypes = klass.EnumValues(HKEY_LOCAL_MACHINE, "SOFTWARE\ODBC\ODBCINST.INI\ODBC Drivers");

のように、複数の返り値(ActiveX のメソッドでは out パラメータ)がある場合は、このように自然に書きたかったが、この仕様を満たすような実装は JavaScript では実現できない。

実現できない理由は、

  • instance.Caption のようなアクセスを許容するには、WMI オブジェクトを生で扱うよりない(JavaScript には C# でいうプロパティの実装手段がないため)
  • 一方、この場合の EnumValues を実現するには ActiveX を生で呼び出すのではなく、JavaScript のクラスなどでラップする必要がある
  • そもそも、JavaScript には多重代入がない。

からだ。

これらは、Ruby であれば実現できる。というわけで、ふと習作として Ruby で実現したら、どうなるかを考えて、JavaScript 版をどうするかを考え直してみたいと思った。

単純に、Ruby で実装してみても仕方がないので、最近話題になっている IronRuby で書いてみた。

http://github.com/cuzic/ironruby-wmi

require 'System';
require 'System.Management, Version=2.0.0.0, 
     Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'

class String
  def underscore
    scan(/[A-Z][a-z]+/).to_a.map(&:downcase).join("_")
  end
end

module WMI
  class WMIError 

IronRubyRuby の柔軟性をどこまで引き出せるかのテストみたいな状態になってしまっているが、

  • const_missing を活用することで
 WMI::Win32_Process.get_instances

などのように即座に WMI クラスを利用可能

  • define_method も駆使して動的なメソッド定義を行い、WMI のプロパティを自然にアクセスできるように工夫
  • get_instances_async で非同期の WMI オブジェクトの取得の実現も可能に
  • get_instances_async はすべてのインスタンス取得が完了するまで、スクリプトを終了しないように at_exit で sleep する。

といった点を工夫したつもりである。

このライブラリを使うことで、

WMI::Win32_Process.get_instances_async do |process|
  puts caption
end

のような簡潔なコードで、現在実行中のプロセスの名称を非同期に取得できるようになる。

最初の目標から大きく外れ、IronRubyRuby の柔軟な機構をごりごりと使う実験みたいな例になってしまったが、define_method, instance_eval などを含むコードでもちゃんと動いてもらえて、非常に満足である。

なんとなく作ってしまったので、公開してみたが、今後の展開としては、

  • IronRuby ベースで PowerShell のラッパーとしても使えるライブラリの作成(この路線を発展)
  • IronRuby から、CRuby で動作する RubyCLR ベースに移植
  • IronRuby ではなく、JScript.NET に移植。(eval の連打での実装???)
  • 最初、本当に作りたかった WMI の JavaScript ラッパーの作成。

といった選択肢のどれかを進めていこうと思っている。

やってみて、一息ついてみると、JavaScriptJScript) で実現する場合での限界などについても理解が深まった。私がやりたいことは JScript.NET でプロパティの部分については実現可能そうだ。高速さは犠牲になるが、JScript.NET で動的なメソッド定義も実現可能だ。JScript.NET コンパイラは、.NET フレームワークがインストールされている環境であればインストールされているようなので、普通の XP パソコンであれば使えて、どこでも動くといいやすい。PowerShell についても JScript.NET であれば、自然に扱えそうである。そのため、JScript.NET への移植などを進めていくかもしれない。(実際、今後どうするかは私の気分で決まるわけだが)