UWSCのpowershell関数をスピードアップする(その2)
前回は、IE経由でした。
UWSCのpowershell関数をスピードアップする - じゅんじゅんのきまぐれ
あれはあれで、オブジェクトを渡せる可能性を秘めているわけですが、IEを起動したりして、起動が遅い。
UWSC公式掲示板で、pshモジュールを利用した回答を書いた時、起動の遅さにうんざりしました。
ということで、名前付きパイプを使って、早くしてみました!
スクリプト
ちなみに、一番苦戦したのは、Powershellでブロックしない名前付きパイプの作成。
ブロックすると、いってしまうのです、UWSCが。
psh.uws
OPTION EXPLICIT IFB GET_UWSC_NAME = "psh.uws" THEN psh.Create() DIM code = "" WHILE code <> EMPTY code = psh.Run(code) IF LENGTH(code) THEN PRINT code code = INPUT("?") WEND psh.Dispose() ENDIF MODULE psh CONST _pipe_name = "JuneUwscPsh" CONST _pipe_full_name = "\\.\pipe\" + _pipe_name CONST _size = 1024 DIM _hPipe 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 byte[],dword,var dword,dword): bool: kernel32.dll DEF_DLL WriteFile(dword,byte[],dword,var dword,dword): bool: kernel32.dll TEXTBLOCK _psh_code if (-not ("CallbackEventBridge" -as [type])) { Add-Type @" using System; public sealed class CallbackEventBridge { public event AsyncCallback CallbackComplete = delegate {}; private void CallbackInternal(IAsyncResult result) { CallbackComplete(result); } public AsyncCallback Callback { get { return new AsyncCallback(CallbackInternal); } } } "@} $po = New-Object PSObject $po | Add-Member NoteProperty "buf" (New-Object byte[] 1024) $po | Add-Member NoteProperty "pipe" (New-Object System.IO.Pipes.NamedPipeServerStream "$$name$$", InOut, 1, Message, Asynchronous, 1024, 1024) $po | Add-Member NoteProperty "wb" (New-Object CallbackEventBridge) $po | Add-Member NoteProperty "rb" (New-Object CallbackEventBridge) $callback = { param($asyncResult) # 本当は書き込んだサイズをチェックすべきかも $asyncResult.AsyncState.EndWrite($asyncResult) } Register-ObjectEvent -InputObject $po.wb -EventName CallbackComplete -action $callback > $null $callback = { param($asyncResult) $asyncResult.AsyncState.pipe.EndWaitForConnection($asyncResult) } Register-ObjectEvent -InputObject $po.rb -EventName CallbackComplete -action $callback > $null $ar = $po.pipe.BeginWaitForConnection($po.rb.Callback, $po) while(!$ar.IsCompleted) { Start-Sleep -Milliseconds 100 } $callback = { param($asyncResult) $po = $asyncResult.AsyncState $len = $po.pipe.EndRead($asyncResult) $cmd = [System.Text.Encoding]::Unicode.GetString($po.buf, 0, $len) if($cmd -match '^exit|^quit') { $po.pipe.Close() } else { try { $cmd = "" + (iex $cmd) } catch { $cmd = "Invoke error. " + $cmd + "`n" + ($Error[0] | Out-String) } $cb = [System.Text.Encoding]::Unicode.GetBytes($cmd) $wb = New-Object byte[] ($cb.Length + 4) [Array]::Copy([BitConverter]::GetBytes($cb.Length), 0, $wb, 0, 4) [Array]::Copy($cb, 0, $wb, 4, $cb.Length) $ar = $po.pipe.BeginWrite($wb, 0, $wb.Length, $po.wb.Callback, $po) while(!$ar.IsCompleted -and $po.pipe.IsConnected) { Start-Sleep -Milliseconds 100 } } } Register-ObjectEvent -InputObject $po.rb -EventName CallbackComplete -action $callback > $null while($po.pipe.IsConnected) { $ar = $po.pipe.BeginRead($po.buf, 0, $po.buf.Length, $po.rb.Callback, $po) while(!$ar.IsCompleted) { Start-Sleep -Milliseconds 100 } } $po.pipe.Close() ENDTEXTBLOCK FUNCTION Create() POWERSHELL(REPLACE(_psh_code, "$$name$$", _pipe_name), TRUE) DIM count = 0, wait = 30 WHILE !WaitNamedPipeW(_pipe_full_name, 0) AND count < wait SLEEP(0.1) count = count + 1 WEND RESULT = FALSE IFB count < wait THEN _hPipe = CreateFileW(_pipe_full_name, GENERIC_ALL, 0, NULL, OPEN_EXISTING, 0, NULL) IFB _hPipe = INVALID_HANDLE_VALUE THEN PRINT "Err: CreateFile." ELSE RESULT = TRUE ENDIF ELSE PRINT "Err: WaitNamedPipe." ENDIF FEND PROCEDURE Dispose() WriteString("exit") IFB _hPipe <> 0 AND !CloseHandle(_hPipe) THEN PRINT "Err: CloseHandle." ENDIF FEND FUNCTION Run(code) RESULT = EMPTY IF WriteString(code) THEN RESULT = ReadString() FEND FUNCTION ReadString() DIM buf[_size], ret, i, rest = 0 RESULT = ReadFile(_hPipe, buf, 4, ret, NULL) AND ret = 4 IFB RESULT THEN rest = ((buf[3] * 256 + buf[2]) * 256 + buf[1]) * 256 + buf[0] RESULT = "" ELSE PRINT "Err: ReadFile size." ENDIF WHILE rest > 0 IFB ReadFile(_hPipe, buf, _size, ret, NULL) THEN FOR i = 0 TO INT(ret / 2) - 1 RESULT = RESULT + CHR(buf[i*2] + buf[i*2+1] * 256) NEXT rest = rest - ret ELSE PRINT "Err: ReadFile." BREAK ENDIF WEND FEND FUNCTION WriteString(data) DIM len = LENGTH(data) * 2, buf[len], i, t RESULT = len IFB RESULT THEN FOR i = 0 TO len / 2 - 1 t = ASC(COPY(data, i + 1, 1)) buf[i * 2] = t MOD 256 buf[i * 2 + 1] = INT(t / 256) NEXT // 本来なら少量書込みでの続行処理が必要 RESULT = (WriteFile(_hPipe, buf, len, t, NULL) > 0 AND t = len) IF !RESULT THEN PRINT "Err: WriteFile." ENDIF FEND ENDMODULE
使い方は前回と同じ。
本モジュールをCALLする。
最初に「psh.Create()」
最後に「psh.Dispose()」
今までPowershell関数を呼んでいたところを、psh.Run関数に置き換える。
psh.Disposeしないと不幸になるのは、前回と同じ。
前回は野良Powershellと野良IEができてましたが、今回は野良Powershellだけで済みますけど。
これさー、成果はすんなり動くけど、ここまでくるの大変なんだぜ。
Powershell内もUWSC側もAPI等でブロックしたら、アウト。
そこに注意しながら、いろんなところにWhileループが入っているのです。
ちなみに、UWSC->Powershellのコマンドは、500文字程度までの想定。
戻りは何文字でも良いように、最初にサイズを送るようにしている。
コマンドももっとたくさんにしたい場合は、最初にサイズを送るようにすべき。