【入門編】【ファイルシステム変化の擬似イベント検知】フォルダ内状態のハッシュ比較による差分検出エンジンの作成 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

こんにちは!VBScriptの世界へようこそ。
マクロの記録ボタンを押すだけの世界から一歩踏み出し、自らの手でWindowsを意のままに操る自動化の扉を開こうとしているあなたを、私は心から歓迎します。

さて、業務の現場で「特定のフォルダにファイルが追加されたら、自動で処理を走らせたい」という要件に直面することはよくあります。
ここで多くの人が思い浮かべるのが、OSの裏側で常に監視を続ける「WMI(Windows Management Instrumentation)イベント」です。しかし、WMIの常時監視は、時としてCPUに重い負荷をかけたり、ネットワークドライブ上ではうまく動かなかったりと、実務では悩ましいトラブルの種になります。

そこで今回は、「軽量で、確実で、しかも泥臭く強い」、FileSystemObject(FSO)を使ったファイルシステムの差分検出エンジンの作り方を伝授しましょう。
ここをクリアすれば、VBScriptの基礎だけでなく、メモリの概念やロジックの組み立て方まで、一気にプログラミングの本質が見えてきますよ。

1. なぜ「WMI」ではなく「ハッシュ比較」なのか?

ファイルの変更を検知するアプローチには、大きく分けて2つあります。

1. イベント駆動型(WMI監視):ファイルシステムの変化をOSに通知してもらう。一見スマートだが、環境によって不安定でリソースを食う。
2. ポーリング型(定期的なスナップショット比較):一定時間ごとにフォルダ内をスキャンし、前回の状態と比較する。

今回構築するのは2番目の「ポーリング型・差分検出エンジン」です。
「定期的にスキャンするなんて非効率では?」と思いましたか? いいえ、数千ファイル程度なら、VBScriptのFSOとメモリ上のDictionaryオブジェクトを使えば、一瞬(コンマ数秒)でスキャンが完了します。余計な常駐プロセスを立てず、実行したい時だけ安全に動く。これが現場で最も信頼されるエンジニアの知恵です。

2. 差分検出エンジンの全体像と仕組み

仕組みはとてもシンプルです。

1. ベースラインの作成:監視対象フォルダの中身を読み込み、「ファイルパス」をキー、「更新日時(タイムスタンプ)」を値として、メモリ上の連想配列(`Scripting.Dictionary`)に記録します。
2. 比較(スキャン):一定時間後、再度フォルダをスキャンし、現在の状態と前回の状態を見比べます。

  • 作成 (Created):前回なかったパスが、今回存在している。
  • 削除 (Deleted):前回あったパスが、今回消えている。
  • 改変 (Modified):パスは同じだが、更新日時が変わっている。

これを視覚的に表すと、次のようになります。

[前回メモリ上の記録] [今回のフォルダ状態]
fileA.txt : 10:00 ──────────> fileA.txt : 10:00 (変化なし)
fileB.txt : 10:00 ──────────> fileB.txt : 12:00 (★改変を検知!)
(なし) ──────────> fileC.txt : 11:00 (★新規作成を検知!)
fileD.txt : 10:00 ──────────> (消滅) (★削除を検知!)

3. 実装コード:フォルダ監視差分エンジン

以下のコードをメモ帳に貼り付け、拡張子を `.vbs` (例: `file_watcher.vbs`)として保存してください。
デスクトップなどに「`TargetFolder`」というテスト用フォルダを作り、パスを書き換えて実行してみましょう。

‘ ==============================================================================
‘ フォルダ内状態のハッシュ比較による差分検出エンジン
‘ ==============================================================================
Option Explicit

Dim fso, dictOld, dictNew
Dim targetPath, intervalSeconds, isFirstRun
Dim key, files, file

‘ — 1. 設定エリア —
targetPath = “C:\Cals\TargetFolder” ‘ 監視対象フォルダのパスを指定してください
intervalSeconds = 5 ‘ チェック間隔(秒)

‘ オブジェクトの生成
Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ 監視対象フォルダの存在チェック
If Not fso.FolderExists(targetPath) Then
MsgBox “監視対象フォルダが存在しません:” & vbCrLf & targetPath, vbCritical, “エラー”
WScript.Quit
End If

WScript.Echo “=== 差分検出エンジンを起動します ===” & vbCrLf & “対象: ” & targetPath

‘ 初回スキャンフラグ
isFirstRun = True

‘ — 2. メインループ(ポーリング) —
Do
‘ 今回の状態を格納するDictionaryを生成
Set dictNew = CreateObject(“Scripting.Dictionary”)

‘ フォルダ内スキャン実行
Call ScanFolder(fso.GetFolder(targetPath), dictNew)

If isFirstRun = Then
‘ 初回はベースライン(基準)を作成するだけ
Set dictOld = dictNew
isFirstRun = False
WScript.Echo “[INFO] 初期スキャン完了。変更の監視を開始します…”
Else
‘ 3. 差分の比較・判定
Call DetectChanges(dictOld, dictNew)

