【実務・中級編】【無応答プロセス自己復旧】監視用子スクリプト(ウォッチドッグ)を並列起動して親スクリプトのハングアップを解消する構造 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【無応答プロセス自己復旧】監視用子スクリプト(ウォッチドッグ)を並列起動して親スクリプトのハングアップを解消する構造

開発現場でVBScriptを使った自動化バッチを組んでいると、避けて通れない悪夢がある。それが「COMコンポーネントの呼び出し端での突然のフリーズ(無応答状態)」だ。

Excel操作、IE/EdgeのDOM操作、重いSQL Serverへのクエリ、あるいはネットワーク越しのファイル共有アクセス。これらは一見正常に動いているように見えても、突然APIの内部でスレッドがデッドロックを起こしたり、タイムアウトの概念がないまま無限待機に入ったりすることがある。

WScript宿主(cscript.exe / wscript.exe)は、単一スレッドの脆弱な世界だ。プロセスがハングアップすれば、タスクマネージャーから手動でkillする以外に打つ手はない。夜間無人で稼働するはずのRPAや業務自動化バッチが、たった1つのハングアップで翌朝まで全停止している――そんな絶望を味わった開発者も少なくないはずだ。

今回は、このVBScriptの限界をアーキテクチャの力でねじ伏せる。「ウォッチドッグ(監視)パターン」を用いた、デュアルプロセスによる完全な自己復旧システムの設計と実装を伝授しよう。

1. なぜ「単一スクリプト内」の対策では無意味なのか?

よくある誤ったアプローチとして、「VBScriptの `On Error Resume Next` やタイマー処理で何とかしよう」とするケースがある。

結論から言えば、プロセスがハングアップ(無応答)した瞬間、VBScriptのインタプリタ自体が停止する。 コード上のいかなるエラーハンドリングも、タイマー割り込みも、ハングしたプロセスの中では一切動作しない。

だからこそ、「監視する側(ウォッチドッグ)」と「実行する側(メイン)」を完全に独立した別プロセスとして起動し、プロセス間の死活監視を行う必要がある。これが、プロの世界におけるフォールトトレラント(耐障害性)の基本設計だ。

2. デュアルプロセス・ウォッチドッグのアーキテクチャ

今回構築するシステムの全体像はこうだ。

1. メインスクリプト(親)の起動: 自動化の本処理を実行する。
2. ウォッチドッグ(子)の起動: メインプロセスが自ら、監視用の別VBScriptプロセスをバックグラウンドで生み出す。
3. ハートビート(生存確認): メインプロセスは生存の証として、定期的にテンポラリフォルダへ「タイムスタンプファイル(心拍ファイル)」を上書き更新する。
4. 監視ループ: ウォッチドッグは常に監視し、「設定された時間(例: 60秒)」を超えてもタイムスタンプが更新されない場合、メインプロセスが死んでいると判定する。
5. 強制リカバリ: ウォッチドッグはハングアップしたメインプロセスを強制終了(`taskkill`)し、自動的にメインプロセスを再起動する。

この仕組みにより、どれほど深いCOMの闇でフリーズしようとも、外側から確実に検知して再始動させることが可能になる。

3. プロダクションコード実装

現場でそのままコピー&ペーストして、パスや処理部分を書き換えるだけで使える実用コードを提示する。

ファイル構成は以下の2つとする。

  • `main_process.vbs` (メイン業務スクリプト)
  • `watchdog.vbs` (監視用子スクリプト)

① メインスクリプト: `main_process.vbs`

‘ ==============================================================================
‘ 業務メインスクリプト (main_process.vbs)
‘ ==============================================================================
Option Explicit

Const WORK_DIR = “C:\Automation\Temp\”
Const HEARTBEAT_FILE = “heartbeat.txt”
Const CHECK_INTERVAL_SEC = 10 ‘ 心拍を更新する間隔(秒)
Const TOTAL_RUNTIME_SEC = 3600 ‘ テスト用の全体実行時間(例: 1時間)

Dim objFSO, wShell
Set objFSO = CreateObject(“Scripting.FileSystemObject”)
Set wShell = CreateObject(“WScript.Shell”)

