VBScriptにおけるFSO操作の極致:I/O例外を制御し、堅牢な自動化アーキテクチャを構築する
多くの開発者は、`FileSystemObject` (FSO) を「単なるファイル操作の道具」だと過小評価している。彼らは `On Error Resume Next` を乱用し、エラーが起きた瞬間に沈黙するか、あるいは意味のないログを吐き出して停止する。
だが、我々プロフェッショナルにとって、I/O操作とは「不確実性の塊」である。
ファイルはウイルススキャンによって一瞬ロックされる。ネットワークドライブは瞬時に切断される。ディスク容量は予期せぬタイミングで枯渇する。
本稿では、VBScriptにおけるFSO操作の真の姿、すなわちHRESULTエラーコードの解読、オブジェクトのライフサイクル管理、そして高度な例外復旧ロジック(Retry/Skip/Quarantine)の設計について、その深淵を解説する。
1. HRESULTの深淵:Err.Numberの正体を暴く
VBScriptにおける `Err.Number` は、COM(Component Object Model)が返す `HRESULT` をデシマル(10進数)で表現したものに過ぎない。FSOが直面する致命的なエラーを制御するためには、その背後にある16進数表記のHRESULTを理解しておく必要がある。
以下の主要なエラーコードは、実装において必ず「分岐条件」として組み込むべきものだ。
| HRESULT (Hex) | Err.Number (Dec) | 原因 | 復旧戦略 |
|---|---|---|---|
| `0x800A0046` | `2147024710` | Permission Denied (書き込み禁止/他プロセスによるロック) | Exponential Backoff (指数関数的リトライ) |
| `0x800A004B` | `2147024715` | Path Not Found (パスが存在しない/ネットワーク切断) | Path Validation & Reconnect |
| `0x800A004C` | `2147024716` | Disk Full (ディスク容量不足) | Critical Halt & Cleanup |
| `0x800A0005` | `2147024741` | Access Denied (権限不足) | Skip & Log (管理者通知) |
単に「エラーが起きた」と判定するのではなく、「このエラーは一時的なものか、それとも致命的なものか」を判別すること。これがアーキテクトの責務である。
2. オブジェクトのライフサイクルとハンドルリークの回避
VBScriptはガベージコレクション(GC)を持つが、その挙動は非決定的である。
特にFSOでファイルストリームを開いた際、エラーハンドリング内でオブジェクトの解放(`Set obj = Nothing`)を怠ると、ファイルハンドルが解放されず、次回の実行やリトライが「Permission Denied」で失敗するという悪循環(デッドロック)を引き起こす。
エラーが発生した際、必ず `Err.Clear` と共に、関与したすべてのオブジェクトを明示的に破棄する「クリーンアップ・セクション」を設計しなければならない。
3. 実践:高度な例外ハンドリング・エンジン
以下に、リトライアルゴリズム、エラー分類、および隔離(Quarantine)機能を備えたプロフェッショナル向けのコード・テンプレートを示す。
' ==============================================================================
' Module: FSO_Resilient_Engine
' Description: 高度なエラー復旧ロジックを備えたファイル操作エンジン
' Author: Legendary Chief Architect
' ==============================================================================
Option Explicit
' 定数定義:エラーコードのデシマル表現
Const ERR_PERMISSION_DENIED = 2147024710 ' 0x800A0046
Const ERR_PATH_NOT_FOUND = 2147024715 ' 0x800A004B
Const ERR_DISK_FULL = 2147024716 ' 0x800A004C
' 設定値
Const MAX_RETRIES = 3
Const RETRY_DELAY_MS = 1000 ' ミリ秒
Dim fso
Set fso = CreateObject("Scripting.FileSystemObject")
' ------------------------------------------------------------------------------
' Main Execution Logic
' ------------------------------------------------------------------------------
Call Main()
Sub Main()
Dim targetPath, content
targetPath = "C:\Work\CriticalData.txt"
content = "Architect's Log: System integrity verified."
' ファイル書き込みの実行
If SafeWriteFile(targetPath, content) Then
WScript.Echo "Operation Successful."
Else
WScript.Echo "Operation Failed after all recovery attempts."
End If
End Sub
' ------------------------------------------------------------------------------
' Function: SafeWriteFile
' Description: リトライとエラー分類を内包した堅牢な書き込み関数
' ------------------------------------------------------------------------------
Function SafeWriteFile(strFilePath, strContent)
Dim objFile, retryCount, success, errNum
success = False
retryCount = 0
On Error Resume Next ' エラーハンドリングの開始
Do While retryCount <= MAX_RETRIES And Not success
' 1. 実行
Set objFile = fso.CreateTextFile(strFilePath, True)
If Err.Number = 0 Then
objFile.WriteLine strContent
objFile.Close
success = True
Else
' 2. エラー分析
errNum = Err.Number
WScript.Echo "[ERROR] Attempt " & (retryCount + 1) & " failed. Code: " & errNum
' 3. 戦略的判断
Select Case errNum
Case ERR_PERMISSION_DENIED
' ロックされている場合は、指数関数的バックオフでリトライ
WScript.Echo " -> Reason: File Locked. Retrying..."
Call ExponentialBackoff(retryCount)
retryCount = retryCount + 1
Case ERR_PATH_NOT_FOUND
' パス喪失はリトライしても無意味。ディレクトリ再構築を試みるか即時終了
WScript.Echo " -> Reason: Path Not Found. Critical Failure."
Exit Do
Case ERR_DISK_FULL
' ディスク満杯はリトライしても無意味。システム全体への通知が必要
WScript.Echo " -> Reason: Disk Full. Immediate Halt Required."
Call CriticalAlert("DISK FULL: " & strFilePath)
Exit Do
Case Else
' 未知のエラーはログを残してスキップ(Quarantine)
WScript.Echo " -> Reason: Unknown Error. Moving to Quarantine."
Call QuarantineFile(strFilePath)
Exit Do
End Select
End If
' 4. オブジェクトの明示的解放(ハンドルリーク防止の要)
Set objFile = Nothing
Err.Clear
Loop
SafeWriteFile = success
On Error GoTo 0 ' エラーハンドリングの解除
End Function
' ------------------------------------------------------------------------------
' Sub: ExponentialBackoff
' Description: リトライの間隔を徐々に広げるアルゴリズム
' ------------------------------------------------------------------------------
Sub ExponentialBackoff(attempt)
Dim waitTime
' 2^attempt * base_delay
waitTime = (2 ^ attempt) * RETRY_DELAY_MS
WScript.Sleep waitTime
End Sub
' ------------------------------------------------------------------------------
' Sub: QuarantineFile
' Description: 処理不能なファイルを隔離フォルダへ移動
' ------------------------------------------------------------------------------
Sub QuarantineFile(strPath)
Dim quarantineDir
quarantineDir = "C:\Work\Quarantine\"
On Error Resume Next
If Not fso.FolderExists(quarantineDir) Then fso.CreateFolder(quarantineDir)
' ファイルを隔離ディレクトリへ移動(既存ファイルとの衝突を避けるためタイムスタンプ付与)
fso.MoveFile strPath, quarantineDir & fso.GetBaseName(strPath) & "_" & Timer & ".bak"
If Err.Number <> 0 Then
WScript.Echo " [FATAL] Failed to quarantine file."
End If
On Error GoTo 0
End Sub
' ------------------------------------------------------------------------------
' Sub: CriticalAlert
' Description: 管理者への緊急通知(実務ではメール送信やイベントログ書き込みを行う)
' ------------------------------------------------------------------------------
Sub CriticalAlert(msg)
' ここに WSH.Shell によるイベントログ書き込みや、SMTPによるメール送信ロジックを実装する
WScript.Echo "[CRITICAL ALERT] " & msg
End Sub
4. 結論:エンジニアの資質は「例外」に宿る
優れたコードとは、正常な入力を処理するコードではない。予期せぬ異常事態において、システムをいかにエレガントに、かつ最小限の被害で停止(あるいは継続)させられるかを設計したコードである。
FSOを操作する際、単に `CreateTextFile` を呼び出すだけでは素人だ。その背後にあるOSの挙動、HRESULTの重み、そしてオブジェクトのライフサイクルを完全に掌握して初めて、我々は「自動化エンジニア」と名乗ることができる。
レガシーな環境であればあるほど、この「防御的プログラミング」の価値は高まる。君の書くコードが、深夜のシステムダウンによる呼び出しを防ぐ盾となることを願う。
