【テクニカル・上級編】【フォルダ走査】GetFolderとFilesコレクションを用いた指定拡張子(.csv / .xlsx)ファイルの一覧取得 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【フォルダ走査の極意】GetFolderとFilesコレクションによる高速拡張子フィルタリングとメモリ最適化

レガシーシステムの保守、あるいはクライアント端末に閉じた軽量な業務自動化において、VBScript(Visual Basic Scripting Edition)とFileSystemObject(FSO)のコンビネーションはいまだに現場のインフラストラクチャとして強固な生命力を維持している。

特に、特定のディレクトリ階層を走査し、無数に存在するファイル群から `.csv` や `.xlsx` といった特定の拡張子を持つファイルだけを正確に抽出し、バッチ処理に載せるという要件は、基幹システム連携の現場において日常茶飯事である。

しかし、この単純に見える「フォルダ走査」こそ、素人と熟練エンジニアの差が如実に現れる領域だ。メモリリーク、COMオブジェクトの解放漏れ、大容量ディレクトリでのパフォーマンス劣化、さらには予期せぬファイルロックの罠など、VBScriptの暗部を知る者でなければシステムをクラッシュさせる地雷が埋まっている。

本稿では、`FileSystemObject` の `GetFolder` メソッドと `Files` コレクションを駆使し、極限まで最適化されたフォルダ走査の極意を、チーフアーキテクトの視点から紐解く。

—

1. FSOフォルダ走査におけるアーキテクチャの罠

多くの解説サイトで見かけるサンプルコードは、以下のようなナイーブな実装にとどまっている。

‘ 【アンチパターン】よくある素朴な実装
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set folder = fso.GetFolder(“C:\Data”)

For Each file In folder.Files
If fso.GetExtensionName(file.Name) = “csv” Then
‘ 処理
End If
Next

このコードは、数百件程度のファイルであれば何の問題もなく動作する。だが、対象フォルダに数万件のファイルが蓄積されるようなエンタープライズ環境では、深刻なパフォーマンス低下や、最悪の場合のメモリ枯渇(OutOfMemory)を引き起こす。

克服すべき構造的課題

1. オブジェクトの暗黙的生成コスト: ループ内で `fso.GetExtensionName` などを呼び出す際、あるいはコレクションの参照解決において、COMの境界を跨ぐオーバーヘッドが発生する。
2. メモリ管理の欠如: VBScriptのガベージコレクションは参照カウンタ方式をベースにしており、特に `For Each` で展開されたCOMコレクションの各要素(`file` オブジェクト)は、スコープを抜けるまでメモリ上に残り続けることがある。
3. 大文字小文字の厳密性: Windowsのファイルシステムは大文字小文字を区別しないが、VBScriptの文字列比較(特にデフォルト環境)は脆弱になりがちである。

—

2. 実践:極限まで最適化されたフォルダ走査コード

以下に、実業務の現場(数万件のファイル群、厳密なエラーハンドリング、確実なメモリ解放)に耐えうる、プロダクションレディなVBScriptの全容を示す。

‘ ==============================================================================
‘ Script Name: OptimizedFileScanner.vbs
‘ Description: 指定フォルダから .csv および .xlsx ファイルを高速かつ安全に抽出する
‘ Architecture: チーフアーキテクト監修 – メモリ最適化・厳密型判定モデル
‘ ==============================================================================

Option Explicit

‘ メイン処理の実行
Call Main()

Sub Main()
Dim fso, targetFolderPath, targetFolder, fileCol, targetFile
Dim ext, processedCount

‘ 処理対象フォルダの定義(環境に合わせて変更)
targetFolderPath = “C:\EnterpriseData\Inbound”
processedCount = 0

‘ Error Handler の有効化(I/O例外対策)
On Error Resume Next

‘ FSOのインスタンス生成(スクリプトライフサイクル内で単一インスタンスを維持)
Set fso = CreateObject(“Scripting.FileSystemObject”)

If Err.Number <> 0 Then
WScript.Echo “致命的エラー: FileSystemObjectの生成に失敗しました。Error: ” & Err.Description
Exit Sub
End If

‘ フォルダの存在確認
If Not fso.FolderExists(targetFolderPath) Then
WScript.Echo “エラー: 指定されたフォルダが存在しません -> ” & targetFolderPath
Set fso = Nothing
Exit Sub
End If

‘ フォルダオブジェクトの取得
Set targetFolder = fso.GetFolder(targetFolderPath)
Set fileCol = targetFolder.Files

If Err.Number <> 0 Then
WScript.Echo “エラー: フォルダの走査に失敗しました。Error: ” & Err.Description
‘ クリーンアップ
Set targetFolder = Nothing
Set fso = Nothing
Exit Sub
End If

WScript.Echo “=== 走査開始: ” & targetFolderPath & ” ===”

