こんにちは!VBScriptの世界へようこそ。
業務の自動化を進めていると、一度はこんな絶望的な状況に出会ったことはありませんか?
「大量のデータを処理するVBScriptを実行したはいいものの、途中でネットワークがフリーズしたり、外部アプリケーションが固まったりして、スクリプト全体が『応答なし』になって止まってしまった……。朝出社したら、画面がフリーズしたまま一日中時間が無駄になっていた」
マクロの記録や単純な自動化から一歩進んで、本格的な運用を考え始めると避けて通れないのがこの「ハングアップ(無応答)問題」です。
今回は、VBScriptがどれだけ深い闇(フリーズ)に落ちようとも、外側から監視して自動で叩き起こし、再起動させてくれる「ウォッチドッグ(監視犬)構造」を一緒に作っていきましょう。
ここをクリアすれば、あなたの書く自動化スクリプトは、プロの現場でも通用する「止まらない堅牢なシステム」に生まれ変わります。さあ、一緒に扉を開けましょう!
—
なぜVBScriptは「応答なし」で沈黙するのか?
VBScriptは、Windows標準で動く非常に手軽で強力なスクリプト言語です。しかし、その裏側にある仕組み(WSH:Windows Script Host)を知ることは、エンジニアとしてとても大切です。
VBScriptの基本は「シングルスレッド(1つの道)」です。
つまり、上から下へ順番に処理を実行している最中に、外部のCOMオブジェクト(Excel操作やWebAPI呼び出しなど)が無限ループや応答待ちに陥ると、スクリプト自身は「今、作業中だよ」と訴えるだけで、自分の異常に自分で気づくことができなくなります。
解決の鍵は「デュアルプロセス設計(二人の監視者)」
自分自身がフリーズしているのだから、「自分を監視する別の独立したプロセス(別の子スクリプト)」を外側から動かすしかありません。これが、今回紹介する「ウォッチドッグ構造」の核心です。
[Windowsタスク / 手動起動]
│
▼
┌──────────────┐ 生きてるよ! ┌──────────────┐
│ 親スクリプト │ ──(ハートビート)──> │ 子スクリプト │
│ (メイン処理) │ │ (ウォッチドッグ)│
└──────────────┘ └──────┬───────┘
│ │
│ (もしフリーズして信号が途絶えると…) │
└─────────────────── ✖ ───────────────┘
│
▼
[ 親プロセスを強制終了 & 再起動! ]
親が「生きている証(ハートビート)」として、定期的にテキストファイルを更新(またはタイムスタンプを刻む)。子スクリプトはそれを監視し、もし一定時間更新が途絶えたら、「おや、親が倒れたな」と判断して親を強制終了し、新しく起動し直すという仕組みです。
—
実装コード:止まらない自動化の全貌
それでは、実際に現場でそのままコピペして使えるコードを見ていきましょう。
今回は分かりやすくするため、以下の2つのファイルに分けて作成します。
1. `parent.vbs` (メインの処理を行う親スクリプト)
2. `watchdog.vbs` (親を監視し続ける子スクリプト)
1. 親スクリプト(`parent.vbs`)のコード
親スクリプトは、自分の生存を知らせるために、定期的に「生きているよファイル(heartbeat.txt)」のタイムスタンプを更新します。
‘ =================================================================
‘ 親スクリプト (parent.vbs)
‘ メインの業務処理を行いつつ、定期的に生存信号(ハートビート)を送信する
‘ =================================================================
Option Explicit
Dim fso, shell, heartbeatPath, intervalSec
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set shell = CreateObject(“WScript.Shell”)
‘ 生存確認用ファイルのパス(同階層に作成)
heartbeatPath = fso.GetParentFolderName(WScript.ScriptFullName) & “\heartbeat.txt”
intervalSec = 5 ‘ 5秒ごとに生存信号を更新
WScript.Echo “親スクリプトを起動しました。処理を開始します。”
‘ メインループ(ここでは無限に処理が続くと仮定)
Dim counter
counter = 0
Do While True
counter = counter + 1
‘ — 【ここに実際の重い業務処理を書きます】 —
‘ 例: WScript.Echo “処理中… ” & counter
‘ ————————————————
‘ 【重要】「生きてますよ」というサインとしてファイルを上書き作成(タイムスタンプ更新)
On Error Resume Next
Dim ts
Set ts = fso.CreateTextFile(heartbeatPath, True)
ts.Write Now & ” – Alive”
ts.Close
Set ts = Nothing
On Error GoTo 0
‘ わざと重い処理のフリーズを再現したい場合は、以下のコメントアウトを外してテストしてみてください
‘ If counter = 5 Then
‘ WScript.Echo “ここでわざとフリーズ(無限ループ)します…”
‘ Do: Loop ‘ フリーズ状態の模擬
‘ End If
‘ 次の信号まで待機 (5秒)
WScript.Sleep intervalSec 1000
Loop
2. 監視用子スクリプト(`watchdog.vbs`)のコード
次に、この親スクリプトを安全な距離から見守るウォッチドッグを作ります。
‘ =================================================================
‘ 子スクリプト / ウォッチドッグ (watchdog.vbs)
‘ 親スクリプトの生存信号を監視し、沈黙したら再起動する
‘ =================================================================
Option Explicit
Dim fso, shell, currentDir, parentScript, heartbeatPath, timeoutSec
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set shell = CreateObject(“WScript.Shell”)
currentDir = fso.GetParentFolderName(WScript.ScriptFullName)
parentScript = currentDir & “\parent.vbs”
heartbeatPath = currentDir & “\heartbeat.txt”
timeoutSec = 15 ‘ 15秒間、信号が途絶えたら「フリーズ」とみなす
Dim checkIntervalMs
checkIntervalMs = 5000 ‘ 5秒ごとに生死チェック
WScript.Echo “ウォッチドッグ(監視システム)が起動しました。”
‘ 初回:親スクリプトを起動する
Call StartParentProcess()
Do While True
WScript.Sleep checkIntervalMs
‘ ハートビートファイルが存在するか確認
If fso.FileExists(heartbeatPath) Then
Dim targetFile, lastModified, diffSeconds
Set targetFile = fso.GetFile(heartbeatPath)
lastModified = targetFile.DateLastModified
‘ 最後の更新から何秒経過したか計算
diffSeconds = DateDiff(“s”, lastModified, Now)
‘ タイムアウト時間を超えていたら、親がフリーズしていると判断
If diffSeconds > timeoutSec Then
WScript.Echo “【警告】親スクリプトの応答がありません(経過秒数: ” & diffSeconds & “秒)。強制終了して再起動します。”
‘ 親プロセスを強制終了 (cscript.exe / wscript.exe のうち parent.vbs を叩いているものをキル)
‘ ※タスクkillコマンドで強制終了します
shell.Run “taskkill /f /fi “”imagename eq wscript.exe”” /fi “”windowtitle eq parent.vbs”””, 0, True
shell.Run “taskkill /f /fi “”imagename eq cscript.exe”” /fi “”windowtitle eq parent.vbs”””, 0, True
‘ 少し待ってから再起動
WScript.Sleep 2000
Call StartParentProcess()
End If
Set targetFile = Nothing
Else
‘ ファイルが最初から無い場合は作成を待つか再起動
Call StartParentProcess()
End If
Loop
‘ 親プロセスを起動するサブルーチン
Sub StartParentProcess()
WScript.Echo “親スクリプトを新規起動します…”
‘ cscriptでバックグラウンド実行(必要に応じてwscriptに変更可)
shell.Run “cscript //nologo “”” & parentScript & “”””, 1, False
‘ 起動直後のタイムアウトを防ぐため、強制的にハートビートファイルを今の日時で作成しておく
On Error Resume Next
Dim ts
Set ts = fso.CreateTextFile(heartbeatPath, True)
ts.Write Now & ” – Initialized”
ts.Close
Set ts = Nothing
On Error GoTo 0
End Sub
—
現場でつまずきやすい「陥りやすいエラー」と対策
VBScriptでプロセス管理やファイル操作を行う際、初心者が必ずと言っていいほどハマる罠があります。ここを知っておくだけで、開発スピードが何倍も変わります。
1. ファイルアクセスの競合エラー(エラー 70: 書き込み権限がありません)
- 現象: 親スクリプトがハートビートファイルを書き込んでいるまさにその瞬間に、子スクリプトが読み込もうとして「Permission denied」でスクリプトがクラッシュする。
- 対策: 上記のコードでも入れている `On Error Resume Next` を一時的に挟むか、ファイルIOのエラーをハンドリングするのが鉄則です。VBScriptでは排他制御が難しいため、「一瞬の読み書きの衝突でスクリプト自体を落とさない」防御的プログラミングが不可欠です。
2. パスの指定ミス(カレントディレクトリの罠)
- 現象: タスクスケジューラやショートカット経由で実行すると、カレントディレクトリ(基準となる場所)が意図せぬ場所(System32など)になり、ファイルが見つからないエラーになる。
- 対策: スクリプト内でファイルや外部アプリを呼び出すときは、必ず `WScript.ScriptFullName` を基にして絶対パスを構築してください(上記のサンプルコードでは `fso.GetParentFolderName(WScript.ScriptFullName)` を使って安全に同階層を指定しています)。
—
先輩エンジニアからの温かいアドバイス
ここまで読み進めていただきありがとうございます!
「VBScriptなんて古い言語なのかな」と思うかもしれませんが、Windows環境があれば特別なインストールも不要で、今でも現場の自動化において最強の相棒になってくれる素晴らしい言語です。
今回紹介した「ウォッチドッグ構造」は、VBScriptに限らず、PythonやPowerShell、VBAなど、あらゆる自動化スクリプトの設計に応用できる普遍的なエンジニアリングの知恵です。
「プログラムはいつか必ず止まるもの。止まった後にどうリカバリーするか」を考えることこそが、マクロの記録から卒業したワンランク上のエンジニアの証です。
ここをクリアしたあなたなら、もう日常のフリーズに怯える必要はありません。自信を持って、スマートで頑健な自動化ライフを楽しんでくださいね!バッチリうまくいきますよ!
