【テクニカル・上級編】【安全削除デザイン】DeleteFolder時の不完全削除を防ぐ「一時退避・一括消去」トランザクション風スクリプト – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

VBScriptを掌握する極限の知見:【安全削除デザイン】DeleteFolder時の不完全削除を防ぐ「一時退避・一括消去」トランザクション風スクリプト

レガシーシステムの自動化、あるいはクライアント端末のバッチ処理において、`FileSystemObject`(以下、FSO)の `DeleteFolder` メソッドほどエンジニアを絶望の淵に追い込む機能はない。

「巨大なディレクトリ構造を削除している最中に、ネットワークが切断された」
「途中でアクセス権のないファイルにヒットし、エラーで止まった」
「その結果、半端にファイルが消え、半端に残った『ゴミの山』が業務システムを沈黙させた」

このような修羅場をくぐり抜けてきたシニアエンジニアであれば、FSOの削除処理をそのまま信頼して本番環境に投入することの愚かさは骨身に染みているはずだ。FSOの `DeleteFolder` はアトミック(不可分)ではない。ファイル単位で削除を試み、途中で失敗すればそこで処理を放棄する。

今回は、この脆弱なファイルシステム操作に対し、「MoveFolderによる一時退避とトランザクション的確定処理」を実装し、不完全削除(ゾンビフォルダ問題)を根絶するための極限のアーキテクチャを解説する。

—

1. FSO削除の限界と「ゾンビフォルダ」のメカニズム

なぜ `DeleteFolder(path, True)`(強制削除フラグあり)を使っても事故が起きるのか。

FSOの内部実装は、Win32 APIの `SHFileOperation` や `RemoveDirectory` / `DeleteFile` のラッパーに過ぎない。数万ファイルを超える階層構造を削除する場合、OSのハンドル枯渇、ウイルス対策ソフトのリアルタイムスキャンによるファイルロック(Sharing Violation)、あるいはリモート共有(UNCパス)上のタイムアウトが複合的に絡み合う。

途中でエラーが発生した場合、以下のような致命的な状態異常(ゾンビ化)を引き起こす。

  • ルートフォルダ直下のファイルは消えたが、深い階層のサブフォルダとファイルが残存する。
  • ファイルは削除されたが、ディレクトリのロックが解除しきれず、フォルダ構造だけが宙ぶらりんになる。
  • リカバリしようにも、どこまで処理が進んだのかログから追跡できない。

これを防ぐ唯一の解法は、「削除対象を不可視の別領域へ一瞬で移動(Move)させ、視界から消去した上で、安全な環境下でバックグラウンド消去する」という、データベースのトランザクション思想の持ち込みである。

—

2. アーキテクチャ設計:一時退避・一括消去パターン

今回構築するスクリプトのライフサイクルは以下の4フェーズで構成される。

1. Isolation(隔離フェーズ)
同一ボリューム内のテンポラリ領域へ、対象フォルダを一撃で `MoveFolder` する。同一ボリューム間の移動であれば、ファイルの実体データは移動せず、MFT(マスターファイルテーブル)のポインタ書き換えだけで完了するため、極めて高速(O(1)のオーダー)であり、途中でネットワーク切断が発生するリスクを最小化できる。
2. Sanctuary Execution(安全消去フェーズ)
安全な退避先へ移動したフォルダに対し、エラーハンドリングを厳密に組み込みながら `DeleteFolder` を実行する。仮にここでエラーが起きても、業務システム本体への影響はゼロである。
3. Fallback / Rollback(例外処理フェーズ)
隔離に失敗した場合や致命的なエラーが発生した場合の状態復旧。
4. Resource Hygiene(メモリ・オブジェクト解放フェーズ)
VBScript特有のCOM参照リークを防ぐための徹底的な `Nothing` 代入。

—

3. 実装コード:完全版セーフティデリート・スクリプト

以下のコードは、エラーハンドリング、同一ボリューム判定の概念、そしてVBScriptにおける厳格なオブジェクト管理を網羅したプロダクションクオリティの実装である。

Option Explicit

‘ ==============================================================================
‘ スクリプト名: SafeDeleteFolder.vbs
‘ 概要: FSOの不完全削除を防ぐため、同一ボリューム内一時退避を経て削除を実行する
‘ ==============================================================================

Dim targetPath, tempDirBase, fso
targetPath = “D:\DataSync\TargetFolder” ‘ 削除対象のルートフォルダ
tempDirBase = “D:\DataSync\_TempTrash” ‘ 同一ボリューム内の退避用ベースディレクトリ

‘ 実行
Call ExecuteSafeDelete(targetPath, tempDirBase)

Sub ExecuteSafeDelete(ByVal strTarget, ByVal strTempBase)
Dim objFSO, objTargetFolder, destPath, uniqueName
Dim lngErrNumber, strErrDesc

Set objFSO = CreateObject(“Scripting.FileSystemObject”)

‘ 1. 前提条件チェック
If Not objFSO.FolderExists(strTarget) Then
WScript.Echo “[INFO] 削除対象フォルダが存在しません: ” & strTarget
Set objFSO = Nothing
Exit Sub
End If

If Not objFSO.FolderExists(strTempBase) Then
On Error Resume Next
objFSO.CreateFolder(strTempBase)
If Err.Number <> 0 Then
WScript.Echo “[FATAL] 退避ベースディレクトリの作成に失敗しました: ” & strTempBase
Set objFSO = Nothing
Exit Sub
End If
On Error GoTo 0
End If

