【テクニカル・上級編】【複数フォルダ差分同期】FileSystemObject とファイルサイズ・更新日時比較による軽量バックアップエンジン – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

VBScriptを掌握する極限の知見:FileSystemObjectによる超高速・差分同期バックアップエンジンの構築

レガシーシステムの保全、あるいは閉域網におけるエッジ端末のデータ退避において、サードパーティ製の同期ツールを持ち込めないジレンマは、インフラエンジニアであれば誰もが一度は直面する壁だ。

「重いインストーラは入れられない」「PowerShellの実行ポリシー(ExecutionPolicy)すら厳格に制限されている」。
そんな極限の環境下で、Windowsが標準搭載するWSH (Windows Script Host) と VBScript は、今なお最強のカードとして機能する。

今回は、GUIのオーバーヘッドを極限まで排除し、`Scripting.FileSystemObject` (FSO) のメモリ管理の急所と、ファイルサイズ・更新日時の厳密な比較ロジックを組み合わせた、「真に実用的な差分同期バックアップエンジン」の全貌を解説する。

—

1. VBScriptにおけるパフォーマンスの急所:FSOのライフサイクル管理

素人が書いたVBScriptが遅い理由は決まっている。それは「無駄なオブジェクトの生成と解放」と「ファイルI/Oの乱発」だ。

FSOはCOMコンポーネントであり、スクリプトエンジンとCOMの境界を跨ぐたびにオーバーヘッドが発生する。特に数万ファイルの階層を再帰的に走査(リカーシブ・スキャン)する場合、インスタンスの生成・破棄のコストや、参照カウンタのリークは致命的なメモリ肥大化を招く。

究極のメモリ最適化原則

1. FSOインスタンスはスクリプト全体でただ1つをグローバル(あるいは適切にスコープを切った変数)で使い回す。
2. 再帰呼び出しを行う関数内では、ローカル変数のメモリフットプリントを最小限にし、不要になったオブジェクト参照は即座に `Nothing` を代入してCOMの参照カウンタをデクリメントする。
3. 文字列連結 (`&`) の多用によるヒープ領域の断片化を防ぐため、パス構築には `BuildPath` メソッドを厳格に使用する。

—

2. アーキテクチャ設計:なぜ「サイズ」と「更新日時」なのか?

ファイル同期における変更検知のアルゴリズムにはいくつかの選択肢がある。

  • ハッシュ値(SHA-256等)比較:【極めて低速】(全ファイルのI/Oが発生するため論外)
  • タイムスタンプ(更新日時)のみ:【危険】(タイムゾーンの差異や、コピー時のファイルシステム仕様により誤検知・未検知が起きる)
  • 【採用】ファイルサイズ + 更新日時(UTC換算または厳密なシリアル値)のハイブリッド比較

Windowsのファイルシステム(NTFS)において、`DateLastModified` プロパティは内部的に浮動小数点数(VT_DATE)として保持される。これをそのまま比較すると、環境依存のミリ秒単位の丸め誤差で無限ループや無駄なコピーが発生する。
本エンジンでは、この誤差を完全に吸収しつつ、真に変更されたファイルだけをピンポイントで炙り出すロジックを実装する。

—

3. 実装コード:超高速・差分同期バックアップエンジン

以下のコードを `SyncEngine.vbs` として保存し、コマンドプロンプトから `cscript //nologo SyncEngine.vbs` で実行せよ。UTF-8(BOM付き)またはANSI(Shift_JIS)での保存を推奨する。

‘ ==============================================================================
‘ Script Name : UltraSyncEngine.vbs
‘ Description : FileSystemObject based high-performance differential backup engine.
‘ Author : Chief Architect
‘ ==============================================================================
Option Explicit

Const LOG_LEVEL_INFO = “[INFO] ”
Const LOG_LEVEL_WARN = “[WARN] ”
Const LOG_LEVEL_ERROR = “[ERROR]”

‘ — 設定エリア —
Dim strSourceDir, strDestDir
strSourceDir = “C:\SourceData” ‘ 同期元フォルダ(末尾のバックスラッシュ不要)
strDestDir = “D:\BackupTarget” ‘ 同期先フォルダ

‘ 実行時間の計測開始
Dim dblStartTime
dblStartTime = Timer

WScript.Echo LOG_LEVEL_INFO & “同期処理を開始します…”
WScript.Echo ” SRC: ” & strSourceDir
WScript.Echo ” DST: ” & strDestDir

‘ グローバルFSOの生成(スクリプトライフサイクルを通じて単一インスタンスを維持)
Dim objFSO
Set objFSO = WScript.CreateObject(“Scripting.FileSystemObject”)

‘ 同期元存在チェック
If Not objFSO.FolderExists(strSourceDir) Then
WScript.Echo LOG_LEVEL_ERROR & “同期元フォルダが存在しません: ” & strSourceDir
WScript.Quit 1
End If

‘ 同期先が存在しない場合は自動作成
If Not objFSO.FolderExists(strDestDir) Then
Call CreateTargetFolderRecursive(objFSO, strDestDir)
End If

‘ 統計カウンタ
Dim lngCopiedCount, lngSkippedCount
lngCopiedCount = 0
lngSkippedCount = 0

‘ 同期メイン処理の実行
Call SyncFolders(objFSO, strSourceDir, strDestDir, strSourceDir)

‘ クリーンアップ
Set objFSO = Nothing

Dim dblElapsedTime
dblElapsedTime = Timer – dblStartTime

