【テクニカル・上級編】【再帰的フォルダ探索】FileSystemObject を用いたサブフォルダ全検索と特定拡張子ファイルの一括処理ロジック – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【再帰的フォルダ探索】FileSystemObject を用いたサブフォルダ全検索と特定拡張子ファイルの一括処理ロジック

レガシーシステムの保全、あるいはモダンなクラウドストレージへ移行する前のローカルクレンジングにおいて、深層階層化されたフォルダ構造の走査は避けて通れない課題だ。

GUIツールが使えない制限された閉域網、あるいはタスクスケジューラから管理者権限なしでサイレント実行が求められる環境において、VBScript(Visual Basic Scripting Edition)と `Scripting.FileSystemObject` (FSO) の組み合わせは、今なお最強の武器として機能する。

だが、安易な再帰呼び出しは、メモリリークや「パスが長すぎます(Path Not Found)」という致命的な例外を引き起こす。
本稿では、数万ファイルを超える巨大なディレクトリツリーを、メモリを枯渇させることなく安全かつ高速に走査し、特定拡張子のファイルを一括処理するための極限のロジックを解説する。

1. VBScriptにおけるFSO再帰処理のアーキテクチャ上の罠

VBScriptのランタイムは、現代の言語のような高度なガベージコレクションを持たない。COMオブジェクトの参照カウント方式に依存しているため、再帰処理のループ内でオブジェクトの解放(`Set obj = Nothing`)を怠ると、コールスタックの深さに比例してメモリ消費量が肥大化し、最終的にVBScriptランタイムエラー(「プロシージャの呼び出しまたは引数が不正です:’LBound’」やメモリ不足)を引き起こす。

また、WindowsのデフォルトAPIが持つ `MAX_PATH`(260文字)の制限を超えるパスに遭遇した際、FSOは容赦なくエラーを投げてクラッシュする。
シニアエンジニアたる者、これらのハードウェアおよびOS制約を事前にコードレベルで調停しなければならない。

2. 実装コード:堅牢性とパフォーマンスを極めた再帰探索エンジン

以下のコードは、エラーハンドリング、オブジェクトの厳密な解放、そして無限ループや深い階層によるスタック崩壊を防ぐ設計を取り入れた、プロダクション品質の再帰的フォルダ探索・一括処理スクリプトである。

‘ ==============================================================================
‘ Script Name : DeepRecursiveFileProcessor.vbs
‘ Description : FileSystemObjectを用いた安全かつ高速な再帰的フォルダ走査
‘ Author : Chief Architect (Legacy Systems Division)
‘ ==============================================================================

Option Explicit

‘ 定数の定義
Const TargetExtension = “.log” ‘ 処理対象の拡張子
Const LogFilePath = “C:\Logs\ProcessResult.log”

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

‘ ログファイルの準備 (UTF-8またはASCIIでの出力ストリーム確保)
Set objLogFile = fso.OpenTextFile(LogFilePath, 2, True)
objLogFile.WriteLine “=== 処理開始: ” & Now & ” ===”

‘ 処理の起点となるルートフォルダを指定
Dim rootPath
rootPath = “C:\TargetData”

If fso.FolderExists(rootPath) Then
‘ 再帰処理のメインルーチンを呼び出し
ProcessFolder fso.GetFolder(rootPath)
objLogFile.WriteLine “=== 処理正常終了: ” & Now & ” ===”
Else
objLogFile.WriteLine “[ERROR] 指定されたルートフォルダが存在しません: ” & rootPath
End If

‘ リソースのクリーンアップ
objLogFile.Close
Set objLogFile = Nothing
Set fso = Nothing

WScript.Echo “処理が完了しました。ログを確認してください。”
WScript.Quit(0)

‘ ==============================================================================
‘ フォルダ再帰処理コア関数
‘ ==============================================================================
Sub ProcessFolder(ByVal currentFolder)
On Error Resume Next

Dim subFolders, file, files, subFolder
Set files = currentFolder.Files
Set subFolders = currentFolder.SubFolders

If Err.Number <> 0 Then
objLogFile.WriteLine “[WARNING] アクセス拒否または無効なフォルダ: ” & currentFolder.Path & ” (Error: ” & Err.Description & “)”
Err.Clear
Exit Sub
End If

‘ 1. 現在の階層にあるファイルを走査し、条件に合致するものを一括処理
For Each file In files
If LCase(fso.GetExtensionName(file.Name)) = LCase(Replace(TargetExtension, “.”, “”)) Then
‘ — 【ここに個別ファイルに対するビジネスロジックを記述】 —
PerformBusinessLogic file
End If
Next