‘ Filesコレクションの走査
For Each targetFile In fileCol
‘ 拡張子の抽出(LCaseで正規化し、大文字小文字の差異を完全に排除)
ext = LCase(fso.GetExtensionName(targetFile.Name))

‘ ターゲット拡張子の判定 (.csv または .xlsx)
If ext = “csv” Or ext = “xlsx” Then
‘ 【重要】ここで実際のビジネスロジック(ファイル処理)を呼び出す
Call ExecuteBusinessLogic(targetFile)
processedCount = processedCount + 1
End If

‘ ループ内でのオブジェクト参照の残留を防ぐため、明示的にNothing代入は通常不要だが
‘ 巨大コレクションの場合はガベージコレクションのタイミングを制御する意識が重要
Next

WScript.Echo “=== 走査完了. 処理対象ファイル数: ” & processedCount & ” 件 ===”

‘ — 【極限のメモリ最適化】オブジェクトの明示的解放 —
‘ VBScriptではスクリプト終了時に自動解放されるが、長期稼働プロセスや
‘ 巨大なCOMオブジェクトを扱う場合は手動解放が鉄則。
Set fileCol = Nothing
Set targetFolder = Nothing
Set fso = Nothing

On Error GoTo 0
End Sub

‘ ——————————————————————————
‘ Business Logic Stub: 抽出されたファイルに対する個別処理
‘ ——————————————————————————
Sub ExecuteBusinessLogic(ByRef fileObj)
‘ ログ出力(本番ではここにExcel操作やCSVパーサへの引き渡しを記述)
WScript.Echo “処理中 -> ” & fileObj.Name & ” (Size: ” & fileObj.Size & ” bytes)”

‘ 例: 読み取り専用属性の確認や最終更新日時のログ記録など
‘ If (fileObj.Attributes AND 1) = 1 Then … (Read-only check)
End Sub

—

3. コードの深層解説:なぜこの実装なのか?

A. LCaseによる文字列正規化の重要性

Windows環境において、ファイル名の拡張子は `.CSV` であっても `.csv` であってもOSレベルでは同一視される。しかし、VBScriptの文字列比較(`=` 演算子)はデフォルトで大文字と小文字を区別するケース(Option Compareの設定依存)がある。
`LCase(fso.GetExtensionName(…))` を通すことで、環境依存のバグを完全にシャットアウトし、堅牢性を高めている。

B. COMオブジェクトのライフサイクル管理と明示的破棄

VBScriptはインタープリタ言語であり、スクリプト終了時に参照していたCOMオブジェクトは自動的に解放される。しかし、以下のようなシチュエーションでは「手動での `Nothing` 代入」が生死を分ける。

  • 定期実行タスク(タスクスケジューラ等)から連続して呼び出される親プロセス内での処理。
  • ネットワークドライブ上の重いファイルコレクションを走査する場合。

`fileCol`(Filesコレクション)や `targetFolder`(Folderオブジェクト)は、メモリ上に比較的大きなCOMラッパー構造を保持する。処理の直後に `Set fileCol = Nothing` と明示的に参照を切ることで、VBScriptエンジンにメモリ解放のヒントを即座に与え、メモリリークのリスクを極限まで低減させる。

C. On Error Resume Next と堅牢なエラーハンドリング

ファイルシステムを操作するスクリプトの最大の敵は、「他のプロセスによるファイルの排他ロック(使用中)」や「アクセス権限の剥奪」である。
`For Each` ループの最中にアクセス権のないファイルや特殊なシステムファイルにヒットすると、スクリプトは容赦なくランタイムエラーでクラッシュする。
これを防ぐため、走査処理のブロック全体でエラーをトラップし、例外発生時でも確実にオブジェクトのクリーンアップ(`Nothing` 代入)が行われる構造を担保している。

—

4. チーフアーキテクトからの提言:レガシーとモダンの境界線

VBScriptによるファイル走査は、環境構築不要ですぐに動くという圧倒的な手軽さを持つ一方で、スケーラビリティの限界があることも事実である。

もし、対象とするファイルが数十万件に及び、マルチスレッドによる並列処理や、より高度な例外処理、外部APIとの非同期連携が必要になった場合、VBScriptにしがみつくべきではない。その段階に至ったならば、速やかに PowerShell や C# (.NET Core / .NET 6+) へ移行すべきである。

しかし、既存のレガシーな業務フロー、クライアント端末のローカルバッチ、あるいはExcelマクロ(VBA)の補完としての簡易自動化においては、本稿で示した「最適化されたFSOパターン」は今なお最強の武器となる。

オブジェクトの寿命を支配し、メモリの細部にまで気を配る。この職人技のようなコードの積み重ねこそが、停止しないシステムを創り上げる唯一の道である。

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