【標準入力のノンブロッキング読み取り】CScript実行時のキー入力待ちによる処理中断を起こさない対話制御
レガシーシステムの自動化、あるいはインフラストラクチャの常時稼働バッチにおいて、VBScript(WSH)は今なおWindows環境の裏側で静かに、しかし強靭に稼働し続けている。
だが、VBScriptによるCUI(Command Line Interface)ツール設計において、長年エンジニアたちの頭を悩ませてきた致命的なボトルネックが存在する。それが 「標準入力(StdIn)のブロッキング挙動」 だ。
通常、`WScript.StdIn.ReadLine` や `Read` を実行すると、ユーザーがEnterキーを押すかストリームが終端に達するまで、スクリプトのスレッドは完全に停止(ブロック)される。バックグラウンドで重いファイル処理やAPIポーリングを行っている最中にユーザーからの割り込み(「中断:Qキー」「状態確認:Sキー」など)を受け付けたい場合、このブロッキング仕様は致命傷となる。
本稿では、VBScriptの標準機能およびWindows APIの緻密な連携により、処理を一切止めずにキー入力をポーリング検知する「ノンブロッキング対話制御」 の極限の知見を公開する。
—
1. なぜ標準入力のノンブロッキングは困難なのか?
VBScriptのランタイム(`wscript.exe` または `cscript.exe`)は、シングルスレッドで動作する。
さらに、`WScript.StdIn` は同期型の文字ストリームであり、内部バッファにデータが存在しない状態で読み取りを試みると、OSレベルでスレッドがサスペンドされる。
これを解決するためによくある誤ったアプローチとして、`WScript.Shell` の `Popup` メソッドや `SendKeys` を駆使するハックが挙げられるが、これらはGUIポップアップを強制したり、フォーカスを奪ったりするため、純粋なCUIバッチのバックグラウンド制御には到底使えない。
真の解決策は、「WScript.StdInのラッパーとしての限界を見切り、外部のCOMオブジェクトや非同期I/Oの概念をVBScriptに持ち込むこと」、あるいは 「Windows APIのコンソール制御関数を直接たたくこと」 にある。
—
2. アプローチの比較:WSH標準 vs COM/API連携
| 方式 | 実装難易度 | パフォーマンス | 制御の精度 | 備考 |
| :— | :— | :— | :— | :— |
| ① `StdIn.AtEndOfStream` 判定 | 极めて低 | 悪(CPU高騰) | 低 | パイプ入力時のみ有効。キーボード入力には無力 |
| ② PowerShellプロキシ連携 | 中 | 許容範囲 | 高 | VBScriptからPowerShellの`.KeyAvailable`を叩く |
| ③ WbemScripting / その他COM | 高 | 中 | 中 | 環境依存が高く非推奨 |
今回は、レガシー環境(Windows Server 2012R2以降、Windows 10/11)で追加のサードパーティ製DLLを一切インストールせず、確実に動作する「PowerShellをバックグラウンドで介在させたハイブリッド・ノンブロッキング制御」および、純粋なVBScriptの限界を突破する設計思想を解説する。
—
3. 実装コード:ノンブロッキング・キーインターセプター
以下のコードは、CUI上で重いバックグラウンド処理(ループ)を回しつつ、ユーザーが `Q` キーを押した瞬間にそれを検知して安全にループを脱出する、実用的なVBScriptの完全版である。
‘ ==============================================================================
‘ File: NonBlockingControl.vbs
‘ Description: CScript実行時におけるノンブロッキング・キー入力検知サンプル
‘ Architect Note: スレッドブロッキングを回避するため、WMI/PowerShellストリーム
を巧妙に抽象化し、CPU負荷を抑制しながら割り込みを検知する。
‘ ==============================================================================
Option Explicit
Sub Main()
‘ CScript環境での実行強制チェック
If InStr(LCase(WScript.FullName), “cscript.exe”) = 0 Then
MsgBox “このスクリプトは CScript.exe で実行してください。” & vbCrLf & _
“例: cscript NonBlockingControl.vbs”, vbCritical, “実行環境エラー”
WScript.Quit 1
End If
WScript.Echo “=== ノンブロッキング対話制御テスト ===”
WScript.Echo “処理を実行中… [Q] キーを押すと安全に中断します。”
WScript.Echo “————————————————–”
Dim shell, execObj, counter, isRunning
Set shell = CreateObject(“WScript.Shell”)
‘ PowerShellのコンソールAPI (.KeyAvailable) を利用した非同期キーチェッカーを起動
‘ ※標準入力を占有せず、キーボードバッファを直接覗き見るためのトリック
Dim psCommand
psCommand = “powershell -NoProfile -Command “”while(1) { if ([Console]::KeyAvailable) { $k = [Console]::ReadKey($true); [int]$k.Key; break } Start-Sleep -Milliseconds 100 }”””
Set execObj = shell.Exec(psCommand)
counter = 0
isRunning = True
Do While isRunning
‘ ——————————————————————
DoinBackgroundWork()
‘ ——————————————————————
counter = counter + 1
WScript.StdOut.Write vbCr & “経過ループ回数: ” & counter & ” 処理継続中…”
‘ PowerShellプロセス側からキーコードが出力されたか(ノンブロッキングで確認)
If Not execObj.StdOut.AtEndOfStream Then
Dim keyInput
keyInput = Trim(execObj.StdOut.ReadLine())
‘ ‘Q’ キーの仮想キーコードは 81
If keyInput = “81” Then
WScript.StdOut.WriteLine vbCrLf & “[!] ユーザーによる中断シグナルを検知しました。”
isRunning = False
End If
End If
‘ CPUのコアを焼き尽くさないためのスリープ(約50ms待機)
WScript.Sleep 50
Loop
‘ プロセスのクリーンアップ(ゾンビプロセスを残さない)
On Error Resume Next
If execObj.Status = 0 Then execObj.Terminate
On Error GoTo 0
‘ オブジェクトの明示的解放(メモリ管理の鉄則)
Set execObj = Nothing
Set shell = Nothing
WScript.Echo “スクリプトを正常に終了しました。”
End Sub
‘ バックグラウンドで行われる重い処理の模擬
Sub DoinBackgroundWork()
‘ ここに実際のAPI通信やファイルI/O、DB処理が入る
WScript.Sleep 100
End Sub
‘ エントリポイント呼び出し
Main()
—
4. チーフアーキテクトが解説するコードの急所と極意
① CScript強制の担保
GUI環境でダブルクリック等により `wscript.exe` で実行された場合、`WScript.StdOut` やパイプ制御は致命的なエラーを引き起こす。冒頭の `InStr` による実行バイナリの検証は、エンタープライズスクリプトにおける必須の防御的プログラミングである。
② PowerShellプロキシによるキーバッファの盗み見
VBScript単体ではコンソールのキーボードバッファをノンブロッキングでポーリングするネイティブなAPIを持たない。そこで、極めて軽量なPowerShellの `[Console]::KeyAvailable` と `[Console]::ReadKey($true)` をバックグラウンド実行(`shell.Exec`)させ、その標準出力ストリームの状態を `execObj.StdOut.AtEndOfStream` で監視するというアーキテクチャを採用している。
これにより、VBScript側のメインスレッドを1ミリ秒たりともブロックさせることなく、キー入力を非同期にキャッチすることが可能になる。
③ ゾンビプロセスの根絶とメモリ管理
`WScript.Shell.Exec` は外部プロセスを生成するため、スクリプト異常終了時にバックグラウンドに残骸(ゾンビプロセス)を残しやすい。ループ脱出時、あるいは予期せぬエラー時に `.Terminate` を安全に呼び出し、さらに `Set execObj = Nothing` によってCOM参照カウンタを適切にデクリメントする。
この「リソースのライフサイクル管理の徹底」こそが、数ヶ月間連続稼働するバッチサーバーにおいてメモリリークを防ぐ唯一の道である。
④ CPU使用率の適正化(ビジーウェイトの回避)
ノンブロッキング処理を実装する際、`Do While` ループの中にウェイト(`WScript.Sleep`)を入れ忘れると、CPUコアの1つが100%に張り付くという悲惨なパフォーマンス劣化を引き起こす。`WScript.Sleep 50` を挟むことで、人間の反応速度(数10ms〜数100ms)を損なうことなく、システムリソースを極限まで保護している。
—
5. まとめ
VBScriptは「古い言語」として片付けられがちだが、そのランタイムの特性とOSのプリミティブ(PowerShellやWindows API)を正しく組み合わせることで、現代の過酷なインフラ自動化の現場でも十分すぎるほどの戦闘力を発揮する。
今回解説したノンブロッキング制御の知見をあなたのシステムに組み込むことで、「途中で止まらない、しかしユーザーの意思には即座に応答する」、極めて洗練されたCLI自動化ツールを実現できるはずだ。
レガシーの限界を嘆く前に、アーキテクチャの工夫で限界を突破せよ。
