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

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

「ファイル作成」を検知しただけで満足していないか?――書き込みロックを回避する、極限のファイル監視設計論

業務自動化の現場において、多くのエンジニアが陥る「初歩的かつ致命的な罠」がある。

それは、「ファイルが作成された瞬間に、そのファイルを操作しようとする」ことだ。

「ファイルが生成された」というイベントをトリガーに、後続の処理(ファイル移動、DBへのインポート、解析など)を開始する。一見、論理的に正しい。しかし、実務環境では、その瞬間に必ずと言っていいほど `Permission denied` や `File in use` といった実行時エラーが牙をむく。

なぜか? 答えは単純だ。「ファイルが作成されたこと」と「ファイルの書き込みが完了したこと」は、全く別次元の事象だからだ。

本稿では、WMI(Windows Management Instrumentation)によるイベント駆動型の検知と、FileSystemObjectによる状態検証を組み合わせた、プロフェッショナルが実戦で用いる「堅牢なファイル監視ロジック」を伝授する。

1. なぜ、あなたの監視スクリプトは失敗するのか

ファイルが作成されるプロセスを、OSのレイヤーで分解してみよう。

1. ファイル・ハンドル生成: OSがファイルの実体(エントリー)を作成する。
2. データ・ストリーム書き込み: アプリケーションがデータを書き込み続ける。(←ここが問題だ)
3. ファイル・クローズ: アプリケーションが書き込みを終え、ハンドルを解放する。

一般的な `FileSystemObject` のループや、単純な WMI の `__InstanceCreationEvent` だけでは、ステップ1の瞬間を検知してしまう。この時、ファイルはまだ「書き込み中」であり、他のプロセスによって排他的ロックがかかっている。

この状態でファイルを開こうとすれば、スクリプトはクラッシュするか、不完全な(中身が空、あるいは途切れた)データを処理することになる。これは「自動化」ではなく、単なる「エラーの自動生成器」だ。

2. 解決策:二段構えのアーキテクチャ

堅牢な監視を実現するためには、以下の二段構えの設計が必要不可欠である。

第1層:WMIによる「イベント駆動型」の検知

ポーリング(一定間隔でフォルダを巡回してファイルを探す手法)は、CPUリソースを無駄に消費し、検知の遅延も発生する。プロフェッショナルは、OSのイベント通知を利用する WMI (Windows Management Instrumentation) を使い、ファイルが「現れた」瞬間を低負荷でキャッチする。

第2層:FileSystemObjectによる「安定性検証」

ファイルが検知された後、即座に処理へ移るのではなく、「ファイルサイズの変化が止まったか」および「排他的に開けるか」を確認する待機ロジックを挟み込む。これが「書き込み完了」を担保する防波堤となる。

3. 実装:プロダクション・グレードの監視スクリプト

以下に、そのまま実務に投入できるレベルの VBScript コードを示す。単なるコピペで終わらせず、コード内の「設計意図」を読み解いてほしい。

' ==============================================================================
' Script Name: RobustFileWatcher.vbs
' Description: WMIとFSOを組み合わせ、ファイル書き込み完了を確実に検知する
' Author: Chief Architect
' ==============================================================================
Option Explicit

' --- 設定領域 ---
Const WATCH_DIR      = "C:\\Work\\TargetFolder\\" ' 監視対象フォルダ (WMI用に\\を使用)
Const CHECK_INTERVAL = 1000                       ' サイズ確認の間隔 (ms)
Const MAX_RETRIES    = 30                         ' 最大待機回数
Const LOG_FILE       = "C:\\Work\\monitor_log.txt"

' --- オブジェクト初期化 ---
Dim objFSO, objWMI, objEventSink, objWatcher
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objWMI = GetObject("winmgmts:\\.\root\cimv2")

