【WSHからPowerShell双方向連携】WScript.Shell.Execを用いたPowerShellコマンドレットのリアルタイム出力取得と制御
レガシーシステムの保全とモダナイゼーションの境界線で、我々エンジニアは常にジレンマに直面している。
「VBScriptの堅牢な基盤とゼロフットプリントの稼働性を維持したい。しかし、現代のWindows管理に必要なPowerShellの豊富なコマンドレット(Get-CimInstance, Invoke-RestMethodなど)や最新のWindows APIへのアクセスを捨て去ることはできない」
`WScript.Shell`の`Run`メソッドを使った同期・非同期実行では、標準出力(Stdout)をファイル経由でダンプするか、一切キャプチャせずに闇に葬るかの二者択一だった。これでは「プロセスの生存確認」すらままならない。
今回は、`WScript.Shell.Exec`メソッドが内包するストリーム機構を極限まで引き出し、VBScriptとPowerShellの間でリアルタイムな双方向ストリーミング通信を実現するアーキテクチャを提示する。
—
1. アーキテクチャの核心:`Exec` と `Run` の決定的な違い
多くの初学者は、`WScript.Shell`のプロセス起動メソッドとして`Run`を好む。しかし、システム自動化のアーキテクトにとって、`Run`は「過去の遺物」に過ぎない。
| 特性 | `WScript.Shell.Run` | `WScript.Shell.Exec` |
| :— | :— | :— |
| 戻り値 | 終了コード (Integer) | WshProcess オブジェクト |
| 標準出力の取得 | 不可(ファイルリダイレクト必須) | `StdOut` ストリームでリアルタイム取得可能 |
| 標準入力への書き込み | 不可 | `StdIn` ストリームで対話的制御が可能 |
| プロセス制御 | 終了待ち (`bWaitOnReturn`) のみ | プロセスID(`ProcessID`)、終了状態(`Status`)の監視が可能 |
`Exec`メソッドが返すのは、実行されたプロセスの実体を握る`WshProcess`オブジェクトである。これを利用することで、PowerShell側で生成された出力をバッファリングの呪縛から解放し、1行ごとにVBScript側へ流し込むことが可能になる。
—
2. 実装コード:リアルタイム双方向ストリーミングの全貌
以下のコードは、VBScriptからPowerShellを呼び出し、無限ループまたは特定のバッチ処理を実行させつつ、その進行状況(標準出力)をリアルタイムに捕捉、さらに必要に応じてPowerShell側へ終了シグナルや入力を送り込む実用的なスクリプトである。
‘ ==============================================================================
‘ Script Name: VBS_PowerShell_Bridge.vbs
‘ Description: WScript.Shell.ExecによるPowerShell双方向連携エンジン
‘ Architecture: Chief Architect’s Technical Series
‘ ==============================================================================
Option Explicit
Sub Main()
Dim wsh, execObj, psCommand, line
Dim exitCode, startTime
startTime = Timer
Set wsh = CreateObject(“WScript.Shell”)
‘ 実行するPowerShellコマンド(例:WMI/CIMを使った最新のプロセス列挙とCPU負荷の高いプロセスの抽出)
‘ ※ 実際には複雑なJSONペイロードの処理やREST API呼び出しなどをここに記述する
psCommand = “powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command ” & _
“””Get-CimInstance Win32_Process | Select-Object ProcessId, Name, WorkingSetSize | ForEach-Object { ” & _
” [PSCustomObject]@{ ” & _
” PID = $_.ProcessId; ” & _
” Name = $_.Name; ” & _
” MemoryMB = [Math]::Round($_.WorkingSetSize / 1MB, 2) ” & _
” } ” & _
“} | ConvertTo-Json -Compress”””
WScript.Echo “[VBS] PowerShellプロセスを起動します…”
‘ Execメソッドによるプロセスの非同期実行とストリームの確立
Set execObj = wsh.Exec(psCommand)
‘ 【極限の知見】
‘ リアルタイムにStdOutを監視する。
‘ AtEndOfStreamプロパティを評価する際、PowerShell側がまだ出力中であればブロックされるが、
‘ プロセスが生存している限り、生成された行単位で即座に取得できる。
Do While execObj.Status = 0 ‘ 0: WshRunning
If Not execObj.StdOut.AtEndOfStream Then
line = execObj.StdOut.ReadLine()
‘ ここで取得した行データをパース、またはログ出力する
WScript.Echo “[PS Output]: ” & line
End If
‘ CPUのスパイクを防ぎつつ、イベントループを回すための微小ウェイト
WScript.Sleep 50
Loop
‘ プロセス終了後の残余ストリームの回収(取りこぼし防止)
Do Until execObj.StdOut.AtEndOfStream
line = execObj.StdOut.ReadLine()
WScript.Echo “[PS Output (Flush)]: ” & line
Loop
‘ エラー出力(StdErr)の捕捉
If Not execObj.StdErr.AtEndOfStream Then
Dim errOutput
errOutput = execObj.StdErr.ReadAll()
If Len(errOutput) > 0 Then
WScript.Echo “[CRITICAL ERROR]: ” & errOutput
End If
End If
exitCode = execObj.ExitCode
WScript.Echo “[VBS] 処理完了. ExitCode: ” & exitCode & ” (経過時間: ” & FormatNumber(Timer – startTime, 2) & “秒)”
‘ オブジェクトの明示的解放(メモリ管理の鉄則)
Set execObj = Nothing
Set wsh = Nothing
End Sub
‘ エントリポイントの呼び出し
Main()
—
3. シニアエンジニアが押さえるべき「メモリ最適化とリソース管理」の極意
VBScriptの寿命は短い。しかし、COMコンポーネントを多用するWSH環境において、「ガベージコレクションへの過信」はメモリリークを引き起こす致命傷となる。
A. COMオブジェクトの明示的破棄 (`Set obj = Nothing`)
`WScript.Shell` やそこから派生する `WshProcess` (`execObj`) は、内部でWindowsのハンドルやCOM参照カウンタを保持している。特に `Exec` は子プロセスとの間でパイプ(匿名パイプ)を生成するため、スクリプト終了時にこれを明示的に解放しないと、ハンドルリークやゾンビプロセスの温床となる。
コードの最後で必ず `Set execObj = Nothing` を行うこと。
B. バッファブロックの回避と `Status` 監視
`Exec.StdOut.ReadAll()` をいきなり呼び出すと、PowerShell側がプロセスを終了してストリームを閉じるまでVBScript側が完全にフリーズする(ブロッキングI/Oの罠)。
上記のコードのように `execObj.Status = 0` のループの中で `ReadLine()` を回す、あるいは `AtEndOfStream` をポーリングするアプローチこそが、GUIや他の処理をブロックしないための唯一の解法である。
C. 文字コード(UTF-8 / Shift-JIS)の罠
PowerShell 5.1以前のデフォルト出力は環境依存(日本語版WindowsではShift-JIS/CP932)であることが多いが、PowerShell Core (v6以降) や現代のコマンドレットはUTF-8(BOMなし)を出力する。
VBScriptの標準ストリームはデフォルトでANSI(Shift-JIS)として解釈するため、JSONや特殊文字を含むオブジェクトをやり取りする際、文字化けが発生する。
これを回避するため、PowerShell側の呼び出し部で出力エンコーディングを明示的に制御するか、VBScript側でバイナリとして受け取る高度なラッパー設計が必要になる場合がある。基本的には `-NoProfile` を付与し、予期せぬプロファイルスクリプトによる出力汚染を防ぐことが鉄則である。
—
総括
VBScriptはもはや「古い言語」ではない。それは、Windows OSの根底に組み込まれた「極限まで無駄を削ぎ落とした軽量ランタイム」である。
PowerShellという現代の強力なエンジンを、このVBScriptという軽量な外殻(シェル)から `WScript.Shell.Exec` を通じて自在にコントロールする。この双方向連携の技術を習得した者こそが、レガシーとモダンの壁を軽々と超え、堅牢かつモダンなシステム基盤を構築できる真のプロフェッショナルである。
