【安全削除デザイン】DeleteFolder時の不完全削除を防ぐ「一時退避・一括消去」トランザクション風スクリプト
業務自動化の現場において、ファイルシステムオブジェクト(FSO)を用いたフォルダ削除(`DeleteFolder`)は、一見すると最も枯れた、シンプルな処理に見える。
しかし、ここに落とし穴がある。数万ファイルを含む巨大なフォルダ構造を削除している最中に、ネットワークの一瞬の瞬断、OSのI/Oロック、あるいは他プロセスによるファイル競合が発生したとしたらどうなるか?
結果は「中途半端に一部のファイルだけが消え、残りが残存した状態(ゴミ構造)」の完成だ。
ログには中途半端なエラーが残り、人間が手動で残骸を漁ってクリーンアップする羽目になる。自動化スクリプトが、かえって業務の地雷を増やす瞬間である。
プロのエンジニアであれば、この「部分削除の恐怖」に対してトランザクション的なアプローチで対抗しなければならない。今回は、FSOの物理削除の脆さを克服する「一時退避・一括消去」デザインパターンを授けよう。
—
なぜ素の `DeleteFolder` は実務で使えないのか?
VBScriptの `FSO.DeleteFolder(path, True)` は、再帰的に配下のファイルとフォルダを容赦なく破壊していく。しかし、この処理は非アトミック(不可分)である。
‘ 【アンチパターン】絶対にプロダクション環境で書いてはならないコード
Dim fso
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ 途中でエラーが起きると、半端な状態で処理が停止する
fso.DeleteFolder “C:\Data\TargetFolder”, True
このアプローチが実務で破綻する理由は以下の通りだ:
1. 部分削除の発生: 削除途中でロックファイル(Excelが開かれている等)にヒットすると、そこでエラー終了し、それ以前に走査・削除されたフォルダは消え、ロックされたファイルだけが宙ぶらりんで残る。
2. リトライ不能な状態: 「どこまで消えて、どこが残っているか」をスクリプト側で追跡不可能なため、二重実行時のハンドリングが極めて困難になる。
解決策:ファイルシステムにおける「2段階コミット」
データベースの世界では、コミット前の操作をトランザクションログに書き、最後に一括反映またはロールバックする。
これをファイルシステムで模倣するのが、「①まず安全なテンポラリ領域へ移動(MoveFolder)し、②移動が完了した安全な状態から一括削除(DeleteFolder)する」という設計思想だ。
- `MoveFolder` 自体は、同一ボリューム内(NTFS等)であればメタデータの書き換えだけで完結するため、実質的に一瞬で完了する。
- もし移動中にエラーが起きれば、元フォルダは微動だにせずそのまま残るため、完全な原子性(Atomicity)が担保される。
—
プロダクション品質:トランザクション風・安全削除スクリプト
以下のコードは、エラーハンドリング、一意なテンポラリ名の生成、そして厳密なオブジェクト開放のライフサイクル管理を網羅した、実務投入可能なプロダクションコードである。
Option Explicit
‘ ==============================================================================
‘ スクリプト名: SafeFolderDeleter.vbs
‘ 概要: 巨大フォルダや競合リスクのあるフォルダを、一時退避エリアを経由して
‘ 安全に完全削除(トランザクション風)するサンプル
‘ ==============================================================================
Call Main()
Sub Main()
Dim targetPath, stagingDir, fso
‘ 設定値
targetPath = “C:\Automation\TargetData” ‘ 削除対象のルートフォルダ
stagingDir = “C:\Automation\_StagingArea” ‘ 退避用のテンポラリ親フォルダ(同一ボリューム推奨)
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ 1. 対象存在チェック
If Not fso.FolderExists(targetPath) Then
WScript.Echo “警告: 削除対象のフォルダが存在しません: ” & targetPath
Exit Sub
End If
‘ ステージングエリアの確保
If Not fso.FolderExists(stagingDir) Then
fso.CreateFolder(stagingDir)
End If
‘ 2. 一意の退避フォルダ名を生成(タイムスタンプ + 乱数で衝突防止)
Dim uniqueName, destPath
uniqueName = “Del_” & 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 = fso.BuildPath(stagingDir, uniqueName)
‘ エラーハンドリングの有効化
On Error Resume Next
‘ 3. 【第1フェーズ:アトミック退避】
‘ 同一ボリューム間であればMoveFolderはメタデータ操作のみなので高速かつ安全
WScript.Echo “Phase 1: フォルダを安全エリアへ退避中…”
fso.MoveFolder targetPath, destPath
If Err.Number <> 0 Then
‘ 退避失敗時は元データは無傷。即座に処理を中断する。
WScript.Echo “【致命的エラー】退避フェーズで失敗しました。コード: ” & Hex(Err.Number) & ” – ” & Err.Description
On Error GoTo 0
Set fso = Nothing
Exit Sub
End If
‘ 4. 【第2フェーズ:一括消去】
‘ 目視できない、あるいはユーザーから隔離された退避エリア内のフォルダを削除
WScript.Echo “Phase 2: 退避エリアから一括削除を実行中…”
fso.DeleteFolder destPath, True
If Err.Number <> 0 Then
‘ ここでエラーが出ても、実業務の表舞台(targetPath)からはすでに切り離されているため実害はゼロ。
‘ 管理者アラート用のログを残す等の処置を行う。
WScript.Echo “【警告】削除フェーズで一部残骸が発生しました(退避先: ” & destPath & “)”
Err.Clear
Else
WScript.Echo “正常終了: フォルダの安全削除が完了しました。”
End If
On Error GoTo 0
Set fso = Nothing
End Sub
—
現場のエンジニアへ:知見と実装上の注意点
このスクリプトを運用するにあたり、アーキテクトとして押さえておくべき「ハードウェアおよびOSの制約」を共有しておく。
1. 「同一ボリューム」の原則を破るな
`fso.MoveFolder` は、移動元と移動先が同一の論理ドライブ(例: CドライブからCドライブ)である場合、ファイル自体の物理コピーは発生せず、MFT(Master File Table)のポインタ書き換えだけで完了するため一瞬で終わる。
もしこれを別ドライブ(CからDへ)に逃がそうとすると、FSOは「裏で全ファイルのバイト単位のコピー&元データの削除」を実行する。これでは削除どころか無駄なI/O負荷を増やすだけなので、退避エリア(`stagingDir`)は必ず削除対象と同一ドライブ内に作成すること。
2. プロセス寿命とCOMオブジェクトの解放
VBScriptにおける `CreateObject(“Scripting.FileSystemObject”)` は軽量だが、ループ処理や長時間の自動化プロセスの中でインスタンスを生成・破棄し忘れると、メモリリークの温床となる。スクリプトのライフサイクル終端では必ず `Set fso = Nothing` を明示し、COMコンポーネントの参照カウンタを確実にデクリメントさせよ。
3. アンチウイルスソフトやインデクサとの競合対策
`MoveFolder` 直後に `DeleteFolder` を連続して実行すると、Windowsの検索インデクサ(`SearchIndexer.exe`)やアンチウイルスソフトが「移動してきた謎の大量ファイル群」を瞬時にスキャンし始め、ファイルハンドルを一時的に掴んでしまうことがある。これが原因で `DeleteFolder` が `Permission Denied (エラー 70)` を吐くケースがある。
もしエンタープライズ環境でこの現象に頻発する場合は、Phase 1とPhase 2の間に `WScript.Sleep 1000`(1秒のインターバル)を挟むことで、OS側のI/O落ち着きを待つという泥臭い、しかし極めて効果的なテクニックが生きる。
—
総括
「動けばいい」という次元のコードは、現場を疲弊させる。
ファイルを消すという極めてプリミティブな操作であっても、「もし途中で止まったらどうなるか?」を逆算し、安全なステージングエリアへ退避させるという一手間を加えるだけで、夜間のバッチ処理やRPAの信頼性は劇的に向上する。
泥臭い例外処理に怯える日々を終わらせたいなら、今すぐあなたの自動化スクリプトにこの「一時退避・一括消去」デザインを取り入れるべきだ。
