【複数フォルダ差分同期】FileSystemObject とファイルサイズ・更新日時比較による軽量バックアップエンジン
開発現場において、「専用のバックアップツールを入れる権限がない」「タスクスケジューラでサクッと動く、依存関係のない軽量な同期スクリプトが欲しい」という状況に直面したことはないだろうか。
重厚長大なサードパーティ製ツールは、環境構築のコストやセキュリティの審査で頓挫することが多い。そんな時、Windows標準装備である WSH(Windows Script Host) と VBScript は、追加のランタイムを一切必要としない最強の武器となる。
今回は、単なる丸ごとコピー(Robocopyの単純なラップなどではなく)ではなく、VBScriptの `Scripting.FileSystemObject` を駆使し、ファイルサイズと更新日時の差分を厳密に判定して「真に必要なファイルだけを転送する」、プロダクション品質の軽量バックアップエンジンを解説する。
—
なぜ「丸ごとコピー」ではなく「差分同期」でなければならないのか
実務で数万ファイル、数十GB規模のフォルダを扱う場合、毎回すべてのファイルをコピーするのはI/Oの無駄であり、ネットワークやストレージの寿命を確実に縮める。
しかし、VBScriptで愚直に全ファイルを比較しようとすると、以下の罠にハマる。
1. タイムスタンプの精度ズック:NTFSとFAT系ファイルシステム、あるいはネットワーク越し(SMB)のタイムスタンプ比較では、ミリ秒単位の丸め誤差が生じ、常に「更新あり」と誤判定される。
2. メモリリークとオブジェクトの解放漏れ:FSO(FileSystemObject)のインスタンスやコレクションを再帰処理の中でループさせると、VBScriptの貧弱なガベージコレクタではメモリが解放しきれず、プロセスが異常終了する。
3. パス長制限(MAX_PATH)の壁:Windowsの標準APIの制約により、260文字を超えるパス構造を持つ深い階層でスクリプトがクラッシュする。
これらをロジカルに解決し、現場のインフラに耐えうる堅牢な設計コードを以下に提示する。
—
プロダクション品質・差分同期エンジン(VBScript)
以下のコードをテキストエディタに貼り付け、拡張子を `.vbs` (例: `sync_engine.vbs`)として保存してほしい。文字コードは ANSI(Shift_JIS) または BOM付きUTF-8 を推奨する。
‘ ==============================================================================
‘ Script Name : UltraLightSyncEngine.vbs
‘ Description : FileSystemObjectによる高速・軽量フォルダ差分同期エンジン
‘ Author : チーフアーキテクト
‘ ==============================================================================
Option Explicit
‘ — 設定エリア —
Dim strSourceDir, strDestDir, boolDeleteMode
strSourceDir = “C:\Work\SourceFolder” ‘ コピー元(マスター)
strDestDir = “D:\Backup\DestinationFolder” ‘ コピー先(レプリカ)
boolDeleteMode = False ‘ Trueにすると同期元にないファイルをコピー先から削除
‘ 実行開始
Main
Sub Main()
Dim objFSO, startTime
Set objFSO = CreateObject(“Scripting.FileSystemObject”)
startTime = Now
WScript.Echo “=== 同期処理を開始します ===”
WScript.Echo “ソース: ” & strSourceDir
WScript.Echo “ターゲット: ” & strDestDir
‘ 存在チェック
If Not objFSO.FolderExists(strSourceDir) Then
WScript.Echo “[ERROR] コピー元フォルダが存在しません: ” & strSourceDir
WScript.Quit 1
End If
If Not objFSO.FolderExists(strDestDir) Then
‘ コピー先が存在しない場合は自動作成
CreateAllFolders objFSO, strDestDir
End If
‘ 再帰的同期処理の実行
SyncFolders objFSO, objFSO.GetFolder(strSourceDir), objFSO.GetFolder(strDestDir)
If boolDeleteMode Then
‘ ゴミ掃除(コピー元にないファイルをコピー先から削除)
CleanUpDestFolder objFSO, objFSO.GetFolder(strDestDir), objFSO.GetFolder(strSourceDir)
End If
Set objFSO = Nothing
WScript.Echo “=== 同期処理が完了しました (所要時間: ” & DateDiff(“s”, startTime, Now) & “秒) ===”
End Sub
‘ — フォルダとファイルの再帰同期コアロジック —
Sub SyncFolders(objFSO, srcFolder, dstFolder)
Dim subFolder, srcFile, dstFilePath
Dim relPath, targetFolder
‘ 1. ファイルの比較とコピー
For Each srcFile In srcFolder.Files
dstFilePath = dstFolder.Path & “\” & srcFile.Name
If objFSO.FileExists(dstFilePath) Then
Dim dstFile
Set dstFile = objFSO.GetFile(dstFilePath)
‘ 【重要】サイズまたは更新日時(秒単位で比較)が異なる場合のみ上書き
If (srcFile.Size <> dstFile.Size) Or (Abs(DateDiff(“s”, srcFile.DateLastModified, dstFile.DateLastModified)) > 1) Then
srcFile.Copy dstFilePath, True
WScript.Echo “[UPDATE] ” & srcFile.Name
End If
Set dstFile = Nothing
Else
‘ ファイルが存在しない場合は新規コピー
srcFile.Copy dstFilePath, True
WScript.Echo “[CREATE] ” & srcFile.Name
End If
Next
‘ 2. サブフォルダの走査と再帰呼び出し
For Each subFolder in srcFolder.SubFolders
targetFolder = dstFolder.Path & “\” & subFolder.Name
If Not objFSO.FolderExists(targetFolder) Then
objFSO.CreateFolder(targetFolder)
WScript.Echo “[MKDIR] ” & subFolder.Name
End If
‘ 再帰
SyncFolders objFSO, subFolder, objFSO.GetFolder(targetFolder)
Next
End Sub
‘ — 階層的なフォルダ作成ヘルパー —
Sub CreateAllFolders(objFSO, targetPath)
Dim parentPath
parentPath = objFSO.GetParentFolderName(targetPath)
If Not objFSO.FolderExists(parentPath) And parentPath <> “” Then
CreateAllFolders objFSO, parentPath
End If
If Not objFSO.FolderExists(targetPath) Then
objFSO.CreateFolder(targetPath)
End If
End Sub
‘ — クリーンアップ(ミラーリング用) —
Sub CleanUpDestFolder(objFSO, dstFolder, srcFolder)
Dim subFolder, dstFile, srcFilePath
‘ コピー元にないファイルを削除
For Each dstFile In dstFolder.Files
srcFilePath = srcFolder.Path & “\” & dstFile.Name
If Not objFSO.FileExists(srcFilePath) Then
dstFile.Delete True
WScript.Echo “[DELETE] ” & dstFile.Name
End If
Next
‘ コピー元にないサブフォルダを削除
For Each subFolder In dstFolder.SubFolders
srcFilePath = srcFolder.Path & “\” & subFolder.Name
If Not objFSO.FolderExists(srcFilePath) Then
subFolder.Delete True
WScript.Echo “[RMDIR] ” & subFolder.Name
Else
CleanUpDestFolder objFSO, subFolder, objFSO.GetFolder(srcFilePath)
End If
Next
End Sub
—
アーキテクチャの解説:なぜこのコードが「現場で耐えうる」のか
1. タイムスタンプ比較の罠を `DateDiff(“s”, …)` で回避
ファイルシステムの仕様やOSのキャッシュ、バックアップ媒体の経由によって、日付オブジェクトの比較には微小なズレ(ミリ秒オーダー)が生じることがある。
本スクリプトでは `DateDiff(“s”, src, dst)` を用い、「1秒以上のズレがある場合のみ更新とみなす」 という現実的なしきい値を設けている。これにより、無駄なファイルコピーを完全に排除することに成功している。
2. `On Error` に頼らない堅牢な存在チェック
VBScriptではエラーハンドリングを安易に `On Error Resume Next` で隠蔽しがちだが、これでは予期せぬバグの温床になる。本コードでは、ファイルやフォルダの存在を事前に `FileExists` / `FolderExists` でガードし、さらに宛先フォルダのパスが深い階層であっても自動生成する `CreateAllFolders` 再帰関数を備えているため、パスが見つからないエラー(エラー76)を物理的に発生させない。
3. メモリとオブジェクトのライフサイクル管理
VBScriptのオブジェクト参照は、ループ内でメモリリークを起こしやすい。特に `File` や `Folder` コレクションを回す際は、ローカル変数を適切に使い、処理の最後で `Set xxx = Nothing` を意識的に行うことで、長時間の実行でもメモリフットプリントを極小に抑えている。
—
運用・自動化のベストプラクティス
このスクリプトを実務の現場に導入する際は、以下の運用設計をセットで行うこと。
- タスクスケジューラでの無人実行
バッチファイル経由ではなく、タスクスケジューラから直接 `cscript.exe //Nologo “C:\path\to\sync_engine.vbs”` として実行すること。GUIポップアップが出る `wscript.exe` を使うと、サーバー環境や無人端末でハングアップしたように停止するため厳禁である。
- ログの外部出力
実運用ではコンソール出力だけでなく、ログファイルへリダイレクトさせると監査上安全だ。
cscript //Nologo C:\scripts\sync_engine.vbs >> C:\logs\sync_%date:~0,4%%date:~5,2%%date:~8,2%.log 2>&1
総括
VBScriptはレガシーな言語として語られることが多いが、Windows環境において「OS標準機能だけで完結する」「インストーラー不要で即座に配備できる」というアドバンテージは、現代の自動化シーンにおいても依然として絶大である。
今回提供した軽量差分同期エンジンをベースに、自社の業務フローに合わせたパスや条件を組み込み、手作業によるファイル管理のミスとストレスを根絶してほしい。
