【テクニカル・上級編】【初心者向け】CreateFolder時の階層欠落エラーを防ぐ「多段サブフォルダ自動安全生成」関数の作成 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

決定論的な自動化への道: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` のような、基礎的ながらも堅牢なユーティリティを自らのライブラリとして保持すること。それこそが、レガシーな環境を統治し、信頼されるシステムを構築するための、エンジニアとしての第一歩である。

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