「イベント」の罠を越えて: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レベルの非同期的な挙動を、いかに制御し、同期させるかという点に集約される。
このロジックを実装することで、あなたのシステムは「たまたま動いている」状態から、「極めて高い信頼性を持って稼働する」状態へと進化するだろう。
