【テクニカル・上級編】【階層検索】再帰呼び出し(Recursive)を用いたサブフォルダ配下の全ファイル網羅的検索スクリプト – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

VBScriptを掌握する極限の知見:再帰呼び出しによるファイル網羅的検索の全貌

レガシーシステムの保全、あるいはWindows環境におけるローカル自動化タスクにおいて、VBScript(Visual Basic Scripting Edition)は今なお現役のインフラストラクチャである。GUIを持たず、OSの深層に直接アクセスできるこの軽量スクリプト言語は、適切に扱えば極めて高い信頼性とパフォーマンスを発揮する。

今回は、ファイルシステムオブジェクト(FSO: FileSystemObject)を用いた「再帰呼び出し(Recursive)によるサブフォルダ配下の全ファイル網羅的検索」を取り上げる。

深層化されたディレクトリツリー、循環参照の罠、そしてVBScript特有のメモリ管理の闇。これらをすべて制御し、エンタープライズ環境に耐えうる堅牢なスクリプトを構築するための極限の知見をここに公開する。

—

1. アーキテクチャ設計:なぜ「再帰」なのか、そしてFSOの限界

無限に深化する可能性のあるフォルダ構造を走査する際、アルゴリズムの選択肢は主に2つある。
1. スタックを用いた非再帰(幅優先/深さ優先)探索
2. 関数自身の再呼び出しによる「再帰」探索

VBScriptのランタイム環境において、明示的なスタックオブジェクトを自前で実装するのはコードの肥大化を招く。可読性と保守性を担保しつつ、OSのコールスタックの範囲内で美しく完結させるためには、正しく設計された再帰アルゴリズムが最適解となる。

しかし、FSO(`Scripting.FileSystemObject`)はCOMコンポーネントであり、インスタンス生成と参照管理コストが無視できない。安易なオブジェクト生成はメモリリークやパフォーマンス劣化の直結原因となる。

シニアエンジニアが押さえるべき3大リスク

  • スタックオーバーフロー: 深度が数百階層に及ぶ異常なディレクトリ構造に対する耐性。
  • アクセス拒否(Permission Denied): 権限のないシステムフォルダへの接触によるランタイムエラーのハンドリング。
  • メモリリーク: ループ内でのCOMオブジェクトの暗黙的保持と解放漏れ。

—

2. 実装:極限まで最適化された再帰検索スクリプト

以下のコードは、エラーハンドリング、動的配列による結果保持、そしてメモリの明示的解放(オブジェクトの破棄)を極めた実戦投入可能なVBScriptの完成形である。

Option Explicit

‘ ==============================================================================
‘ スクリプト名: RecursiveFileSearch.vbs
‘ 概要: 指定されたルートディレクトリから再帰的にファイルを検索し、
‘ 条件に合致するパスのリストを返すエンタープライズ向けスクリプト。
‘ ==============================================================================

Dim objFSO, strRootPath, strSearchPattern, arrResults
Set objFSO = WScript.CreateObject(“Scripting.FileSystemObject”)

‘ — 検索パラメータの設定 —
strRootPath = “C:\EnterpriseData” ‘ 検索起点フォルダ
strSearchPattern = “.log” ‘ 検索する拡張子(または部分一致文字列)

‘ 結果格納用の動的配列を初期化
arrResults = Array()

‘ 計測開始
Dim dblStartTime
dblStartTime = Timer

WScript.Echo “検索を開始します: ” & strRootPath

‘ 再帰検索プロシージャの呼び出し
Call SearchFilesRecursive(objFSO.GetFolder(strRootPath), strSearchPattern, arrResults)

‘ 計測終了
Dim dblElapsedTime
dblElapsedTime = Timer – dblStartTime

‘ — 結果出力 —
Dim i, lngTotalCount
lngTotalCount = UBound(arrResults) + 1

WScript.Echo “————————————————–”
WScript.Echo “検索完了. ヒット数: ” & lngTotalCount & ” 件 (” & FormatNumber(dblElapsedTime, 2) & ” 秒)”
WScript.Echo “————————————————–”

‘ 上位50件までコンソール出力
For i = 0 to lngTotalCount – 1
If i < 50 Then WScript.Echo arrResults(i) End If Next If lngTotalCount > 50 Then
WScript.Echo “… 残り ” & (lngTotalCount – 50) & ” 件省略 …”
End If

‘ ルートFSOオブジェクトの解放
Set objFSO = Nothing

