VBScriptを掌握する極限の知見:FSOによる高速・堅牢なバックアップ&ミラーリングの実装
レガシーシステムの保全、あるいは閉域網におけるエッジ端末のデータ退避において、VBScript(Visual Basic Scripting Edition)いまだにその生命力を失っていない。GUIを持たず、OSの標準ランタイム(WSH)だけで完結するこの軽量スクリプトは、インフラエンジニアにとっての「最後の一手」である。
今回は、FileSystemObject(FSO)の `CopyFile` および `CopyFolder` メソッドを極限までチューニングし、単なるコピーを超えた「堅牢なファイルシステム同期・ミラーリング」を実現するアーキテクチャを提示する。
—
1. FSOバックアップにおける致命的な罠と設計思想
多くの初学者は、FSOの `CopyFolder` を呼び出すだけで満足する。しかし、エンタープライズ環境のバッチ処理において、それは「爆弾を抱える」に等しい。
上書きフラグ(OverWriteFiles)の真実
`FileSystemObject.CopyFolder source, destination[, overwrite]` の第3引数である `overwrite` は、デフォルトで `True` である。しかし、これが予期せぬファイル破壊を招く。
- 更新日時の無視: ソース側が古くても、ターゲット側が強制上書きされるリスク。
- アクセス権の喪失: ネットワーク越しのコピーにおいて、ACL(アクセスコントロールリスト)の不整合やロックファイルによる「Permission Denied(書き込み権限がありません)」エラーの発生。
これを回避するためには、FSO単体に頼るのではなく、エラーハンドリングの徹底と差分検知のロジックをスクリプト層で担保する必要がある。
—
2. 実装コード:堅牢性・トレーサビリティを極めたミラーリングスクリプト
以下のコードは、単にファイルをばら撒くのではなく、ログ出力、エラー捕捉、そしてメモリ管理(オブジェクトのライフサイクル制御)のベストプラクティスを網羅したプロダクション品質のVBScriptである。
‘ ==============================================================================
‘ Script Name: FSO_Mirroring_Engine.vbs
‘ Description: FileSystemObjectを使用した堅牢なフォルダ同期・ミラーリングスクリプト
‘ Author: Chief Architect (Legacy Systems)
‘ ==============================================================================
Option Explicit
‘ 定数の定義
Const LogPath = “C:\Logs\Backup_Execution.log”
Const SourceDir = “C:\Work\SourceData”
Const DestDir = “D:\Backup\SourceData”
‘ メイン処理の実行
Call Main()
Sub Main()
Dim fso, logger
Dim startTime, endTime
startTime = Timer
‘ オブジェクトの生成
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set logger = New Logger
logger.Write “=== バックアップ処理を開始します ===”
logger.Write “Source: ” & SourceDir
logger.Write “Destination: ” & DestDir
‘ 前提条件チェック
If Not fso.FolderExists(SourceDir) Then
logger.Write “[FATAL] ソースディレクトリが存在しません: ” & SourceDir
WScript.Quit 1
End If
‘ デスティネーションが存在しない場合は作成
If Not fso.FolderExists(DestDir) Then
fso.CreateFolder(DestDir)
logger.Write “[INFO] デスティネーションフォルダを作成しました.”
End If
‘ コア同期処理の呼び出し
On Error Resume Next
Call SyncFolders(fso, SourceDir, DestDir, logger)
If Err.Number <> 0 Then
logger.Write “[ERROR] 予期せぬエラーが発生しました: ” & Err.Description & ” (0x” & Hex(Err.Number) & “)”
Err.Clear
Else
logger.Write “=== バックアップ処理が正常に完了しました ===”
End If
On Error Goto 0
endTime = Timer
logger.Write “実行時間: ” & FormatNumber(endTime – startTime, 2) & ” 秒”
‘ 【極限の知見】COMオブジェクトの明示的解放
‘ スクリプト終了間際であっても、メモリリークとファイルハンドル解放のためNothing化を徹底する
Set logger = Nothing
Set fso = Nothing
End Sub
‘ ——————————————————————————
‘ フォルダ同期再帰関数 (Deep Sync Engine)
‘ ——————————————————————————
Sub SyncFolders(fso, srcPath, dstPath, logger)
Dim srcFolder, subFolder, file
Dim targetFile, targetSubFolder
Set srcFolder = fso.GetFolder(srcPath)
‘ 1. ファイルの同期(CopyFileのラッパー)
For Each file in srcFolder.Files
targetFile = dstPath & “\” & file.Name
If fso.FileExists(targetFile) Then
‘ タイムスタンプ比較による差分検知(無駄な上書きI/Oを抑制)
Dim dstFile
Set dstFile = fso.GetFile(targetFile)
If file.DateLastModified > dstFile.DateLastModified Then
file.Copy targetFile, True ‘ 上書き許可
logger.Write “[UPDATE] ” & file.Name
End If
Set dstFile = Nothing
Else
‘ 新規ファイルコピー
file.Copy targetFile, True
logger.Write “[CREATE] ” & file.Name
End If
Next
‘ 2. サブフォルダの再帰処理
For Each subFolder in srcFolder.SubFolders
targetSubFolder = dstPath & “\” & subFolder.Name
If Not fso.FolderExists(targetSubFolder) Then
fso.CreateFolder(targetSubFolder)
logger.Write “[MKDIR] ” & subFolder.Name
End If
‘ 再帰呼び出し
Call SyncFolders(fso, subFolder.Path, targetSubFolder, logger)
Next
‘ 参照解放
Set srcFolder = Nothing
End Sub
‘ ——————————————————————————
‘ 簡易ロガー(クラス定義)
‘ ——————————————————————————
Class Logger
Private fso, ts
Private Sub Class_Initialize()
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ 追記モードでログファイルを開く (ForAppending = 8, Create = True)
On Error Resume Next
Set ts = fso.OpenTextFile(LogPath, 8, True)
If Err.Number <> 0 Then
‘ ログが開けない場合はイベントログ等に逃げるかコンソール出力
WScript.Echo “Critical: Logger initialization failed.”
Err.Clear
End If
On Error Goto 0
End Sub
Public Sub Write(msg)
Dim logLine
logLine = “[” & Now & “] ” & msg
WScript.Echo logLine
If Not ts Is Nothing Then
ts.WriteLine logLine
End If
End Sub
Private Sub Class_Terminate()
If Not ts Is Nothing Then
ts.Close
Set ts = Nothing
End If
Set fso = Nothing
End Sub
End Class
—
3. シニアエンジニアが押さえるべき「メモリ最適化」と「ライフサイクル」
VBScriptのガベージコレクション(COMリファレンス・カウンティング)は、一見すると自動で行われるため安全に見える。しかし、FSOやWScript.Shellといったコンポーネントは、インスタンスが破棄されるまでOSレベルのリソース(ファイルハンドルやプロセスアタッチ)を掴み続ける。
オブジェクトのスコープと `Nothing` の強制
スクリプトが肥大化したり、ループ内でむやみに `CreateObject` を呼び出すと、メモリリーク(Memory Leak)を引き起こす。
特に長時間のバックアップバッチにおいて、`For Each` ループ内で取得したフォルダ・ファイルオブジェクトをそのまま放置すると、VBScriptホスト(`wscript.exe` / `cscript.exe`)のメモリフットプリントが徐々に増大し、最終的にリソース枯渇エラーを引き起こす。
上記のコードで示した通り、処理が終わったオブジェクトは直ちに `Set xxx = Nothing` によって参照を断ち切るのが、プロフェッショナルの作法である。
—
4. レガシー環境の保守における注意点とトラブルシューティング
1. 文字コード(BOMの有無)の罠:
VBScriptファイル自体を保存する際、UTF-8(BOM付き)やANSI(Shift-JIS)が混在すると、日本語のログ出力やパス名解決で `Type mismatch` が発生する。インフラ管理用スクリプトは、極力 ANSI (Shift-JIS) または UTF-8(BOMあり) で統一せよ。
2. ネットワーク切断耐性の限界:
SMB(Windows共有フォルダ)経由のバックアップにおいて、ネットワークの瞬間的な切断は `CopyFile` メソッドを一撃で異常終了させる。真に堅牢なシステムを構築する場合は、VBScript単体に頼らず、内部で `robocopy.exe` を `WScript.Shell.Run` でキックし、リトライ制御やスレッド制御をOSネイティブの堅牢なツールに委ねるアプローチも常に視野に入れておくべきである。
(※ただし、外部依存をゼロにしたい極限環境においては、上記のようなFSO再帰スクリプトが唯一無二の武器となる)
総括
VBScriptによるファイル操作は、古い技術のように見えて、その実「OSの根幹にダイレクトに触れるプリミティブな力」を持っている。FSOの挙動を熟知し、メモリのライフサイクルを完全にコントロール下におくことで、モダンな言語に引けを取らない堅牢な自動化基盤を構築することが可能だ。コードの裏にあるメモリとリソースの動きを想像しながら、日々の運用を高度化していってほしい。