' 監視対象のフルパス(FSO用)
Dim strWatchPath
strWatchPath = Replace(WATCH_DIR, "\\", "\")

WScript.Echo "監視を開始しました: " & strWatchPath

' ------------------------------------------------------------------------------
' メインプロセス:WMIイベント待機
' ------------------------------------------------------------------------------
Sub StartMonitoring()
    Dim strQuery, objEvents, objFile
    
    ' WMI Query: 指定ディレクトリ内でのファイル作成イベントを監視
    ' 注意: WMIのパス形式は '\\' を使用する必要がある
    strQuery = "SELECT * FROM __InstanceCreationEvent WITHIN 1 " & _
               "WHERE TargetInstance ISA 'CIM_DataFile' " & _
               "AND TargetInstance.Drive = '" & Left(strWatchPath, 2) & "' " & _
               "AND TargetInstance.Path = '" & Replace(strWatchPath, "\", "\\") & "' "

    ' イベントの取得
    Set objEvents = objWMI.ExecNotificationQuery(strQuery)

    Do
        ' イベントが発生するまでここでブロック(待機)
        Set objFile = objEvents.NextEvent
        
        Dim strFilePath
        strFilePath = objFile.TargetInstance.Name ' フルパスを取得
        
        WScript.Echo "【検知】ファイル作成を確認: " & strFilePath
        
        ' ファイルの安定性を検証
        If WaitForFileReady(strFilePath) Then
            WScript.Echo "【成功】書き込み完了を確認。後続処理を開始します。"
            ProcessFile strFilePath
        Else
            WScript.Echo "【警告】ファイルがロックされたまま、またはタイムアウトしました。"
        End If
    Loop
End Sub

' ------------------------------------------------------------------------------
' 関数:ファイルの安定性を検証する (Size Stability Check)
' ------------------------------------------------------------------------------
Function WaitForFileReady(strFilePath)
    Dim objFile, lngLastSize, lngCurrentSize, i, bIsReady
    bIsReady = False
    lngLastSize = -1
    
    For i = 1 To MAX_RETRIES
        On Error Resume Next
        Set objFile = objFSO.GetFile(strFilePath)
        
        If Err.Number = 0 Then
            lngCurrentSize = objFile.Size
            
            ' 1. サイズに変化がないか
            ' 2. かつ、ファイルを開けるか(排他ロックの確認)
            If lngCurrentSize = lngLastSize And lngCurrentSize > 0 Then
                ' 最終確認:実際に開けるかテスト
                If TestFileAccess(strFilePath) Then
                    bIsReady = True
                    Exit For
                End If
            End If
            
            lngLastSize = lngCurrentSize
            Err.Clear
        Else
            ' ファイルがまだ見えない、あるいはアクセス不可の場合
            Err.Clear
        End If
        On Error GoTo 0
        
        WScript.Sleep CHECK_INTERVAL
    Next
    
    WaitForFileReady = bIsReady
End Function

' ------------------------------------------------------------------------------
' 関数:ファイルへのアクセス権をテストする
' ------------------------------------------------------------------------------
Function TestFileAccess(strFilePath)
    Dim objStream
    On Error Resume Next
    ' 読み取りモードで開けるか試行
    Set objStream = objFSO.OpenTextFile(strFilePath, 1)
    If Err.Number = 0 Then
        objStream.Close
        TestFileAccess = True
    Else
        TestFileAccess = False
    End If
    On Error GoTo 0
End Function

' ------------------------------------------------------------------------------
' 関数:後続の業務ロジック(ここに実際の処理を書く)
' ------------------------------------------------------------------------------
Sub ProcessFile(strFilePath)
    ' 例: ファイルを別の場所に移動する、あるいはDBに登録するなど
    ' ここではログへの記録のみ
    Dim objLog
    Set objLog = objFSO.OpenTextFile(LOG_FILE, 8, True)
    objLog.WriteLine Now & " - Processed: " & strFilePath
    objLog.Close
End Sub

' 実行開始
StartMonitoring

4. アーキテクトの視点:なぜこのコードは「強い」のか

このコードが、単なる「動くコード」ではなく「プロダクション・コード」である理由を解説する。

① 「サイズ変化の停止」と「アクセス権」の二重チェック

多くのエンジニアは、ファイルサイズの比較だけで満足する。しかし、ファイルサイズが止まった直後でも、OSがファイルハンドルを閉じるまでの僅かなタイムラグ(ミリ秒単位)が存在する。
私の設計では、`TestFileAccess` 関数を導入し、「実際に開けること」を最終的な合格条件としている。これが、エラー率を限りなくゼロに近づける鍵だ。

② WMIによるリソース効率の最適化

`Do…Loop` 内で `FSO.FileExists` を回し続けるポーリング方式は、監視対象のファイル数が増えると劇的にパフォーマンスを悪化させる。WMIの `ExecNotificationQuery` を使うことで、OSが「ファイルができたぞ」と教えてくれるまで、スクリプトは極めて低いCPU負荷で待機できる。

③ エラーハンドリングの局所化

`On Error Resume Next` をスクリプトの冒頭に書くようなことは、プロとして絶対に避けるべきだ。エラーハンドリングは、`WaitForFileReady` 内のように、「エラーが想定される箇所」にのみ局所的に適用する。これにより、予期せぬ致命的なバグ(構文ミスやメモリ不足など)を隠蔽することなく、制御可能なエラー(ファイルロック)だけをハンドリングできる。

5. 運用に向けたアドバイス

このロジックを実務に組み込む際は、以下の点に留意せよ。

  • タイムアウト設計: `MAX_RETRIES` は業務要件に合わせて調整すること。巨大なファイル(数GB単位)を扱う場合、書き込み完了まで数分かかるケースを想定しなければならない。
  • 監視対象のパス形式: WMIのクエリにおいて、パスのバックスラッシュ(`\`)はエスケープが必要になる。ここを間違えると、スクリプトは沈黙し続ける。
  • ログの重要性: 自動化ツールは「動いていること」よりも「なぜ止まったか」が重要だ。必ずエラーログを出力する仕組みを持たせておくこと。

結論

「ファイルが作成された」という事象は、プロセスの始まりに過ぎない。
真に堅牢なシステムを構築したいのであれば、その裏側にあるOSの挙動、ファイルシステムのライフサイクル、そしてリソースの競合を理解しなければならない。

この知見を武器に、単なる「スクリプト屋」から、信頼される「自動化エンジニア」へと昇華してほしい。

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