【テクニカル・上級編】【ファイルシステム監視】FileSystemObject と WMI を組み合わせた「ファイル作成完了」の確実な検知と後続処理 – VBScript (Visual Basic Scripting Edition)解析バイブル

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

「イベント」の罠を越えて:VBScriptにおけるファイルI/O同期の極致

業務自動化の現場において、VBScriptは「使い捨てのスクリプト」として軽視されがちである。しかし、WMI(Windows Management Instrumentation)を駆使し、OSの低レイヤーな挙動を理解した上で記述されたスクリプトは、極めて堅牢なバックエンド・エージェントへと昇華する。

多くのエンジニアが陥る致命的な設計ミスがある。それは、「ファイルが作成されたイベント(Creation Event)を検知した瞬間、そのファイルは処理可能である」という盲信だ。

本稿では、WMIによるファイル監視とFileSystemObject(FSO)を組み合わせ、書き込み中のロック(Sharing Violation)を完璧に回避しながら、ファイルの実体化を確実に捉える「極限の監視ロジック」を解説する。

1. 概念の再定義:Creation Event と Write Completion の乖離

Windowsのファイルシステム(NTFS)において、ファイルが作成されるプロセスは、OSのカーネルレベルで複数のステップを踏む。

1. File Handleの生成: プロセスが `CreateFile` APIを呼び出し、ファイルのエントリがMFT(Master File Table)に作られる。
2. バッファリングと書き込み: データがメモリバッファを経由し、ディスクへフラッシュされる。
3. ハンドル解放: 書き込みプロセスがファイルを閉じ、排他的ロックが解除される。

WMIの `__InstanceCreationEvent` が発火するのは、あくまでステップ1の直後である。このタイミングで後続の処理(ファイルの移動、読み取り、解析)を開始しようとすれば、高確率で `Permission Denied (0x80070005)`、すなわち「書き込み中ロックエラー」に直面する。

シニアエンジニアが構築すべきは、「作成されたこと」をトリガーとしつつ、「書き込みが完了したこと」を検証する二段構えのステートマシンである。

2. アーキテクチャ設計:WMI監視とポーリング・バリデーション

堅牢な監視スクリプトには、以下の3つのコンポーネントが必要となる。

1. WMI Observer: `SWbemServices.ExecNotificationQuery` を用いた、非同期的なイベント待機。
2. Size-Delta Validator: ファイルサイズの変化が止まったことを検知する、高精度なポーリング・ループ。
3. Object Lifecycle Manager: COMオブジェクトのメモリリークを防ぐための、厳格な破棄プロセス。

戦略的アプローチ

WMIでイベントを捕捉した後、直ちに処理へ移行せず、`FileSystemObject` を用いて「前回のファイルサイズ」と「現在のファイルサイズ」を比較する。サイズが同一、かつ一定時間変化がないことを確認して初めて、ビジネスロジックへとバトンを渡す。

3. 実装:堅牢なファイル監視エンジンの極致

以下に、実戦投入可能なレベルのVBScriptコードを示す。単なるサンプルではない。エラーハンドリング、タイムアウト制御、およびオブジェクトの明示的解放を組み込んだ、プロフェッショナルな設計である。

' **
' File Monitor Engine (High-Reliability Version)
' 
' Description:
'   WMIを使用してファイル作成を検知し、ファイルサイズの変化を監視することで
'   書き込み完了を確実に判定する。
' **

Option Explicit

' --- Configuration ---
Const WATCH_DIR      = "C:\\Temp\\WatchFolder\\"  ' 監視対象ディレクトリ(\\を二重にする)
Const MAX_RETRIES    = 10                         ' サイズ変化確認の最大試行回数
Const RETRY_INTERVAL = 1000                       ' 試行間隔 (ms)
Const TIMEOUT_SEC    = 30                         ' 最終的なタイムアウト (秒)

' --- Global Objects ---
Dim objFSO, objWMI, objEventSink
Set objFSO = CreateObject("Scripting.FileSystemObject")

' --- Main Execution ---
Main