‘ 2. 同一ボリュームチェックの担保(ドライブレターの比較)
‘ ※異なるボリューム間でのMoveFolderはコピー&元データ削除になるためパフォーマンスが劣化する
If UCase(Left(strTarget, 3)) <> UCase(Left(strTempBase, 3)) Then
WScript.Echo “[ERROR] ターゲットと退避先が異なるドライブです。同一ボリュームを指定してください。”
Set objFSO = Nothing
Exit Sub
End If

‘ 3. 衝突防止のためのユニーク名生成(タイムスタンプ + ランダム)
Randomize
uniqueName = “Trash_” & Year(Now) & _
Right(“0” & Month(Now), 2) & _
Right(“0” & Day(Now), 2) & “_” & _
Right(“0” & Hour(Now), 2) & _
Right(“0” & Minute(Now), 2) & _
Right(“0” & Second(Now), 2) & “_” & _
Int((9999 – 1000 + 1) Rnd + 1000)

destPath = objFSO.BuildPath(strTempBase, uniqueName)

‘ 4. 【Isolationフェーズ】一時領域へアトミック移動
On Error Resume Next
WScript.Echo “[DEBUG] 退避処理を開始します -> ” & destPath
objFSO.MoveFolder strTarget, destPath
lngErrNumber = Err.Number
strErrDesc = Err.Description
On Error GoTo 0

If lngErrNumber <> 0 Then
WScript.Echo “[ERROR] フォルダの退避に失敗しました。プロセスがロックされている可能性があります。”
WScript.Echo “詳細: ” & strErrDesc
Set objFSO = Nothing
Exit Sub
End If

WScript.Echo “[SUCCESS] フォルダの隔離に成功しました。安全領域から消去を開始します。”

‘ 5. 【Sanctuary Executionフェーズ】退避先からの削除実行
On Error Resume Next
objFSO.DeleteFolder destPath, True
lngErrNumber = Err.Number
strErrDesc = Err.Description
On Error GoTo 0

If lngErrNumber <> 0 Then
‘ ここで削除に失敗しても、本番領域からはすでに消えているため業務影響は最小限
WScript.Echo “[WARNING] 退避先での完全削除に一部失敗しました(ゾンビフォルダ化の可能性)。”
WScript.Echo “手動クリーンアップが必要なパス: ” & destPath
WScript.Echo “詳細: ” & strErrDesc
Else
WScript.Echo “[COMPLETE] フォルダの完全消去が正常に完了しました。”
End If

‘ 6. 【Resource Hygieneフェーズ】オブジェクトの明示的破棄
Set objTargetFolder = Nothing
Set objFSO = Nothing
End Sub

—

4. チーフアーキテクトが指摘する「VBScriptの闇」と最適化テクニック

実務でVBScriptを扱う際、コードの美しさ以上に「COMのライフサイクル管理」と「エラーの隠蔽(On Error Resume Next)の罠」を理解しているかがエンジニアの格を決める。

① COMオブジェクトのデストラクタ不在問題

VBScriptには明示的なデストラクタが存在しない。スクリプトが終了するまで、あるいはスコープを抜けるまで、生成したCOMオブジェクト(`Scripting.FileSystemObject` など)はメモリ上に居座り続ける。
特に、ループ内で `CreateObject` を呼び出すような愚行を犯すと、メモリリーク(RAMの肥大化)を引き起こし、最終的にブルースクリーンやVBScriptホスト自体の強制終了を招く。
コード例の通り、処理の終端では必ず `Set objFSO = Nothing` を明示し、参照カウントをデクリメントさせることが鉄則である。

② 同一ボリューム間ムーブの物理的特性

WindowsのNTFSファイルシステムにおいて、同一ボリューム内のフォルダ移動(`MoveFolder`)は、ファイルの中身をコピーしているわけではない。MFT上の親ディレクトリのポインタを書き換えているだけである。
そのため、数テラバイトのデータであっても一瞬で完了する。
逆に、これを異なるボリューム間で行うと、OSは「裏でコピーして、元のファイルを地道に削除する」という重たい処理を実行するため、ネットワーク遮断のリスクに再び直面することになる。スクリプト内のドライブレターチェック(`Left(strTarget, 3) <> Left(strTempBase, 3)`)は、この物理的制約をハックするための必須防壁である。

③ エラーハンドリングのスコープ制御

`On Error Resume Next` を宣言すると、以降のエラーはすべて無視される。これはVBScriptにおける諸刃の剣であり、バグの温床となる。
レガシーコードの多くは、スクリプトの先頭にこれを書き、エラーハンドリングを完全に放棄している。
今回の設計では、エラーが発生しうる危険な行の直前でのみ `On Error Resume Next` を有効化し、直後に `On Error GoTo 0` で厳格なエラー監視状態へ復帰させるというスコープ制御を徹底している。

—

5. 総括

VBScriptはレガシーな言語と揶揄されることがあるが、Windows OSの根幹APIやファイルシステム(NTFS)の挙動を正しく理解していれば、これほど軽量かつ強力な自動化ツールはない。

今回解説した「一時退避・一括消去トランザクション風スクリプト」は、単なるファイル操作のテクニックに留まらず、「予測不能な障害(ネットワーク切断、ファイルロック)を前提としたシステム耐障害性(レジリエンス)の設計思想」そのものである。

現場のインフラやバッチ処理の信頼性を一段上のステージへ引き上げるために、ぜひこの安全削除デザインを導入してほしい。

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