PowershellとUWSCの連携(その2)
PowershellからUWSCにコマンドを実行させる。
前回はIE経由な上に、PowershellとUWSCの二つのスクリプトが必要でした。
PowershellとUWSCの連携 - じゅんじゅんのきまぐれ
今回は、名前付きパイプを使用してIEを排除した上に、メンテするスクリプトを一つにしました。
、、、ファイルは二つ必要だけど、、、。(二つ目は勝手に作る)
スクリプト
.ps1ファイルとして保存してください。
基本、Powershellスクリプトです。
#=0 // PowershellとUWSCスクリプトを同居させるおまじない # = EVAL("UwscMain()") function TextBlock {} TextBlock _pre_psh function EndTextBlock {} function Fend {} function Result {} EndTextBlock Result = 0 Fend #=0 // UwscMainを書く TextBlock _uwsc_main_pre $uwsc = @' EndTextBlock // ここはUWSCの王国 FUNCTION UwscMain() DEF_DLL CloseHandle(dword): bool: kernel32.dll CONST INVALID_HANDLE_VALUE = $FFFFFFFF DEF_DLL WaitNamedPipeW(wstring,dword): bool: kernel32.dll DEF_DLL CreateFileW(wstring,dword,dword,dword,dword,dword,dword): dword: kernel32.dll CONST GENERIC_ALL = $10000000 CONST OPEN_EXISTING = 3 DEF_DLL ReadFile(dword,var wstring,dword,var dword,dword): bool: kernel32.dll DEF_DLL WriteFile(dword,wstring,dword,var dword,dword): bool: kernel32.dll CONST __pipe_name = "JunePshUwscPipe" CONST __pipe_full_name = "\\.\pipe\" + __pipe_name CONST __size = 1024 // パイプに接続する DIM __count = 0, __hPipe = INVALID_HANDLE_VALUE, __cmd = 30 WHILE !WaitNamedPipeW(__pipe_full_name, 0) AND __count < __cmd SLEEP(0.1) __count = __count + 1 WEND IFB __count < __cmd THEN __hPipe = CreateFileW(__pipe_full_name, GENERIC_ALL, 0, NULL, OPEN_EXISTING, 0, NULL) IFB __hPipe = INVALID_HANDLE_VALUE THEN PRINT "Err: CreateFile." ENDIF ELSE PRINT "Err: WaitNamedPipe." ENDIF RESULT = TRUE IFB __hPipe <> INVALID_HANDLE_VALUE THEN // パイプから読み込む WHILE TRUE __cmd = FORMAT(CHR(0), __size / 2) IF !ReadFile(__hPipe, __cmd, __size, __count, 0) THEN BREAK __cmd = COPY(__cmd, 1, __count / 2) IF __cmd = "exit" OR __cmd = "quit" THEN BREAK TRY __cmd = "" + EVAL(__cmd) EXCEPT __cmd = TRY_ERRMSG + "<#CR>" + __cmd ENDTRY IF LENGTH(__cmd) = 0 THEN __cmd = CHR(0) IF !WriteFile(__hPipe, __cmd, LENGTH(__cmd) * 2, __count, 0) THEN BREAK WEND // パイプを閉じる CloseHandle(__hPipe) ENDIF FEND // 適当なTextBlock内はPowershellの王国 TextBlock _uwsc_main_past '@ $uwsc = New-Object PSObject $uwsc | Add-Member NoteProperty 'path' 'D:\yama\tools\cmd\bin\uwsc\uwsc.exe' # 自スクリプト名+'.uws'ファイルの同一性チェック。だめなら再生成する $uwsc | Add-Member NoteProperty 'file' ($MyInvocation.MyCommand.Path + '.uws') if(!(Test-Path -Path $uwsc.file) -or $(Compare-Object -ReferenceObject $(Get-Content $MyInvocation.MyCommand.Path) -DifferenceObject $(Get-Content $uwsc.file) | Measure-Object).Count -ne 0) { if(Test-Path -Path $uwsc.file) { Remove-Item $uwsc.file } Copy-Item -Path $MyInvocation.MyCommand.Path -Destination $uwsc.file } $uwsc | Add-Member ScriptMethod 'Create' { # サーバーパイプを作成し、uwscを起動する $this | Add-Member NoteProperty -Force 'pipe' (New-Object System.IO.Pipes.NamedPipeServerStream 'JunePshUwscPipe', InOut) $this | Add-Member NoteProperty -Force 'buf' (New-Object byte[] 1024) $this | Add-Member NoteProperty -Force 'bin' (Start-Process -PassThru $uwsc.path $uwsc.file) $this.pipe.WaitForConnection() } $uwsc.Create() $uwsc | Add-Member ScriptMethod 'Dispose' { # サーバーパイプを終了する $this.pipe.Close() $this.bin.Kill() } $uwsc | Add-Member ScriptMethod 'In' { while($true) { Write-Host -NoNewline 'UWSC> ' $cmd = [Console]::in.ReadLine() if($cmd -match '^(exit|quit)') { break; } $cmd = $this.Eval($cmd) Write-Host $cmd } } $uwsc | Add-Member ScriptMethod 'Eval' { if($args.length -gt 0) { $wb = [System.Text.Encoding]::Unicode.GetBytes($args[0]) if($wb.Length) { $this.pipe.Write($wb, 0, $wb.Length) $len = $this.pipe.Read($this.buf, 0, $this.buf.Length) [System.Text.Encoding]::Unicode.GetString($this.buf, 0, $len) } } } EndTextBlock
使い方
上記スクリプトをPowershellから「. (スクリプトファイル)」として実行します。
UWSCのプロセスが起動し、名前付きパイプでPowershellと接続、$uwscオブジェクトが生成されます。
$uwsc.EvalでEVALを実行するか、$uwsc.In()でUWSCモードに入り、インタラクティブに使用します。
終了は、$uwsc.Dispose()。
Dispose後再開したい場合は、$uwsc.Create()で再度使用可能です。
動作
- 実行すると、「(スクリプトファイル).uws」ファイルの中身を確認し、自身と同じでない場合はコピーします
- $uwscオブジェクトに、名前付きパイプやuwscプロセスを作成します
- 「$uwsc.In()」すると、プロンプトが「UWSC> 」となり、UWSCでEVALする文字列を受け付けます
- UWSCモードは「exit」か「quit」で抜けます
- $uwsc.Evalは引数一つをUWSCでEVALし、結果を返します
UWSC主導と違って、Powershellが終わると、Pipeが閉じてUWSCは終了します。
同一内容のトリック
残念ながら、Powershellは「.ps1」、UWSCは「.uws」と拡張子が固定のため、同一ファイルにはできませんでした。
使いどころはないけど、同一内容で成立させているおまじないを解説します。
ポイントは、最初の部分。
#=0 // PowershellとUWSCスクリプトを同居させるおまじない # = EVAL("UwscMain()") function TextBlock {} TextBlock _pre_psh function EndTextBlock {} function Fend {} function Result {} EndTextBlock Result = 0 Fend
最初の二行
Powershellからすると、「#」で始まるのはコメントです。
UWSCは「#」という変数が取れるため、どちらも代入文です。
一行目はどちらにしてもコメントです。
二行目は、UWSCはUwscMain関数を実行します。
三行目「function TextBlock {}」
Powershellは、TextBlockという関数の宣言です。(中身空なので実行しても何もしない)
UWSCは、TextBlockという関数の開始行です。
四行目「TextBlock _pre_psh」
Powershellは、「_pre_psh」を引数として先ほど定義したTextBlock関数を実行します。(動作なし)
UWSCは、_pre_pshという名前のテキストブロックの開始行です。
五〜七行目
Powershellは、EndTextBlock/Fend/Resultという関数の宣言です。
UWSCは、テキストブロックの中。
八行目「EndTextBlock」
Powershellは、EndTextBlock関数の実行。
UWSCは、テキストブロックの終わり
九行目「Result = 0」
Powershellは、「=」と「0」の二つの引数でResult関数を実行。
UWSCは、戻り値の定義
十行目「Fend」
Powershellは、Fend関数の実行。
UWSCは、TextBlock関数の終了行。
といった感じで、お互いがそれぞれに解釈して処理します。
UWSCが「#」という変数、「function TextBlock {}」という関数開始行を許容することが大きいです。
ここまでくると、「TextBlock」は、Powershellには動作なしの関数、UWSCでは複数行文字列定数になるため、一方には文字列定数で、もう一方には意味あるスクリプト、という状況が作れるようになります。
具体的には、
TextBlock _uwsc_main_pre $uwsc = @' EndTextBlock // UWSCスクリプトはここ TextBlock _uwsc_main_past '@ # Powershellスクリプトはここ EndTextBlock
上のスクリプトは、Powershellは、TextBlock関数の実行と$uwsc変数の定義とEndTextBlock関数の実行。
($uwscに「EndTextBlock〜TextBlock _uwsc_main_past」という文字列が入る)
UWSCは、二つのテキストブロックとその間のスクリプト、ということになります。
どっちかの拡張子が固定でなければ、ファイル一つでできたのだけど、、、残念!