【実務・中級編】【ディレクトリ整理】ファイルが存在しない「空フォルダ」を検出して一括再帰削除するメンテナンスツール – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【VBScript極限講座】ゴミ箱すら肥やさない。ファイルが存在しない「空フォルダ」を再帰的に根絶するメンテナンスツールの設計思想

開発現場のリーダーである私たちが日々直面する、地味だが極めて重要な課題の一つが「ストレージの肥大化とディレクトリの腐敗」だ。
業務システムやバッチ処理、あるいは日々のファイル生成・加工の過程で、一時的な作業ディレクトリが無数に作成される。その中で、ファイルが必要とされなくなった後に捨て置かれた「空フォルダ(Empty Directory)」が、システムのパフォーマンス低下、バックアップ時間の増大、そしてファイル検索時のノイズを引き起こしている。

今回は、VBScriptのFile System Object(FSO)を極限までチューニングし、指定されたルートディレクトリ以下を再帰的に走査して、実体のない空フォルダだけを完全に駆逐するプロダクションレベルのメンテナンスツールを公開する。

単にコードを動かすだけではない。「なぜ一般的なコードではファイルロックや削除漏れが起きるのか」「OSのI/O負荷を最小限に抑えるための正しい再帰アルゴリズムとは何か」を、ロジカルかつシャープに伝授しよう。

—

1. なぜ「上から順の削除」では空フォルダが残るのか?

ディレクトリ構造の削除における最大の罠は、「走査の順序(Top-down vs Bottom-up)」にある。

通常、人間が目で見てフォルダを消すときは、親フォルダから順に中身を確認していく(Top-down)。しかし、これをプログラムで実装すると致命的なバグを生む。
親フォルダを先に削除・判定しようとすると、その子フォルダの走査状態との整合性が狂い、さらに「子フォルダが空になった結果、親フォルダも空になった」という連鎖的な削除(Cascade Deletion)を取りこぼすのだ。

究極の解答:ボトムアップ(底から頂点へ)の再帰走査

確実かつ堅牢なディレクトリ整理を実現するためには、「最下層の葉(Leaf)にあたるサブフォルダを完全に処理し尽くしてから、親へと戻る(Bottom-up)」再帰アルゴリズムを採用しなければならない。
FSOの `SubFolders` コレクションを再帰的に呼び出す際、呼び出しの「戻り値」のタイミングで自身の削除判定を行うことで、このボトムアップ・ロジックを美しく実装できる。

—

2. プロダクションコード:強靭な空フォルダ一括削除ツール

以下に提示するのは、エラーハンドリング(`On Error Resume Next` の適切な局所化)、ログ出力、そして安全性を考慮した実戦投入可能なVBScriptコードだ。

‘ ==============================================================================
‘ Script Name : PurgeEmptyFolders.vbs
‘ Description : 指定されたルートディレクトリ以下の空フォルダを再帰的に検出し削除する
‘ Author : 首席アーキテクト
‘ ==============================================================================

Option Explicit

‘ — 設定エリア —
Dim TARGET_ROOT
TARGET_ROOT = “C:\WorkSpace\TempData” ‘ 整理対象のルートパス(末尾のバックスラッシュなし)

Dim fso
Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ 対象パスの存在確認
If Not fso.FolderExists(TARGET_ROOT) Then
WScript.Echo “[ERROR] 指定されたパスが存在しません: ” & TARGET_ROOT
WScript.Quit(1)
End If

WScript.Echo “[INFO] クリーンアップを開始します: ” & TARGET_ROOT

Dim deletedCount
deletedCount = 0

‘ 処理実行(ルート自身は削除対象外とするため、直下の走査から開始)
CleanEmptyFolders fso.GetFolder(TARGET_ROOT), deletedCount

WScript.Echo “[INFO] 処理が完了しました。削除された空フォルダ数: ” & deletedCount

Set fso = Nothing
WScript.Quit(0)

‘ ==============================================================================
‘ 関数名 : CleanEmptyFolders
‘ 概要 : 再帰的にフォルダを走査し、ファイルも子フォルダもない場合に自身を削除する
‘ 引数 : objFolder (Folderオブジェクト), count (削除カウンター)
‘ 戻り値 : Boolean (このフォルダが空になったかどうか)
‘ ==============================================================================
Function CleanEmptyFolders(objFolder, ByRef count)
Dim subFolder, subFolderCount, fileCount
subFolderCount = 0
fileCount = objFolder.Files.Count

