ネットワークの不確実性を制御せよ:FSOを用いた耐障害性フォールバック・アーキテクチャの構築
現場のエンジニアが直面する最も忌まわしい事象の一つは、「昨日まで正常に動いていた自動化スクリプトが、ネットワークの一時的な瞬断によってプロセスを停止(ハング)させ、後続の業務を全停止させる」ことである。
特にUNCパス(`\\Server\Share\…`)をターゲットとするVBScriptを用いたレガシーシステムにおいて、ネットワークの不通は単なる「エラー」ではない。それは、I/O待ちによるプロセスの長時間フリーズ、あるいは不適切なエラーハンドリングによる、不完全なファイル生成という「破壊的挙動」を引き起こす。
本稿では、FileSystemObject (FSO) を単なるファイル操作ツールとしてではなく、「動的な環境変化に適応するフォールバック・メカニズム」の核として活用する、極限の設計思想を伝授する。
1. 脆弱な設計:なぜ単純な `FileExists` では足りないのか
多くの初級者は、以下のような実装で満足する。
' 典型的な、しかし脆弱な実装例
If fso.FileExists(uncPath) Then
' 書き込み処理
Else
' エラー終了
End If
これは致命的に不完全である。なぜなら、`FileExists` メソッド自体がネットワークパスに対して発行される際、ネットワークが応答不能(Timeout状態)であれば、スクリプトはその応答を待機するために数秒から数十秒間、実行コンテキストをブロックするからだ。
シニアエンジニアが求めるべきは、単なる存在確認ではない。「パスへの到達可能性(Reachability)」と「書き込み権限の即時判定」、そして「失敗時の決定論的な代替経路(Fallback Path)の確保」である。
2. アーキテクチャの核心:フォールバック・パターンの実装
堅牢なスクリプトを構築するためには、以下の3層構造を実装する必要がある。
1. Attempt Layer (試行層): UNCパスへのアクセスを試みる。
2. Detection Layer (検知層): `Err.Number` を精査し、エラーが「ファイル不在」なのか「ネットワーク不通」なのかを峻別する。
3. Fallback Layer (代替層): ネットワーク不通と判定された場合、即座にローカルのセーフティディレクトリへI/Oをリダイレクトする。
実装コード:耐障害性ファイル出力モジュール
以下のコードは、実務における「プロトコル」としてそのまま利用可能なレベルまで昇華させたものである。
' =============================================================================
' Module: ResilientFileHandler
' Purpose: ネットワーク切断を検知し、ローカルへ自動退避するフォールバック処理
' Author: Legendary Chief Architect
' =============================================================================
Option Explicit
' 定数定義:マジックナンバーを排除し、保守性を高める
Const ERR_NETWORK_UNREACHABLE = 53 ' Path not found
Const ERR_ACCESS_DENIED = 70 ' Permission denied
Const ERR_DEVICE_NOT_READY = 76 ' Device not ready
Dim fso, uncPath, localFallbackPath, targetFile
' 初期設定
uncPath = "\\ProductionServer\Data\Output\"
localFallbackPath = "C:\LocalFallback_Storage\"
targetFile = "DailyReport_" & Year(Date) & Month(Date) & Day(Date) & ".txt"
' メインルーチン
Call ExecuteResilientOutput()
Sub ExecuteResilientOutput()
Dim finalPath, isFallback
isFallback = False
' 1. FSOオブジェクトの生成(ライフサイクル管理の開始)
Set fso = CreateObject("Scripting.FileSystemObject")
' 2. ローカル退避用ディレクトリの存在確認と確保
' ネットワークが死んでいても、ローカルの準備はできていなければならない
If Not fso.FolderExists(localFallbackPath) Then
On Error Resume Next
fso.CreateFolder(localFallbackPath)
If Err.Number <> 0 Then
WScript.Echo "CRITICAL ERROR: Local fallback directory creation failed."
WScript.Quit 1
End If
On Error GoTo 0
End If
' 3. 書き込み先パスの決定プロセス
finalPath = DetermineWritePath(fso, uncPath, localFallbackPath, isFallback)
' 4. 実際の書き込み処理
On Error Resume Next
Dim ts
Set ts = fso.CreateTextFile(finalPath, True)
If Err.Number <> 0 Then
' 書き込み中にエラーが発生した場合(例:書き込み中の切断)
WScript.Echo "FATAL: I/O Operation failed even on fallback path: " & Err.Description
' ここでログ出力等の緊急事態処理を行う
Else
ts.WriteLine "Timestamp: " & Now
ts.WriteLine "Status: " & IIf(isFallback, "FALLBACK_MODE", "NORMAL_MODE")
ts.WriteLine "Data content..."
ts.Close
WScript.Echo "SUCCESS: File written to " & finalPath & IIf(isFallback, " (FALLBACK)", "")
End If
On Error GoTo 0
' 5. オブジェクトの明示的解放(メモリリークとCOM参照の残存を防ぐ)
Set ts = Nothing
Set fso = Nothing
End Sub
' -----------------------------------------------------------------------------
' Function: DetermineWritePath
' Description: ネットワーク到達性を判定し、最適なパスを返す
' -----------------------------------------------------------------------------
Function DetermineWritePath(objFSO, targetUNC, fallbackDir, ByRef outIsFallback)
Dim testPath
testPath = targetUNC
outIsFallback = False
' ネットワークパスへのアクセスを試行
' On Error Resume Next を局所的に適用し、エラーを制御下に置く
On Error Resume Next
' フォルダの存在確認をトリガーとして、ネットワークの応答を確認
' ※単なる FileExists よりも FolderExists の方が、ディレクトリ構造へのアクセスを伴うため確実性が高い
If Not objFSO.FolderExists(targetUNC) Then
' エラー番号の精査
Select Case Err.Number
Case ERR_NETWORK_UNREACHABLE, ERR_ACCESS_DENIED, ERR_DEVICE_NOT_READY
' ネットワーク不通または権限エラーの場合、フォールバックへ
outIsFallback = True
Case Else
' その他のエラー(ファイル名不正など)は致命的と判断
WScript.Echo "ERROR: Unexpected error accessing UNC: " & Err.Description
WScript.Quit 1
End Select
End If
On Error GoTo 0
If outIsFallback Then
DetermineWritePath = fallbackDir & targetFile
Else
DetermineWritePath = targetUNC & targetFile
End If
End Function
' 補助関数:三項演算子の代替
Function IIf(condition, truePart, falsePart)
If condition Then IIf = truePart Else IIf = falsePart
End Function
3. 技術的深度:プロフェッショナルのための知見
オブジェクトのライフサイクルとメモリ管理
VBScriptはガベージコレクション(GC)に依存しているように見えるが、COMオブジェクト(FSOなど)の扱いにおいては、明示的な `Set obj = Nothing` が極めて重要である。
特に、長時間稼働するバッチ処理や、ループ内で大量のファイルを操作する場合、オブジェクトの解放を怠ると、メモリリークのみならず、ファイルハンドルが解放されずに残り、後続のプロセスが「ファイルが別のプロセスで使用されています」というエラーで死ぬ原因となる。
Windows API との境界線
本来、ネットワークのドライブタイプ(`DRIVE_REMOTE` か `DRIVE_FIXED` か)を厳密に判定するには、Windows APIの `GetDriveType` を呼び出すのが最短ルートである。しかし、VBScriptから直接Win32 APIを叩くことは不可能だ。
我々エンジニアは、`WScript.Shell` を介して `cmd /c fsutil` を呼び出すか、あるいは今回のように 「FSOの例外トラップ(Error Trapping)をネットワーク・プローブとして利用する」 という、OSの挙動に基づいた擬似的なAPI呼び出し手法を選択する。これは、外部依存を最小限に抑えつつ、実効性を担保する高度なテクニックである。
エラーハンドリングの極意
`On Error Resume Next` は、諸刃の剣である。これをグローバルに適用してはならない。
本コードのように、「エラーが予測される境界領域(I/O操作、ネットワークアクセス)」にのみ局所的に適用し、直後に `Err.Number` を検証して即座に `On Error GoTo 0` で復帰させる。 これが、予測不可能なランタイムエラーからシステムを保護するための、鉄則である。
結論
ネットワークは常に裏切る。その前提に立った設計こそが、真のエンタープライズ・オートメーションである。
FSOを単なる「ファイル操作用ライブラリ」として扱うか、それとも「環境の不確実性を吸収するインテリジェントなインターフェース」として扱うか。その差が、エンジニアとしての格の違いを決めるのである。
