【VBScript極限講座】FSOで実現する「本番稼働に耐えうる」堅牢なファイル同期・ミラーリング設計
開発現場において、「とりあえずFSOの `CopyFolder` を使っておけばバックアップは完了だ」という安易な実装が、どれほどのシステム障害やデータ破損を引き起こしてきたか。私は数々の現場で、その残骸を処理してきた。
VBScriptの `Scripting.FileSystemObject`(以下、FSO)は、レガシーでありながら今なおWindows環境の自動化において最強の武器になり得る。しかし、その手軽さゆえに、エラーハンドリングをサボり、OSのファイルロックやネットワークの瞬断を無視したコードが量産されている。
今回は、実務でそのまま使える、「止まらない・壊れない・ログが残る」バックアップ・ミラーリングスクリプトの設計思想と実装を、チーフアーキテクトの視点から余すところなく伝授する。
—
1. なぜ安易な `CopyFolder` だけでは実務で使えないのか?
FSOの `CopyFolder` メソッドは非常にシンプルだ。
fso.CopyFolder “C:\Source”, “D:\Backup”, True
これだけでフォルダ丸ごとのコピーと上書き(OverWrite = True)が完了する。だが、プロダクション環境(本番環境)において、この1行だけで運用しようものなら、遅かれ早かれ以下のトラブルに直面する。
1. 排他制御の欠如: コピー元またはコピー先のファイルが別プロセス(Excelや別アプリ)で開かれている場合、容赦なく「権限がありません(エラー 70)」でスクリプトがクラッシュする。
2. 「ゴミ(孤立ファイル)」の残留: `CopyFolder` は追加・上書きは得意だが、コピー元で削除されたファイルをコピー先から消してくれない。真のミラーリングには「削除の同期」が必要不可欠である。
3. エラーのサイレント死: `On Error Resume Next` を雑に入れた結果、コピーに失敗しているにもかかわらず「バックアップ成功」とログに記録される最悪の事態を招く。
これらを克服するためには、「ファイル単位の走査(レキュルシブ処理)」と「厳密なエラーハンドリング」を組み合わせた自前のアプローチが必要となる。
—
2. 堅牢なミラーリング・アーキテクチャの設計方針
本記事で提示するスクリプトの設計思想は以下の3点に集約される。
- トランザクション的思考: 予期せぬエラーが発生しても、処理のどこで失敗したかが明確にトレースできること。
- フォールト・トレラント(耐障害性): 1つのファイルのロックごときで全体のバックアップ処理を止めない。エラーファイルをスキップしつつ、ログに「警告」として記録し、処理を完遂する。
- 完全な同期(ミラーリング): コピー元の追加・更新だけでなく、コピー先の不要ファイルを削除するクレンジング機能の実装。
—
3. 【プロダクションコード】実務仕様ファイル同期スクリプト
以下のコードは、文字コード `UTF-8` または `Shift-JIS` で保存し、`.vbs` として実行することを想定している。ログ出力、例外処理、詳細なコメントを網羅した実戦投入可能なコードだ。
‘ ==============================================================================
‘ Script Name : SecureFileMirror.vbs
‘ Description : FSOを用いた堅牢なフォルダミラーリング&バックアップスクリプト
‘ Author : Chief Architect
‘ ==============================================================================
Option Explicit
‘ — 定数定義 —
Const ForReading = 1, ForWriting = 2, ForAppending = 8
Const OverwriteFiles = True
‘ — 設定エリア(環境に合わせて変更してください) —
Dim SOURCE_DIR, DEST_DIR, LOG_FILE
SOURCE_DIR = “C:\BusinessData\TargetFolder”
DEST_DIR = “D:\Backup\TargetFolder”
LOG_FILE = “C:\Logs\Mirroring_” & Year(Now) & Right(“0” & Month(Now), 2) & Right(“0” & Day(Now), 2) & “.log”
‘ — メイン処理 —
Sub Main()
Dim fso, startTime
Set fso = CreateObject(“Scripting.FileSystemObject”)
startTime = Timer()
‘ ログファイルの初期化
Call WriteLog(“INFO”, “=== バックアップ・ミラーリング処理を開始します ===”)
Call WriteLog(“INFO”, “コピー元: ” & SOURCE_DIR)
Call WriteLog(“INFO”, “コピー先: ” & DEST_DIR)
‘ 前提条件チェック
If Not fso.FolderExists(SOURCE_DIR) Then
Call WriteLog(“FATAL”, “コピー元フォルダが存在しません: ” & SOURCE_DIR)
WScript.Quit 1
End If
‘ コピー先が存在しない場合は作成
If Not fso.FolderExists(DEST_DIR) Then
fso.CreateFolder(DEST_DIR)
Call WriteLog(“INFO”, “コピー先フォルダを新規作成しました: ” & DEST_DIR)
End If
‘ 1. 再帰的コピー&更新処理
Call SyncFolders(fso, fso.GetFolder(SOURCE_DIR), fso.GetFolder(DEST_DIR))
‘ 2. コピー先の不要ファイル・フォルダ削除(ミラーリングの肝)
Call PurgeDeletedFiles(fso, fso.GetFolder(DEST_DIR), fso.GetFolder(SOURCE_DIR))
Call WriteLog(“INFO”, “=== 処理が正常に完了しました (所要時間: ” & FormatNumber(Timer() – startTime, 2) & ” 秒) ===”)
Set fso = Nothing
End Sub
‘ — フォルダの同期(コピー&上書き) —
Sub SyncFolders(fso, srcFolder, destFolder)
Dim subFolder, file, destSubPath, destFilePath
‘ 1. ファイルの同期
For Each file In srcFolder.Files
destFilePath = destFolder.Path & “\” & file.Name
On Error Resume Next
file.Copy destFilePath, OverwriteFiles
If Err.Number <> 0 Then
Call WriteLog(“ERROR”, “ファイルコピー失敗: ” & file.Path & ” -> 理由: ” & Err.Description)
Err.Clear
Else
‘ 冗長すぎるログになるため、詳細なデバッグ時以外はコメントアウト推奨
‘ Call WriteLog(“DEBUG”, “Copied: ” & file.Name)
End If
On Error GoTo 0
Next
‘ 2. サブフォルダの走査と作成
For Each subFolder In srcFolder.SubFolders
destSubPath = destFolder.Path & “\” & subFolder.Name
If Not fso.FolderExists(destSubPath) Then
fso.CreateFolder(destSubPath)
Call WriteLog(“INFO”, “サブフォルダ作成: ” & destSubPath)
End If
‘ 再帰呼び出し
Call SyncFolders(fso, subFolder, fso.GetFolder(destSubPath))
Next
End Sub
‘ — コピー元から消されたファイルをコピー先からも削除する(ミラーリング) —
Sub PurgeDeletedFiles(fso, destFolder, srcFolder)
Dim file, subFolder, srcFilePath, srcSubPath
‘ 1. コピー先のファイルがコピー元に存在するか確認し、なければ削除
For Each file In destFolder.Files
srcFilePath = srcFolder.Path & “\” & file.Name
If Not fso.FileExists(srcFilePath) Then
On Error Resume Next
file.Delete True
If Err.Number <> 0 Then
Call WriteLog(“ERROR”, “孤立ファイル削除失敗: ” & file.Path & ” -> 理由: ” & Err.Description)
Err.Clear
Else
Call WriteLog(“WARN”, “コピー元に存在しないため、ファイルを削除しました: ” & file.Path)
End If
On Error GoTo 0
End If
Next
‘ 2. コピー先のサブフォルダがコピー元に存在するか確認し、なければ削除
For Each subFolder In destFolder.SubFolders
srcSubPath = srcFolder.Path & “\” & subFolder.Name
If Not fso.FolderExists(srcSubPath) Then
On Error Resume Next
subFolder.Delete True
If Err.Number <> 0 Then
Call WriteLog(“ERROR”, “孤立フォルダ削除失敗: ” & subFolder.Path & ” -> 理由: ” & Err.Description)
Err.Clear
Else
Call WriteLog(“WARN”, “コピー元に存在しないため、フォルダを削除しました: ” & subFolder.Path)
End If
On Error GoTo 0
Else
‘ 存在する場合はさらに奥をチェック
Call PurgeDeletedFiles(fso, subFolder, fso.GetFolder(srcSubPath))
End If
Next
End Sub
‘ — ログ出力関数 —
Sub WriteLog(level, message)
Dim fso, logFileObj, timestamp
timestamp = “[” & Now & “]”
On Error Resume Next
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ ログフォルダが存在しない場合は自動作成
Dim logDir
logDir = fso.GetParentFolderName(LOG_FILE)
If Not fso.FolderExists(logDir) Then
fso.CreateFolder(logDir)
End If
Set logFileObj = fso.OpenTextFile(LOG_FILE, ForAppending, True)
logFileObj.WriteLine timestamp & ” [” & level & “] ” & message
logFileObj.Close
Set logFileObj = Nothing
Set fso = Nothing
On Error GoTo 0
End Sub
‘ 実行のキック
Call Main()
—
4. コードの急所:アーキテクトが解説する実装のポイント
① 局所的な `On Error Resume Next` の使い所
VBScriptにおいて `On Error Resume Next` をスクリプトの先頭に書くのは素人のやることだ。エラーの握りつぶし(サイレント・フェイル)を引き起こし、バグの温床になる。
本スクリプトでは、ファイルコピーや削除という「外部要因(ファイルロック等)で失敗し得るクリティカルな行」の直前でのみ適用し、`Err.Number` をチェックした直後に `On Error GoTo 0` でデフォルト挙動に戻している。この規律が保守性を担保する。
② `PurgeDeletedFiles` による真の同期
業務システムにおけるバックアップの要件は、「古いデータの退避」だけではない。「誤って作成され、その後削除された機密ファイルが、バックアップ先には残り続ける」というセキュリティリスク(ゾンビファイルの残留)を防ぐ必要がある。
このスクリプトの `PurgeDeletedFiles` 関数は、コピー元を基準にして、コピー先にしかない不要なファイル・フォルダを検知・自動削除する。
③ 堅牢なロギング機構
`WriteLog` プロシージャは、ログファイルの出力先フォルダの有無を動的にチェックして自動生成する。また、処理の開始から終了、エラー内容をタイムスタンプ付きでファイルに永続化するため、タスクスケジューラで夜間無人で実行された後でも、翌朝確実に状況を把握できる。
—
5. 運用上の注意点とさらなる高みへ
- 実行権限(ACL)の継承: FSOのコピーメソッドは、多くの場合、コピー先フォルダのアクセス権を継承する。機密データを扱う場合は、コピー先のセキュリティ権限が適切に絞られているかを事前にインフラ側で担保しておくこと。
- ネットワーク切断への対策: UNCパス(`\\server\share\…`)を対象にする場合、ネットワークの瞬断によってスクリプトが即死することがある。実運用では、ローカルの作業用フォルダへ一度集約するか、リトライ機構(数秒待って再試行するループ)を `SyncFolders` に組み込むと、より鉄壁のシステムとなる。
VBScriptは古い言語と揶揄されることもあるが、OSの奥深く(WSH・FSO)に直接アクセスできるその軽快さと強力さは、いまだに現場の自動化において代え難い価値を持っている。
設計の理をわきまえて書かれたコードは、どんなレガシー環境であっても美しく、そしてタフに稼働し続ける。ぜひ、あなたの現場の業務効率化に役立ててほしい。
