決定論的な自動化への道:FSOにおける階層欠落を防ぐ「多段ディレクトリ生成」の設計思想
自動化エンジニアリングの現場において、最も初歩的でありながら、最もシステムを不安定にする要因の一つが「パスの存在保証」の欠如である。
Windows Script Host (WSH) や VBA 環境で FileSystemObject (FSO) を扱う際、多くの実装者が `FSO.CreateFolder` を単発の命令として利用する。しかし、このメソッドは極めて「原子(Atomic)」な振る舞いをする。つまり、指定したパスの直上の親ディレクトリが存在しない場合、即座に実行時エラーを吐き出し、プロセスを停止させる。
動的に生成される日付ディレクトリ、プロジェクトID、あるいはユーザー固有のパスを扱うエンタープライズ・オートメーションにおいて、この「親フォルダが存在しない」という事態は、もはやバグではなく「前提条件」として設計に組み込まれるべき問題である。
本稿では、単なる「フォルダを作る関数」ではない。メモリのライフサイクル、エラーハンドリングの決定論、そしてレガシー環境における堅牢性を担保した、プロフェッショナル向けの「多段ディレクトリ自動生成エンジン」の構築手法を解説する。
1. `CreateFolder` の限界と設計上の脆弱性
なぜ `FSO.CreateFolder` は、深い階層を一括で作成できないのか。それは、FSOがファイルシステムの整合性を守るために、既存のツリー構造に対してのみ操作を許可する設計思想に基づいているからだ。
' 失敗する典型的な例
Dim fso: Set fso = CreateObject("Scripting.FileSystemObject")
' C:\Temp が存在しても、C:\Temp\2023\Log が存在しなければ、この行は即死する
fso.CreateFolder("C:\Temp\2023\Log")
この脆弱性は、以下のシナリオで致命的なシステムダウンを招く。
1. 非同期的なファイル操作: 他のプロセスがディレクトリを削除、あるいは移動させた場合。
2. 動的パス生成: 日付やランダムな識別子を含むパスを生成する際、その階層が未定義である場合。
3. ネットワーク共有 (UNCパス): サーバー側の構造変更に対して、スクリプトが脆弱である場合。
2. アーキテクチャ設計:反復的パス分解アプローチ
再帰(Recursion)を用いた実装は、コードが簡潔に見えるため好まれがちだが、極端に深い階層やメモリ制約の厳しいレガシーな環境では、スタックオーバーフローのリスクを完全に拭い去ることはできない。
我々が採用すべきは、「パスを構成要素に分解し、ルートから順次、存在を確認しながら構築していく」という、決定論的かつ反復的なアルゴリズムである。
実装の要件
- UNCパスへの対応: `\\Server\Share` 形式のパスを正しく認識すること。
- 冪等性(Idempotency)の確保: 既にフォルダが存在する場合でも、エラーを発生させず、かつ状態を変化させないこと。
- リソースの明示的解放: COMオブジェクトのライフサイクルを管理し、メモリリークを防止すること。
3. 極限のコード実装:`EnsureDirectoryExists`
以下に、シニアエンジニアが現場に投入するための、堅牢な実装を示す。これは VBScript および VBA の両方で動作するように設計されている。
'==============================================================================
' Function: EnsureDirectoryExists
' Description: 指定されたパスまでの全階層を、存在を確認しながら自動生成する。
' Parameters: strPath (String) - 生成したいフルパス
' Returns: Boolean - 生成(または存在確認)に成功した場合は True
'==============================================================================
Function EnsureDirectoryExists(strPath)
Dim fso, strCurrentPath, arrParts, i, part
Dim isUnc, rootPart
On Error Resume Next ' エラーハンドリングの局所化
Set fso = CreateObject("Scripting.FileSystemObject")
' 1. パスの正規化とUNCパスの判定
' UNCパス (\\Server\Share) の場合、先頭の2文字を考慮する必要がある
isUnc = (Left(strPath, 2) = "\\")
' パスを区切り文字 "\" で分解
arrParts = Split(strPath, "\")
' 2. ルートからの構築プロセス
' strCurrentPath は現在構築中のパスを保持する
If isUnc Then
' UNCパスの場合、最初の2つの要素(\\Server と Share)を結合して開始
' Splitの結果、arrParts(0)は空、arrParts(1)がServer名となる
strCurrentPath = "\\" & arrParts(1) & "\" & arrParts(2)
i = 3 ' 3番目の要素からループ開始
Else
' ローカルパスの場合、ドライブレターから開始
strCurrentPath = arrParts(0)
i = 1
End If
' 3. 階層の逐次検証と生成
For i = i To UBound(arrParts)
part = arrParts(i)
' 空の要素(連続する区切り文字など)はスキップ
If part <> "" Then
strCurrentPath = strCurrentPath & "\" & part
' フォルダが存在しない場合のみ生成を試みる
If Not fso.FolderExists(strCurrentPath) Then
fso.CreateFolder(strCurrentPath)
' 生成失敗(権限不足、パス長制限、ネットワーク切断等)のチェック
If Err.Number <> 0 Then
' エラーログ出力等の処理をここに挟むべき
EnsureDirectoryExists = False
Err.Clear
Exit Function
End If
End If
End If
Next
' 4. 成功判定とリソースのクリーンアップ
EnsureDirectoryExists = True
' COMオブジェクトの明示的な解放(メモリ最適化の鉄則)
Set fso = Nothing
On Error GoTo 0
End Function
' --- テスト実行セクション ---
Dim targetPath
targetPath = "C:\Automation\Logs\2023\Project_Alpha\Daily" ' 実際には存在しない深い階層
If EnsureDirectoryExists(targetPath) Then
WScript.Echo "Success: Directory structure is ready at " & targetPath
Else
WScript.Echo "Critical Error: Failed to ensure directory structure."
End If
4. チーフアーキテクトの視点:プロフェッショナルとしての深掘り
このコードを単なる「便利ツール」で終わらせないために、以下の3つの技術的側面を理解しておかなければならない。
① オブジェクトのライフサイクルとメモリ管理
VBScriptにおいて、`CreateObject` で生成されたCOMオブジェクトは、参照カウントがゼロになるまでメモリ上に残り続ける。ループ内でこの関数を数千回呼び出すようなバッチ処理において、`Set fso = Nothing` を怠れば、メモリ使用量は漸増し、最終的にはシステムの安定性を損なう。「使い終わったオブジェクトは、その場で葬る」。これは鉄則である。
② エラーハンドリングの「局所化」
`On Error Resume Next` は、使い方を誤ればデバッグ不可能な「沈黙するバグ」を生む。本実装では、エラーが発生した瞬間に `Err.Number` を確認し、即座に `Exit Function` させることで、エラーの伝播を最小限に抑えつつ、呼び出し元へ失敗を通知する設計にしている。
③ Windows API との境界線
FSOは高レベルな抽象化を提供してくれるが、ファイルパスの長さ制限(MAX_PATH: 260文字)や、特殊なアクセス権限(ACL)の問題に対しては無力な場合がある。もし、極端に深い階層や、特殊な権限が必要なシステムディレクトリを操作する場合は、FSOを捨て、`Kernel32.dll` の `CreateDirectoryW` を直接叩く Windows API 呼び出しへの移行を検討すべきである。
結論
自動化の品質は、成功するケースの多さではなく、「想定外の事態において、いかに優雅に、かつ決定論的に失敗(あるいは対処)できるか」によって決まる。
`EnsureDirectoryExists` のような、基礎的ながらも堅牢なユーティリティを自らのライブラリとして保持すること。それこそが、レガシーな環境を統治し、信頼されるシステムを構築するための、エンジニアとしての第一歩である。
