IronRuby-PowerShell (1)
cuzic です。
ちょっと取り組んでみたら意外とあっさりとできたので、IronRuby による PowerShell のコマンドレットを操作できるようにするライブラリについて、書きます。
もうちょっと、頭の中を整理したいというか、練りたいので、 GitHub 等に公開は特にまだしていません。
たとえば、下記のスクリプトが動作します。
[1, 2, 3, 4].powershell <
これは、PowerShell で
$square = {foreach($x in $input) { $x * $x } 1, 2, 3, 4 | &$square
と同じ効果を持ちます。
iAsyncResult はあえて内部的に非同期的な呼び出しを使っているので必要になるおまじないです。内部での呼び出しを同期的にすれば解消するのですが、インタフェース的にどうするのかについて、考えているところです。
ほかに、WMI で Win32_Process を使って実現したような現在実行中のプロセスの一覧を出力する処理は、
powershell("Get-Process") do |iAsyncResult, output| output.each do |i| puts i end end
のように簡潔に書けます。
このライブラリを使えば、変数名の最初の $ や 関数呼び出しのときの & など目障りな記号を使わずに手馴れた Ruby スクリプトで、PowerShell に用意されているコマンドレットを数多く利用することができます。
現在の仕様としては、
- ブロック呼び出しで呼ばれた場市歩は、非同期処理、ブロックがあれば同期処理
- Object クラスを拡張して、powershell メソッドを実装
- System.Management.Automation.dll の位置はわりと決め打ち
というようなかんじになっています。
PowerShell の呼び出しに関する部分のコードについては、わずか50行ほどの記述で完結しておりますので、理解することは簡単でしょう。
このコードをベースに JScript.Net に移植することも考えたのですが、JScript.Net は Generics がサポートされていないとのことで、PowerShell との連携は不可能そうです。
# MSDN のあちこちで、「JScript では、ジェネリックな型およびメソッドは使用できません。」との記述が・・・。
smafile = ENV["PROGRAMFILES"] + %q(\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0\System.Management.Automation) require smafile SMA = System::Management::Automation class Object def powershell source powershell = SMA::PowerShell.create if block_given? then # ブロック引数があれば同期処理 input = nil case self when Array # IronRuby では Generics を of メソッドで実現 input = SMA::PSDataCollection.of(self.first.class).new each do |elem| input.Add elem end input.Complete else input = SMA::PSDataCollection.of(self.class).new # トップレベルメソッドとして呼ばれた場合 unless self.inspect == "main" && self.class == Object then input.Add self end input.Complete end output = SMA::PSDataCollection.of(System::Object).new completed = false asyncCallback = proc do |iAsyncResult| completed = true if iAsyncResult.IsCompleted yield iAsyncResult, output powershell.Dispose if completed end at_exit do sleep 1 until completed end powershell.AddScript source powershell.BeginInvoke input, output, SMA::PSInvocationSettings.new, asyncCallback, nil else # ブロック引数がなければ同期処理 begin powershell.add_script source if self.is_a? Array then return powershell.invoke(self) else return powershell.invoke([self]) end ensure powershell.Dispose end end end end def test_gps powershell("Get-Process") do |iAsyncResult, output| output.each do |i| puts i end end end def test_gps_html powershell("Get-Process | ConvertTo-Html") do |iAsyncResult, output| output.each do |i| puts i end end end def test_square [1, 2, 3, 4].powershell <