【実務・中級編】【実務中級者向け】他プロセスによるファイル使用中の解放を待機しタイムアウト付きで確実に処理するFSOループ – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

現場で死なないための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は古くからある言語だが、その設計思想は現代の自動化においても全く色褪せていない。
「動けばいい」というコードを書くのは初心者だ。「環境の変化に耐え、エラーを予測し、適切に沈黙(あるいは報告)できる」コードを書くのが、プロフェッショナルである。

このリトライ・パターンをテンプレートとして、あなたのライブラリに組み込んでほしい。それが、あなたの作るシステムの「信頼」という名の価値を決定づける。

タイトルとURLをコピーしました