‘ 【重要】FSOのコレクション操作におけるトラップを回避するため、
‘ サブフォルダを一度配列に退避するか、ボトムアップで確実に処理する
Dim colSubFolders
Set colSubFolders = objFolder.SubFolders

‘ 子フォルダを再帰的に走査(ボトムアップの核心)
For Each subFolder In colSubFolders
‘ 再帰呼び出し:子フォルダが空になって削除されたかを評価
If CleanEmptyFolders(subFolder, count) Then
‘ 子が消えたので、ここでは何もしない(コレクションの整合性を保つため)
Else
subFolderCount = subFolderCount + 1
End If
Next

‘ 再走査ののち、現在のフォルダの状態を再評価
‘ ※子フォルダがすべて削除され、かつファイルが最初から存在しない場合のみ削除を実行
If objFolder.Files.Count = 0 And objFolder.SubFolders.Count = 0 Then
‘ ルートフォルダ自体は削除してはならないため、TARGET_ROOTとの比較を行う
‘ (厳密なパス比較のためLCaseを使用)
If LCase(objFolder.Path) <> LCase(TARGET_ROOT) Then
On Error Resume Next
objFolder.Delete True ‘ 強制削除(読み取り属性等は適宜考慮)
If Err.Number = 0 Then
WScript.Echo “[DELETED] ” & objFolder.Path
count = count + 1
CleanEmptyFolders = True
Else
WScript.Echo “[WARNING] 削除失敗(権限またはロック中): ” & objFolder.Path & ” (” & Err.Description & “)”
Err.Clear
CleanEmptyFolders = False
End If
On Error GoTo 0
Else
CleanEmptyFolders = False
End If
Else
CleanEmptyFolders = False
End If
End Function

—

3. アーキテクトが解説するコードの急所と設計のポイント

① コレクションの動的変更に対する防衛策

VBScriptのFSOにおいて、ループ内でオブジェクト(この場合は `SubFolders`)を削除すると、コレクションのインデックスがズレて予期せぬスキップやエラーが発生する。
上記のコードでは、ループ内ですぐに削除せず、「再帰関数の戻り値(Boolean)」によって子フォルダの生存状態を伝播させ、最終的にそのフォルダ自体のファイル数・サブフォルダ数が共に「0」になった瞬間を見計らって `Delete` メソッドを叩いている。これにより、メモリ上のコレクション不整合を完璧に回避している。

② ルートディレクトリ保護の鉄則

メンテナンスツールにおいて最もやってはいけない致命傷は、スクリプトの誤設定による「ルートフォルダ自体の消失」だ。
今回の実装では、`LCase(objFolder.Path) <> LCase(TARGET_ROOT)` という厳密なパス比較ガードを挿入している。これにより、どれほど階層が深くても、指定した起点(`TARGET_ROOT`)そのものが誤って削除されるリスクを物理的にゼロにしている。

③ ロックファイルとアクセス権への配当

業務環境では、他のプロセスが掴んでいるファイル(`Thumbs.db` や `.DS_Store`、あるいは一時的なロックファイル)が存在するせいで、フォルダが完全に空にならないケースが多々ある。
`On Error Resume Next` を `Delete` 実行の局所にのみ適用し、万が一アクセス権エラーやファイルロックで削除できなかった場合でも、スクリプト全体がクラッシュせず、他のフォルダの処理を続行できるように堅牢化している。

—

4. 実務運用へのステップアップ(タスクスケジューラ連携)

このスクリプトを単発の手動実行で終わらせるのはもったいない。
Windowsの「タスクスケジューラ」に登録し、夜間バッチとして毎日(あるいは週に一度)サイレント実行させることで、システム管理者の手を一切煩わせることなく、常にクリーンなストレージ環境を維持できる。

バッチファイル(`.bat`)を一枚噛ませて実行ログをリダイレクトする構成にすると、運用の監査性も劇的に向上する。

@echo off
chcp 65001 >nul
cscript //nologo “C:\Scripts\PurgeEmptyFolders.vbs” >> “C:\Scripts\Logs\cleanup_%date:~0,4%%date:~5,2%%date:~8,2%.log” 2>&1

総括

VBScriptはレガシーな言語と揶揄されることもあるが、OSの根幹にダイレクトにアクセスできるFSOの機動力とシンプルさは、依然としてインフラ・業務自動化において最強の武器の一つである。
場当たり的なコードでシステムを汚すのではなく、オブジェクトのライフサイクルとアルゴリズムの妙を理解した「美しいコード」で、現場の信頼を勝ち取ってほしい。

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