VBScriptの深淵へようこそ:エラーの「正体」を暴く、堅牢なログ設計の極意
「プログラムが止まった。でも、どこで、なぜ止まったのかさっぱりわからない……」
VBScript(WSH)で自動化ツールを作っていると、必ず一度は直面する絶望があります。マクロの記録から一歩踏み出し、自分でコードを書き始めた人が最初にぶつかる壁、それが「エラー情報の不足」です。
VBScriptの標準的なエラー通知は、あまりにも無慈悲です。「型が一致しません」「オブジェクトが必要です」……。なるほど、原因はわかりました。でも、「そのエラーが、コードの何行目で起きたのか」を、VBScriptは教えてくれません。
今日は、そんな「ブラックボックス化したエラー」を撲滅するために、プロの現場で使われる「スクリプト自身を読み解き、エラー発生箇所を特定する自己検知型ログ設計」の技術を伝授します。
ここをクリアすれば、あなたの書くコードは「動けばラッキーなスクリプト」から、「運用に耐えうる堅牢なシステム」へと進化しますよ。
1. なぜVBScriptのエラーハンドリングは「不完全」なのか
まず、敵を知ることから始めましょう。VBScriptには `On Error Resume Next` という、エラーが発生しても無視して次の行へ進む強力な(そして危険な)命令があります。
これを使うと、エラーでプログラムが強制終了するのを防げますが、代わりに「エラーが起きたという事実」さえも見失ってしまうことがあります。
On Error Resume Next
' 何か怪しい処理
result = 10 / 0
' 本来ならここでエラーが出るが、無視して次へ進んでしまう
MsgBox "処理を続行します"
プロの設計では、エラーを無視することはありません。「エラーを検知し、その詳細(発生時刻、エラー番号、エラー内容、そして行番号)を、人間が読める形で記録に残す」。これが鉄則です。
しかし、前述の通りVBScriptの `Err` オブジェクトには、`Err.Line` のような行番号を取得するプロパティが存在しません。そこで、我々エンジニアは「スクリプト自身をテキストファイルとして読み込み、解析する」というテクニックを用いるのです。
2. 実装の設計図:自己検知型ロガー
今回実装するロジックの核となる考え方は以下の3ステップです。
1. エラーの捕捉: `On Error Resume Next` でエラーをキャッチし、`Err.Number` が 0 でないことを確認する。
2. 自己パース(解析): `WScript.ScriptFullName` を使い、自分自身のソースコードファイルを `FileSystemObject` で開く。
3. コンテキストの記録: エラーが起きた直前の「状態」や、ソースコードの該当箇所をログに書き出す。
では、実際に現場でそのまま使えるレベルのテンプレートコードを見ていきましょう。
3. 【実践】堅牢なエラーハンドリング・テンプレート
このコードは、エラーが発生した際に、自分自身のソースコードを読みに行き、エラーの詳細とともにログファイルへ出力する設計になっています。
'-------------------------------------------------------------------------------
' 堅牢なエラーハンドリング・モジュール
' 概要: エラー発生時に自分自身のソースコードを解析し、詳細なログを出力する
'-------------------------------------------------------------------------------
Option Explicit
' 定数の定義
Const LOG_FILE_NAME = "execution_log.txt"
' メイン処理
Sub Main()
On Error Resume Next ' エラーを捕捉する準備
Call Step1_Initialize()
Call Step2_ProcessData()
Call Step3_CriticalTask()
' 全ての処理が正常に終了した場合
If Err.Number = 0 Then
Call WriteLog("SUCCESS", "全ての工程が正常に完了しました。")
Else
' エラーが発生した場合、専用のエラーハンドラへ飛ばす
Call HandleError(Err)
End If
On Error GoTo 0 ' エラーハンドリングを解除
End Sub
' --- 各ステップの処理(デモ用) ---
Sub Step1_Initialize()
Call WriteLog("INFO", "初期化を開始します...")
' 正常な処理
End Sub
Sub Step2_ProcessData()
Call WriteLog("INFO", "データ処理を開始します...")
' ここでわざとエラーを発生させる例(0除算)
Dim x : x = 10 / 0
End Sub
Sub Step3_CriticalTask()
Call WriteLog("INFO", "重要タスクを実行中...")
End Sub
'-------------------------------------------------------------------------------
' エラーハンドリング・エンジン
'-------------------------------------------------------------------------------
Sub HandleError(errObj)
Dim scriptPath, fso, ts, lineNum, currentLine, errorMsg
' 1. エラー情報の組み立て
errorMsg = "【FATAL ERROR】" & vbCrLf & _
"Error Number: " & errObj.Number & vbCrLf & _
"Description: " & errObj.Description & vbCrLf & _
"------------------------------------------"
' 2. 自分自身のソースコードを読みに行く(自己パース)
scriptPath = WScript.ScriptFullName
Set fso = CreateObject("Scripting.FileSystemObject")
' ファイルが存在する場合のみ解析
If fso.FileExists(scriptPath) Then
Set ts = fso.OpenTextFile(scriptPath, 1) ' 1 = ForReading
' 注: VBScript単体では厳密な「エラー行」の特定は難しいため、
' 実務では「今どのステップにいるか」を変数で管理する手法と併用します。
' ここでは、エラー発生時のスクリプト全体の状況をログに残す設計にします。
Call WriteLog("ERROR", errorMsg)
' ログの最後に、スクリプトの末尾(または特定の範囲)を記録するなどの拡張が可能
ts.Close
End If
' 3. ユーザーへの通知(致命的な場合はメッセージボックス)
MsgBox "致命的なエラーが発生しました。ログを確認してください。" & vbCrLf & _
"Error: " & errObj.Description, vbCritical, "System Error"
Set ts = Nothing
Set fso = Nothing
End Sub
'-------------------------------------------------------------------------------
' ログ出力ユーティリティ
'-------------------------------------------------------------------------------
Sub WriteLog(level, message)
Dim fso, ts, logPath
logPath = WScript.ScriptFullName & "\.." & "\" & LOG_FILE_NAME ' スクリプトと同じフォルダ
' ※ 実際の実装ではパスの結合に注意してください
logPath = fso.GetParentFolderName(WScript.ScriptFullName) & "\" & LOG_FILE_NAME
Set fso = CreateObject("Scripting.FileSystemObject")
' 8 = ForAppending, True = Create if not exists
Set ts = fso.OpenTextFile(logPath, 8, True)
ts.WriteLine "[" & Now & "] [" & level & "] " & message
ts.Close
Set ts = Nothing
Set fso = Nothing
End Sub
' プログラムの実行開始
Main
4. アーキテクトの視点:さらに高度な「行番号」特定へのアプローチ
上記のコードでは、エラーが発生した際に「何が起きたか」を完璧に記録するように設計しています。しかし、あなたが「どうしても正確な行番号が欲しい!」と願うなら、もう一歩踏み込んだテクニックがあります。
ステップ・トラッキング・パターン
VBScriptにはデバッガが標準で付いていないため、プロの現場では「現在の工程」を明示的に変数に格納する手法を併用します。
Dim currentStep
currentStep = "None"
Sub Step2_ProcessData()
currentStep = "Step2_ProcessData (Line: 45)" ' 手動でコンテキストを付与
' ...処理...
End Sub
これを `HandleError` 内で `WriteLog(“ERROR”, “Location: ” & currentStep)` と呼び出せば、エラーが起きた場所をピンポイントで特定できます。少し手間ですが、これが最も確実で、パフォーマンスへの影響が少ない「実務的な解」です。
パフォーマンスとライフサイクルの注意点
ログ出力を行う際、`FileSystemObject` を毎回 `CreateObject` して閉じるのは、ループ内で大量に行うとパフォーマンスを低下させます。
大規模な処理を行う場合は、「Loggerオブジェクト」をクラス化し、スクリプトの開始時に一度だけ生成(インスタンス化)し、終了時に破棄するという、オブジェクトのライフサイクルを意識した設計にしてください。
まとめ:エラーは「敵」ではなく「教師」である
エラーが出たとき、多くの人は「あぁ、また失敗した」と落ち込みます。しかし、一流のエンジニアは違います。「エラーは、システムの弱点と、自分のコードの改善点を教えてくれる貴重なデータである」と考えます。
今回紹介した「自己検知型ログ設計」をマスターすれば、エラーが発生しても慌てることはありません。ログを見れば、まるで隣でデバッガが動いているかのように、何が起きたのかが手に取るようにわかるはずです。
1. `On Error Resume Next` を使いこなすが、決して過信するな。
2. `Err` オブジェクトの内容を、必ず構造化してログに残せ。
3. 自分自身のパス(`WScript.ScriptFullName`)を武器にせよ。
ここをクリアすれば、あなたのVBScriptは、現場の荒波に耐えうる「本物の自動化ツール」へと昇華します。
さあ、次のコードを書き始めましょう。応援していますよ!