Sub Main()
    On Error Resume Next
    
    WScript.Echo "Starting File Monitor on: " & WATCH_DIR
    
    ' WMIサービスの初期化
    Set objWMI = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
    If Err.Number <> 0 Then
        WScript.Echo "Critical Error: WMI Connection Failed. " & Err.Description
        WScript.Quit 1
    End If

    ' WMI Query: ファイル作成イベントを監視
    ' ※ TargetInstance.Drive と Path を指定することで、監視範囲を限定し負荷を軽減する
    Dim strQuery
    strQuery = "SELECT * FROM __InstanceCreationEvent WITHIN 1 " & _
               "WHERE TargetInstance ISA 'CIM_DataFile' " & _
               "AND TargetInstance.Drive = 'C:' " & _
               "AND TargetInstance.Path = '" & Replace(WATCH_DIR, "\", "\\") & "'"

    Dim objQuery, objFileInstance
    Set objQuery = objWMI.ExecNotificationQuery(strQuery)

    ' 無限ループによる監視
    Do
        ' イベントの待機 (NextEvent はブロッキング処理)
        Set objFileInstance = objQuery.NextEvent
        
        If Not objFileInstance Is Nothing Then
            Dim strFilePath
            strFilePath = objFileInstance.TargetInstance.Name
            
            WScript.Echo "Event Detected: " & strFilePath
            
            ' 書き込み完了の検証プロセスへ移行
            If WaitForFileReady(strFilePath) Then
                ProcessFile strFilePath
            Else
                WScript.Echo "Error: File write timeout or access denied: " & strFilePath
            End If
        End If
        
        ' メモリ管理: ループ内でのオブジェクト解放
        Set objFileInstance = Nothing
    Loop

    ' クリーンアップ (到達不能だが、構造上の整合性のために記述)
    Cleanup
End Sub

' **
' Function: WaitForFileReady
' Purpose: ファイルサイズが変化しなくなるまで待機し、ロック状態を脱出させる
' **
Function WaitForFileReady(strPath)
    Dim objFile, lngLastSize, lngCurrentSize, i, bReady
    bReady = False
    lngLastSize = -1
    
    For i = 1 To MAX_RETRIES
        On Error Resume Next
        Set objFile = objFSO.GetFile(strPath)
        
        If Err.Number = 0 Then
            lngCurrentSize = objFile.Size
            
            ' サイズが前回と同じ、かつ0より大きい(または特定の条件)場合に完了とみなす
            ' 実際の業務要件に応じて、Size > 0 などの条件を追加すること
            If lngCurrentSize > 0 And lngCurrentSize = lngLastSize Then
                bReady = True
                Exit For
            End If
            
            lngLastSize = lngCurrentSize
            Err.Clear
        Else
            ' ファイルがまだロックされている、または存在しない場合はスキップ
            Err.Clear
        End If
        On Error GoTo 0
        
        WScript.Sleep RETRY_INTERVAL
    Next
    
    Set objFile = Nothing
    WaitForFileReady = bReady
End Function

' **
' Sub: ProcessFile
' Purpose: ファイルが完全に書き込まれた後に実行されるビジネスロジック
' **
Sub ProcessFile(strPath)
    WScript.Echo "Success: File is ready for processing -> " & strPath
    ' ここに後続の処理(ファイル移動、DB登録、API呼び出し等)を記述
    ' 例: objFSO.MoveFile strPath, "C:\\Temp\\Archive\\"
End Sub

' **
' Sub: Cleanup
' Purpose: COMオブジェクトの明示的解放
' **
Sub Cleanup()
    On Error Resume Next
    Set objQuery = Nothing
    Set objWMI = Nothing
    Set objFSO = Nothing
    WScript.Echo "Cleanup completed."
End Sub

4. アーキテクトの視点:パフォーマンスと堅牢性の深掘り

WMIクエリの最適化

`WITHIN 1` という値は、ポーリング間隔(秒)を意味する。この値を小さくすれば検知速度は上がるが、CPU使用率を増大させる。監視対象の重要度に応じて、`1` から `5` の間で調整するのが定石である。また、クエリ内で `Drive` と `Path` を明示的に指定することは、WMIエンジンが全ドライブを走査するのを防ぐための必須事項である。

オブジェクトのライフサイクル管理

VBScriptにおける `Set obj = Nothing` は、単なる作法ではない。特に `WMI` や `FileSystemObject` のようなCOMオブジェクトは、参照カウントが適切に管理されないと、スクリプトが終了してもプロセス(`wscript.exe`)がゾンビ化したり、ファイルハンドルを掴み続けたりする原因となる。ループ内での `Set objFileInstance = Nothing` は、メモリリークを防ぐための鉄則である。

エラーハンドリングの哲学

`On Error Resume Next` は、劇薬である。これをグローバルに適用するのは三流のすることだ。本コードのように、「ファイルの状態を確認する一時的な試行」という、エラーが発生することが前提の局所的なブロックに対してのみ適用し、それ以外はエラーを伝播させる設計にすべきである。

結論

ファイル監視において、イベントは「きっかけ」に過ぎない。真のエンジニアリングは、その「きっかけ」の後に発生するOSレベルの非同期的な挙動を、いかに制御し、同期させるかという点に集約される。

このロジックを実装することで、あなたのシステムは「たまたま動いている」状態から、「極めて高い信頼性を持って稼働する」状態へと進化するだろう。

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