‘ 次回比較のために状態を更新
Set dictOld = dictNew
End If

‘ 指定秒数だけ待機(WScript.Sleepはミリ秒単位)
WScript.Sleep intervalSeconds 1000
Loop

‘ ==============================================================================
‘ 補助関数: 再帰的にフォルダ内をスキャンし、Dictionaryに格納する
‘ ==============================================================================
Sub ScanFolder(folder, dict)
Dim subFolder, file

‘ ファイルの走査
For Each file in folder.Files
‘ キー: ファイルのフルパス, 値: 最終更新日時
If Not dict.Exists(file.Path) Then
dict.Add file.Path, file.DateLastModified
End If
Next

‘ サブフォルダも監視対象に含める場合(再帰処理)
For Each subFolder in folder.SubFolders
ScanFolder subFolder, dict
Next
End Sub

‘ ==============================================================================
‘ 補助関数: 新旧のDictionaryを比較し、変更イベントを出力する
‘ ==============================================================================
Sub DetectChanges(oldDict, newDict)
Dim k

‘ 1. 「削除」および「改変」の検知
For Each k in oldDict.Keys
If Not newDict.Exists(k) Then
WScript.Echo “[削除検出] ” & k
Else
If oldDict(k) <> newDict(k) Then
WScript.Echo “[改変検出] ” & k & ” (更新時刻: ” & newDict(k) & “)”
End If
End If
Next

‘ 2. 「作成」の検知
For Each k in newDict.Keys
If Not oldDict.Exists(k) Then
WScript.Echo “[新規作成] ” & k
End If
Next
End Sub

4. コードの深掘り解説:ここがVBScriptのキモ!

プログラミング初学者のあなたが、「おっ」と躓きやすいポイントや、エンジニアとして知っておくべき重要概念を解説します。

① `Scripting.Dictionary`(連想配列)の魔力

VBScriptの標準配列はサイズ変更が面倒で扱いづらいですが、`Scripting.Dictionary` を使えば、ExcelのVBAでおなじみの「キーと値のペア(Dictionary)」が使えます。

  • キー (`Key`):ファイルのフルパス(重複しない一意のもの)
  • 値 (`Value`):ファイルの最終更新日時 (`DateLastModified`)

この組み合わせにより、検索コスト(計算量)を劇的に抑え、一瞬で「あれ、このファイル変わったっけ?」を判定できるようになります。

② 無限ループと `WScript.Sleep`

`Do … Loop` の中に `WScript.Sleep intervalSeconds 1000` を入れています。
これを忘れてループを全速力で回すと、CPU使用率が100%に張り付いてパソコンが爆音を上げます(ビジネスクラスのPCであっても容赦ありません)。
「定期実行系」のスクリプトを書くときは、必ず適切なスリープ(休止)を挟むのがエンジニアの鉄則です。

③ 再帰呼び出し(`ScanFolder` サブルーチン)

今回のコードでは、サブフォルダの中にあるファイルまで漏らなくチェックするために、関数の中で自分自身を呼び出す「再帰処理」を使っています。
「フォルダの中にフォルダがあったら、また中身を覗きに行く」というネスト構造を綺麗にコード化できるため、ファイル操作系スクリプトでは必須のテクニックです。

5. 陥りやすい罠と現場の知見

実際にこのスクリプトを業務で運用しようとしたとき、いくつかの「罠」にハマることがあります。プロの知見として先回りしてシェアしておきましょう。

1. ファイルがロックされていてアクセスエラーになる

  • ExcelやWordを開いている最中、あるいは巨大なファイルがコピー中のときにFSOでアクセスすると、「権限がありません (Permission denied)」のエラーでスクリプトが異常終了することがあります。
  • 本番運用では、エラーハンドリング(`On Error Resume Next` と `Err.Number` のチェック)を適切に組み込む耐障害性が求められます。

2. タイムスタンプの粒度

  • Windowsのファイルシステムやネットワークストレージの種類によっては、更新日時の保持精度が「1秒単位」または「2秒単位」である場合があります。人間の手作業であれば問題ありませんが、超高速なバッチ処理から書き込まれたファイルを取りこぼす可能性がゼロではないことを頭に入れておきましょう。

おわりに:ここをクリアすれば、あなたはもう初心者ではない!

お疲れ様でした!
今回作成した「ハッシュ比較による差分検出エンジン」は、単なるテキストのコピペを超えて、「状態をメモリに保持し、時間を置いて比較する」というアルゴリズムの基本が詰まっています。

このロジックをベースに、「検知したらメールを飛ばす」「指定のフォルダに自動バックアップする」といった処理を組み合わせれば、立派な社内業務自動化システムの完成です。

「マクロの記録」という安全な砂場から飛び出し、OSの仕組みをダイレクトにハックするVBScriptの世界。ここをクリアしたあなたなら、どんな自動化の壁も軽々と越えていけるはずです。
明日からの開発ライフを、ぜひ楽しんでくださいね!

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