‘ ワーキングディレクトリの確保
If Not objFSO.FolderExists(WORK_DIR) Then
objFSO.CreateFolder(WORK_DIR)
End If

‘ 1. ウォッチドッグ(監視プロセス)をバックグラウンドで起動
‘ 引数に自身のプロセスID(PID)を渡すことで、ウォッチドッグが自分を正確に特定できるようにする
Dim currentPID
currentPID = GetCurrentProcessId() ‘ ※VBS単体ではPID取得が難しいためWMI経由、または環境変数を利用
‘ 実務ではWScript.ScriptName等をキーにするか、下部の起動スクリプト経由でPIDを渡す設計が堅牢です。
‘ 今回はシンプルに、スクリプト名と心拍ファイルを用いたロジックで構築します。

WScript.Echo “メインプロセス起動. PID関連の初期化完了.”

‘ 2. メインの業務ループ(ここでハングアップが発生する想定)
Dim startTime, lastBeatTime
startTime = Timer
lastBeatTime = 0

Do While (Timer – startTime) < TOTAL_RUNTIME_SEC ' --- 【ここに実際の業務ロジックを記述】 --- ' 例: 重いExcel処理、API通信など ' ------------------------------------------ ' 一定間隔でハートビート(生存確認ファイル)を更新 If (Timer - lastBeatTime) >= CHECK_INTERVAL_SEC Then
Call UpdateHeartbeat(objFSO)
lastBeatTime = Timer
WScript.Echo “Heartbeat updated at ” & Now()
End If

‘ CPUを圧迫しないためのスリープ
WScript.Sleep 1000

‘ 【デバッグ用】もし特定の条件でワザとハングさせたい場合はここで無限ループ等に落とす
‘ If (Timer – startTime) > 30 Then Do: Loop End If

Loop

‘ 正常終了時は心拍ファイルを削除してクリーンアップ
Call Cleanup(objFSO)

Sub UpdateHeartbeat(fso)
On Error Resume Next
Dim ts
Set ts = fso.CreateTextFile(WORK_DIR & HEARTBEAT_FILE, True)
ts.WriteLine Now()
ts.Close
Set ts = Nothing
On Error GoTo 0
End Sub

Sub Cleanup(fso)
On Error Resume Next
If fso.FileExists(WORK_DIR & HEARTBEAT_FILE) Then
fso.DeleteFile WORK_DIR & HEARTBEAT_FILE, True
End If
On Error GoTo 0
End Sub

② 監視用ウォッチドッグ・スクリプト: `watchdog.vbs`

‘ ==============================================================================
‘ 監視用ウォッチドッグ・スクリプト (watchdog.vbs)
‘ ==============================================================================
Option Explicit

Const WORK_DIR = “C:\Automation\Temp\”
Const HEARTBEAT_FILE = “heartbeat.txt”
Const TARGET_SCRIPT_NAME = “main_process.vbs”
Const ALLOWABLE_SILENCE_SEC = 30 ‘ この秒数以上ハートビートが途絶えたら「ハング」とみなす

Dim objFSO, wShell
Set objFSO = CreateObject(“Scripting.FileSystemObject”)
Set wShell = CreateObject(“WScript.Shell”)

WScript.Echo “ウォッチドッグ監視開始…”

Do
WScript.Sleep 5000 ‘ 5秒おめに死活チェック

Dim heartbeatPath
heartbeatPath = WORK_DIR & HEARTBEAT_FILE

If objFSO.FileExists(heartbeatPath) Then
Dim fObj, lastWriteTime, diffSec
Set fObj = objFSO.GetFile(heartbeatPath)
lastWriteTime = fObj.DateLastModified
Set fObj = Nothing

‘ 最終更新からの経過秒数を計算
diffSec = DateDiff(“s”, lastWriteTime, Now())

‘ 許容時間を超過している場合 = メインプロセスがフリーズしていると判定
If diffSec > ALLOWABLE_SILENCE_SEC Then
WScript.Echo “[警告] メインプロセスの無応答を検知!経過秒数: ” & diffSec & “秒”