‘ 2. サブフォルダを再帰的に探索
For Each subFolder In subFolders
‘ ディープパスや特殊なシステムフォルダ(シンボリックリンク等)の無限ループを防ぐガード
If (subFolder.Attributes And 2) = 0 Then ‘ 隠しフォルダやシステム属性をスキップする場合の例(必要に応じて調整)
ProcessFolder subFolder
End If
Next

‘ 3. 【重要】COMオブジェクトの参照を明示的に解放し、メモリリークを根絶する
Set files = Nothing
Set subFolders = Nothing

On Error GoTo 0
End Sub

‘ ==============================================================================
‘ 個別ファイルに対するビジネスロジック(リネーム・移動・内容更新など)
‘ ==============================================================================
Sub PerformBusinessLogic(ByVal targetFile)
On Error Resume Next

Dim originalPath, newPath
originalPath = targetFile.Path

‘ 例:タイムスタンプを付与して別フォルダへ移動する処理
‘ Dim destDir: destDir = “C:\ProcessedData\”
‘ newPath = destDir & fso.GetBaseName(originalPath) & “_” & Year(Now) & Right(“0” & Month(Now), 2) & Right(“0” & Day(Now), 2) & “.” & fso.GetExtensionName(originalPath)
‘ targetFile.Move newPath

‘ 今回はサンプルとしてログ出力のみ
objLogFile.WriteLine “[PROCESSED] ” & originalPath

If Err.Number <> 0 Then
objLogFile.WriteLine “[ERROR] ファイル処理失敗: ” & originalPath & ” (Error: ” & Err.Description & “)”
Err.Clear
End If

On Error GoTo 0
End Sub

3. チーフアーキテクトが解説するコードの急所

① オブジェクトのライフサイクル管理とメモリ最適化

`ProcessFolder` サブルーチン内で取得している `currentFolder.Files` や `currentFolder.SubFolders` コレクションは、VBScriptランタイム内部で強力なCOM参照を保持する。
これをループの脱出時、あるいは再帰呼び出しの直前に `Set files = Nothing` として明示的にメモリから解放しない場合、階層が深くなるにつれてメモリ使用量が右肩上がりに増加し、OSのリソースを圧迫する。
「動くからよし」とする素人コードと、数百万ファイルを処理するプロのコードの決定的な違いは、この「参照のデストロイ(破棄)」の徹底にある。

② `On Error Resume Next` の戦略的配置

ファイルサーバや共有ドライブのクロールにおいて、「アクセス拒否(Access Denied)」や「ファイルが見つからない」といった例外は日常茶飯事である。
スクリプト全体を止めることなく、問題のあるノードのみをスキップして処理を継続させるため、各サブルーチンのスコープ内で局所的にエラーハンドリングを制御している。
エラー発生時は `Err.Description` をログに必ず吐き出し、`Err.Clear` でステータスをリセットする。これがシステム監査に耐えうる堅牢性だ。

③ 拡張子判定の堅牢性

`LCase(fso.GetExtensionName(…))` を用いることで、大文字・小文字の揺れ(`.LOG` と `.log`)を完全に吸収している。また、文字列比較の際には `Replace` 関数を用い、ドットの有無によるバグをあらかじめ排除している点も実務的配慮の現れである。

4. レガシー環境・大規模運用におけるさらなる知見

  • ロングパス(Long Path)問題への対策

Windows 10/11 および Windows Server 2016 以降では、レジストリ設定(`LongPathsEnabled`)またはマニフェストファイルを変更することで 260文字を超えるパスを扱えるようになる。しかし、古いVBScriptランタイム環境ではFSO自体がロングパスを正しく解釈できないケースがある。その場合は、無理にVBScriptで完結させず、PowerShellの `System.IO.DirectoryInfo` へ移行する決断もアーキテクトとしての重要な判断である。

  • CPU・I/Oスロットリング

無限に広がるフォルダ構造を全速力で走査すると、ファイルサーバのI/Oを食いつぶし、業務システムへ悪影響を及ぼす。必要に応じて `WScript.Sleep 10` などのインターバルを挟み、システムへの負荷を制御する配慮を忘れてはならない。

VBScriptはレガシーな技術と揶揄されることもあるが、OSの奥底をダイレクトに叩けるそのポテンシャルは、適切な設計思想のもとでは今なお強力なソリューションである。
正確無比なリソース管理と、予期せぬ例外を想定した防御的プログラミングにより、あなたの管理するシステムを強固なものへと導いてほしい。

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