WScript.Echo “————————————————–”
WScript.Echo LOG_LEVEL_INFO & “同期処理が完了しました。”
WScript.Echo ” コピーされたファイル数 : ” & lngCopiedCount
WScript.Echo ” スキップされたファイル数 : ” & lngSkippedCount
WScript.Echo ” 処理時間 : ” & FormatNumber(dblElapsedTime, 2) & ” 秒”
WScript.Quit 0

‘ ==============================================================================
‘ フォルダ階層を再帰的に走査し、差分を検知してコピーする中核関数
‘ ==============================================================================
Sub SyncFolders(fso, currentSrcPath, currentDstPath, baseSrcPath)
Dim objSrcFolder, objSubFolder, objFile
Dim targetSubDirPath, targetFilePath

Set objSrcFolder = fso.GetFolder(currentSrcPath)

‘ 1. 現在の階層にあるファイルの比較・コピー
For Each objFile In objSrcFolder.Files
‘ 同期先での対応するファイルパスを生成
‘ 例: baseSrcPathからの相対パスを算出してDestinationに結合
Dim relativePath
relativePath = Mid(objFile.Path, Len(baseSrcPath) + 2)
targetFilePath = fso.BuildPath(baseSrcPath, relativePath) ‘ 一旦ベースで組み立ててから置換
‘ 安全にターゲットパスを再構築
targetFilePath = fso.BuildPath(currentDstPath, objFile.Name)

If fso.FileExists(targetFilePath) Then
Dim objDstFile
Set objDstFile = fso.GetFile(targetFilePath)

‘ 【極限の比較ロジック】
‘ 条件A: ファイルサイズが異なる
‘ 条件B: 更新日時(DateLastModified)が異なる(Datediffで秒単位まで正確に比較)
If (objFile.Size <> objDstFile.Size) Or _
(DateDiff(“s”, objFile.DateLastModified, objDstFile.DateLastModified) <> 0) Then

objFile.Copy targetFilePath, True ‘ 上書きコピー
lngCopiedCount = lngCopiedCount + 1
Else
lngSkippedCount = lngSkippedCount + 1
End If
Set objDstFile = Nothing
Else
‘ 同期先にファイルが存在しない場合は新規コピー
objFile.Copy targetFilePath, True
lngCopiedCount = lngCopiedCount + 1
End If
Next

‘ 2. サブフォルダの再帰走査
For Each objSubFolder In objSrcFolder.SubFolders
targetSubDirPath = fso.BuildPath(currentDstPath, objSubFolder.Name)

If Not fso.FolderExists(targetSubDirPath) Then
fso.CreateFolder(targetSubDirPath)
End If

‘ 再帰呼び出し
Call SyncFolders(fso, objSubFolder.Path, targetSubDirPath, baseSrcPath)
Next

Set objSrcFolder = Nothing
End Sub

‘ ==============================================================================
‘ ターゲットフォルダの階層を一括作成するヘルパー関数
‘ ==============================================================================
Sub CreateTargetFolderRecursive(fso, targetPath)
Dim parentPath
parentPath = fso.GetParentFolderName(targetPath)

If Not fso.FolderExists(parentPath) And parentPath <> “” Then
Call CreateTargetFolderRecursive(fso, parentPath)
End If

If Not fso.FolderExists(targetPath) Then
fso.CreateFolder(targetPath)
End If
End Sub

—

コードを熟読した者であれば気づいただろう。このスクリプトには無駄なループやメモリリークの種が一切排除されている。

コードの深層解説:プロが仕込んだ技術的ポイント

1. `DateDiff(“s”, …)` による精度の担保
VBScriptの `Date` 型を単純に `>` や `<` で比較すると、浮動小数点の内部表現の誤差に足元をすくわれることがある。`DateDiff` 関数を用いて「秒("s")」単位の差分として評価することで、OSやファイルシステム間の微小なタイムスタンプのズレによる誤動作を完全に封殺している。 2. パス構築に `BuildPath` を使う理由
文字列の結合演算子 `&` でパスを繋ごうとすると、開発者がバックスラッシュ(`\`)の付与漏れを起こし、`C:\DataFolderfile.txt` のような致命的なパス崩壊を引き起こす。FSOの `BuildPath` は、セパレータの有無を自動的に判定して安全に結合するプロの必須作法である。
3. カスケード(連鎖的)なフォルダ作成
同期先のツリーが深く、一気にサブフォルダ層を作りたい場合、`CreateTargetFolderRecursive` 関数が再帰的に親階層を遡って安全にフォルダを生成する。これにより、あらかじめ同期先の器を用意しておく必要すらなくなる。

—

4. チーフアーキテクトからの実践的助言:運用時の注意点

  • エラーハンドリングの拡張

企業内ネットワーク(NAS等)を同期先にする場合、パケットロスやセッションタイムアウトによる `Permission Denied` (エラー70) や `Path not found` (エラー76) が発生し得る。本番運用に組み込む際は、`On Error Resume Next` を適切にスコープを絞って配置し、エラー発生時はログファイル(TextStream)にタイムスタンプ付きで吐き出す例外処理のラッパーを追加することを強く推奨する。

  • 削除検知(ミラーリング)について

今回の設計は「追加・更新(Incremental)」に特化している。もし同期元で削除されたファイルを同期先からも消去する「ミラーリング(Mirroring)」が必要な場合は、逆向きの走査ロジック(同期先にあって同期元にないファイルの削除)を追加すればよい。ただし、誤削除のリスクが跳ね上がるため、自動削除の導入は慎重なテストを経て行うべきだ。

時代はクラウドでありコンテナかもしれないが、現場の泥臭い自動化を支えているのは、こうした枯れた技術の極限まで最適化されたコードベースである。
無駄を削ぎ落としたVBScriptのエンジンは、今日も静かに、そして確実に、企業の重要データを守り続けている。

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