現場で死なないためのVBScript設計論:ファイル占有(Lock)を制する「リトライ・タイムアウト」実装の極意
業務自動化の現場において、VBScriptは今なお強力な武器だ。しかし、多くの「中級者」が陥る罠がある。それは、「ファイルは常に、自分がアクセスできる状態で存在している」という、あまりにもナイーブな前提に基づいた設計だ。
「ファイル移動を実行したら、『書き込み権限がありません』というエラーで落ちた」
「Excelがファイルを開いていたせいで、バッチ処理が途中で止まった」
これらは、単なるバグではない。「他プロセスによるファイル占有(File Lock)」という、実務における不可避なリアリティを設計に組み込めていない、設計思想の欠如である。
本記事では、プロフェッショナルな自動化エンジニアとして、ファイル占有をスマートに回避し、タイムアウト制御によって堅牢性を担保する「リトライ・ループ・パターン」を伝授する。
1. なぜ、あなたのFSOコードは「脆い」のか
標準的な `FileSystemObject (FSO)` の操作は、極めてシンプルだ。
' 素人の書き方:環境の変化に耐えられない
Set fso = CreateObject("Scripting.FileSystemObject")
fso.MoveFile "C:\Data\Input.csv", "C:\Data\Archive\Input.csv"
このコードは、実行した瞬間にファイルが「完全に解放されている」ことを前提としている。しかし、現実はどうだ?
- ウイルス対策ソフトがスキャンを開始した。
- ユーザーがExcelでファイルを開いたまま離席した。
- バックアッププロセスがファイルにロックをかけた。
これらの事象が発生した瞬間、あなたのスクリプトはランタイムエラーを吐いて停止し、後続のプロセスを破壊する。業務自動化において、「失敗すること」を前提としない設計は、設計ではない。
2. 堅牢な設計思想:Retry-Wait-Timeout パターン
プロフェッショナルは、ファイル操作を行う前に「そのファイルが操作可能(Exclusive Accessが可能)か」を検証するステップを挟む。
設計の要件は以下の3点だ。
1. 検証 (Test): ファイルを書き込みモード(または排他的モード)で開こうと試みる。成功すれば解放されている証拠。
2. 待機 (Wait): 失敗した場合、即座にエラーにせず、一定時間(Sleep)待機してリトライする。
3. 限界 (Timeout): 無限ループは避ける。規定の時間を経過しても確保できなければ、ログに詳細を残して「安全に」異常終了させる。
3. プロダクション・グレードのコード実装
以下に、そのまま実務に投入できるレベルの、再利用可能な関数を実装したコード例を示す。
'**
' Module: FileAutomationEngine
' Description: 堅牢なファイル操作のためのユーティリティ
'**
Option Explicit
Dim fso, logFile
Set fso = CreateObject("Scripting.FileSystemObject")
' --- メイン処理サンプル ---
Sub Main()
Dim targetFile, destFolder
targetFile = "C:\Temp\work_data.csv"
destFolder = "C:\Temp\Archive\"
' フォルダがなければ作成(基本中の基本)
If Not fso.FolderExists(destFolder) Then fso.CreateFolder(destFolder)
' ファイルの移動を試みる(タイムアウト30秒、リトライ間隔2秒)
If SafeMoveFile(targetFile, destFolder & "work_data_bak.csv", 30, 2) Then
WScript.Echo "SUCCESS: File moved successfully."
Else
WScript.Echo "ERROR: File move failed. Check log for details."
WScript.Quit 1 ' 異常終了を呼び出し元に伝える
End If
End Sub
'**
' Function: SafeMoveFile
' Purpose: 他プロセスによるロックを考慮した、安全なファイル移動
' Arguments:
' strSourcePath - 移動元ファイルパス
' strDestPath - 移動先ファイルパス
' intTimeout - 最大待機時間(秒)
' intInterval - リトライ間隔(秒)
' Returns: Boolean (成功ならTrue, 失敗ならFalse)
'**
Function SafeMoveFile(strSourcePath, strDestPath, intTimeout, intInterval)
Dim startTime, isAvailable, errObj
Dim strLogMsg
SafeMoveFile = False ' デフォルトは失敗
startTime = Timer
isAvailable = False
' 1. 移動元の存在確認
If Not fso.FileExists(strSourcePath) Then
WriteLog "ERROR: Source file does not exist: " & strSourcePath
Exit Function
End If
' 2. リトライループ開始
Do While (Timer - startTime) < intTimeout
isAvailable = CheckFileAvailability(strSourcePath)
If isAvailable Then
Exit Do ' ファイルが解放されている
Else
' ログに経過を記録(デバッグ用)
' WriteLog "INFO: File locked. Retrying in " & intInterval & "s..."
WScript.Sleep (intInterval * 1000)
End If
Loop
' 3. 最終判定と実行
If isAvailable Then
On Error Resume Next
fso.MoveFile strSourcePath, strDestPath
If Err.Number = 0 Then
WriteLog "SUCCESS: Moved " & strSourcePath & " to " & strDestPath
SafeMoveFile = True
Else
WriteLog "ERROR: FSO MoveFile failed. Error: " & Err.Description
SafeMoveFile = False
End If
On Error GoTo 0
Else
WriteLog "TIMEOUT: File remains locked after " & intTimeout & "s: " & strSourcePath
End If
End Function
'**
' Function: CheckFileAvailability
' Purpose: ファイルが排他的に開けるか(ロックされていないか)を判定する
'**
Function CheckFileAvailability(strFilePath)
Dim objFile
CheckFileAvailability = False
On Error Resume Next
' ファイルを Append モードで開こうとする。
' 他のプロセスが書き込み中、または開いている場合はここでエラーが発生する。
Set objFile = objFile.OpenTextFile(strFilePath, 8) ' 8 = ForAppending
If Err.Number = 0 Then
' エラーがなければアクセス可能
objFile.Close
CheckFileAvailability = True
End If
On Error GoTo 0
End Function
'**
' Sub: WriteLog
' Purpose: 簡易的なログ出力(実務ではファイル出力へ拡張せよ)
'**
Sub WriteLog(strMsg)
Dim timestamp
timestamp = Now
' 本来はファイル出力すべきだが、ここではデバッグ用にイミディエイト/コンソールへ
' WScript.Echo "[" & timestamp & "] " & strMsg
' 実装例: fso.OpenTextFile("C:\Logs\automation.log", 8, True).WriteLine ...
End Sub
' 実行
Main()
4. アーキテクトの視点:さらに堅牢にするための注意点
このコードは「標準的なリトライ」を網羅しているが、極限の信頼性を求めるなら以下の観点も忘れてはならない。
① データベースとの連携における「ゾンビ・セッション」
ファイル操作の前後でDB(SQL ServerやOracleなど)に接続している場合、ファイル操作の失敗がDBのトランザクションを未完了のまま放置させることがある。「ファイル操作の失敗 = DBのロールバック」という一連の整合性を意識せよ。
② ネットワーク遅延(Latency)の考慮
対象ファイルが共有フォルダ(UNCパス)にある場合、ネットワークの瞬断や遅延により、`CheckFileAvailability` が一時的に失敗することがある。この場合、`intInterval`(リトライ間隔)を少し長めに取るか、タイムアウト時間をネットワークの特性に合わせて設計する必要がある。
③ ログの重要性
「なぜか動かなかった」という報告は、エンジニアにとって最も苦痛なものだ。
今回実装した `WriteLog` のように、「いつ、どのファイルが、なぜ(Lockなのか、NotFoundなのか、権限不足なのか)失敗したのか」を時系列で記録する仕組みを必ず組み込め。ログこそが、深夜のトラブルシューティングにおける唯一の命綱となる。
結論
VBScriptは古くからある言語だが、その設計思想は現代の自動化においても全く色褪せていない。
「動けばいい」というコードを書くのは初心者だ。「環境の変化に耐え、エラーを予測し、適切に沈黙(あるいは報告)できる」コードを書くのが、プロフェッショナルである。
このリトライ・パターンをテンプレートとして、あなたのライブラリに組み込んでほしい。それが、あなたの作るシステムの「信頼」という名の価値を決定づける。