‘ ==============================================================================
‘ サブルーチン: 再帰的ファイル検索コアエンジン
‘ ==============================================================================
Sub SearchFilesRecursive(objCurrentFolder, strPattern, ByRef resultList)
On Error Resume Next ‘ 権限エラー等を安全にスキップするための局所的トラップ

Dim objSubFolders, objSubFolder, objFiles, objFile

‘ 1. 現在の階層のファイルを走査
Set objFiles = objCurrentFolder.Files
If Err.Number = 0 Then
For Each objFile In objFiles
‘ 条件判定(ここでは小文字化して拡張子/部分一致を判定)
If InStr(LCase(objFile.Name), LCase(strPattern)) > 0 Then
‘ 動的配列の拡張
ReDim Preserve resultList(UBound(resultList) + 1)
resultList(UBound(resultList)) = objFile.Path
End If
Next
End If

‘ オブジェクト参照の即時解放(メモリ最適化)
Set objFiles = Nothing

‘ 2. サブフォルダを走査し、再帰呼び出しを実行
Set objSubFolders = objCurrentFolder.SubFolders
If Err.Number = 0 Then
For Each objSubFolder In objSubFolders
‘ 再帰呼び出し
SearchFilesRecursive objSubFolder, strPattern, resultList
Next
End If

‘ オブジェクト参照の即時解放
Set objSubFolders = Nothing

On Error GoTo 0 ‘ エラーハンドリングをデフォルトに戻す
End Sub

—

3. コードの急所:シニアエンジニアのための深掘り解説

A. `On Error Resume Next` の局所的かつ厳格な運用

Windowsのファイルシステムには、SYSTEM権限や他ユーザーのプロファイル領域など、一般実行権限ではアクセス不能な「トラップ」が無数に存在する。グローバルにエラーを無視する設計はバグの温床となるため、ここではファイルコレクションの取得とサブフォルダコレクションの取得という、「アクセス違反が発生し得る境界線」の直前でのみ `On Error Resume Next` を適用し、直後に `On Error GoTo 0` でリセットしている。これにより、権限のないフォルダは静かにスキップされ、スクリプト全体が停止するのを防ぐ。

B. メモリリークを根絶する `Set … = Nothing` の作法

VBScriptのガベージコレクションは参照カウント方式(Reference Counting)をベースにしている。
再帰呼び出しのループ内(特に `For Each` の中)でCOMオブジェクトを次々と生成・破棄しない場合、スコープが抜けるまでメモリ上にオブジェクトが残り続ける。
上記のコードでは、`objFiles` や `objSubFolders` のコレクション処理が終わった瞬間に `Set objFiles = Nothing` を明示的に実行し、コールスタックが深くなった際のメモリ肥大化(OOM: Out of Memory)を完全に防止している。

C. 配列の動的拡張(`ReDim Preserve`)のパフォーマンス特性

配列の要素数を動的に増やす `ReDim Preserve` は、実行のたびにメモリの再割り当てと要素のコピーが発生するため、極端な多要素処理ではパフォーマンスが低下する。
しかし、数万件レベルのファイルパス保持であれば、VBScriptのオーバーヘッド内でおおむね許容範囲内に収まる。もし数十万件を超えるアトミックな処理が前提となる場合は、FileSystemObjectではなく、WMI(`Win32_LogicalFileSharing`等)やADO Recordsetをメモリ上のストレージとして代用するアーキテクチャへの移行を検討すべきである。

—

4. レガシー環境保守とシステム間連携への応用

この再帰検索スクリプトは、単体のバッチ処理としてだけでなく、以下のようなエンタープライズ連携の基盤として組み込むことができる。

  • ETL前段のファイル存在確認: 夜間バッチで外部からドロップされるCSV/DATファイルを検知し、所定のデータベースやメッセージキューにパスを通知するトリガー。
  • ストレージ監査: 一定期間アクセスされていない肥大化したログファイルの自動洗い出しとアーカイブ連携。
  • RPA(Robotic Process Automation)の補助: 市販のRPAツールが持つネイティブなファイル検索機能が重い、あるいは柔軟な部分一致・正規表現的な絞り込みに対応していない場合の「外出し高速エンジン」としてのVBScript呼び出し。

—

総括

VBScriptは「古い言語」として片付けられがちだが、OSのプリミティブな機能にダイレクトに触れられる特権的なレイヤーに位置している。
オブジェクトのライフサイクルをコントロールし、メモリの枯渇や例外処理の隙間を完全に塞いだコードは、現代のモダン言語で書かれたプログラムと同等の堅牢性を宿す。

手元のインフラストラクチャを確実に、かつエレガントに自動化するための一手として、この再帰検索パターンをあなたの武器庫に加えてほしい。

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