‘ 1. 暴走・ハングアップしているプロセスを強制終了 (cscript.exe / wscript.exe の該当スクリプト)
Call KillTargetProcess(TARGET_SCRIPT_NAME)

‘ 2. 心拍ファイルの残骸をクリア
On Error Resume Next
objFSO.DeleteFile heartbeatPath, True
On Error GoTo 0

‘ 3. メインプロセスを自動再起動
WScript.Echo “[復旧] メインプロセスを再起動します…”
wShell.Run “cscript.exe //nologo C:\Automation\” & TARGET_SCRIPT_NAME, 0, False

‘ 再起動直後の誤検知を防ぐため少し待機
WScript.Sleep 10000
End If
Else
‘ ファイルすらない場合はメインがまだ起動していないか、既に終了している状態
‘ 必要に応じてここで「メインが起動していなければ起動する」ロジックを組むことも可能
End If
Loop

Sub KillTargetProcess(scriptName)
‘ WMIを使用して、特定のVBScriptを実行しているcscript/wscriptプロセスを強制終了する
Dim objWMIService, colProcess, objProcess
Set objWMIService = GetObject(“winmgmts:\\.\root\cimv2”)

‘ コマンドラインにスクリプト名が含まれるプロセスを抽出
Set colProcess = objWMIService.ExecQuery _
(“Select from Win32_Process Where Name like ‘%script.exe’ and CommandLine like ‘%” & scriptName & “%'”)

For Each objProcess in colProcess
On Error Resume Next
objProcess.Terminate()
WScript.Echo “プロセス強制終了 [PID: ” & objProcess.ProcessId & “]”
On Error GoTo 0
Next
End Sub

4. プロフェッショナルとしての実装上の注意点

このデュアルプロセス構造を実務のプロダクション環境に導入する際、素人がハマりがちな「実務の罠」がいくつか存在する。ここをクリアしてこそ本物の自動化エンジニアだ。

A. ファイルI/Oの競合(共有違反)への対策

メインスクリプトがハートビートファイルを書き込んでいるまさにその瞬間に、ウォッチドッグ側がそのファイルを読み込もうとすると、VBScript特効薬のエラー 「エラー 70: 書き込みできません (Permission denied)」 が発生する。
これを防ぐため、必ず `On Error Resume Next` でエラーをいなすか、ファイル書き込みはアトミック(一時ファイルを作ってリネーム)に行う設計にすべきだ。上記のサンプルでは簡略化のため `On Error Resume Next` の精神で堅牢性を担保している。

B. プロセスの多重起動(ゾンビの氾濫)の防止

ウォッチドッグが「ハングした」と誤認して `taskkill` を実行し、メインプロセスを再起動させまくった結果、タスクマネージャーが `cscript.exe` のゾンビだらけになる現象がよく起きる。
ウォッチドッグ側でプロセスを殺す前に、現在稼働している対象スクリプトのプロセス数をカウントし、「1つだけであることを確認してから殺す・再起動する」といったガード節を設けると、システム全体の暴走を完全に防ぐことができる。

C. 実行権限とセッションの壁 (Session 0 の罠)

Windows Serverなどでタスクスケジューラを使い、「ユーザーがログオフしている状態でも実行する」設定にしている場合、GUIを伴うコンポーネント(ExcelのApplication.Visible = Trueなど)は起動した瞬間にデッドロックする。
ウォッチドッグを噛ませる場合であっても、メイン業務スクリプトは原則として「完全なヘッドレス(CUI / cscript.exe / 非UIのCOMオブジェクトのみ)」で設計することが大原則である。

総括

VBScriptは古い言語と言われる。しかし、Windows環境の奥深く、OSの根幹を制御するうえで、追加のランタイムを必要としないWSH/VBScriptの機動性は今なお唯一無二の価値を持っている。

「スクリプトはいつか必ずフリーズするものだ」という性悪説に立ち、外側から別プロセスで監視・復旧させるこのウォッチドッグ・アーキテクチャを取り入れれば、あなたの管理する自動化バッチは「止まらないシステム」へと生まれ変わる。

泥臭い現場の自動化を、エレガントなアーキテクチャで支配せよ。

タイトルとURLをコピーしました