【テクニカル・上級編】【上級プロフェッショナル向け】FSO一括削除・更新操作の直前に全変更対象の事前シミュレーション(Dry-Run)を行う安全機構 – VBScript (Visual Basic Scripting Edition)解析バイブル

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

【極限の設計論】VBScriptにおける非破壊的ファイル操作:Dry-Run機構による「不測の事態」の完全排除

現場で長年、レガシーなWindows環境の自動化と向き合ってきたエンジニアなら、誰もが一度は「恐怖」を感じたことがあるはずだ。

`FileSystemObject` (FSO) を用いた一括削除、あるいは大規模なリネーム処理。条件分岐のロジックがわずか一行狂っただけで、数テラバイトのデータが音を立てて消え去る。VBScriptにおける `DeleteFile` や `MoveFile` は、慈悲のないコマンドだ。実行した瞬間に、ゴミ箱を経由することもなく、オブジェクトは消滅する。

シニアエンジニア、あるいはシステム管理者として、我々に求められるのは「コードが動くこと」ではない。「コードが間違っていたとしても、システムを破壊させない設計」である。

本稿では、FSOを用いた一括操作において、実際の破壊的プロセスを実行する前に「何が起こるか」を完全にシミュレートするDry-Run(空回し)機構の設計思想と、その実装について詳述する。

1. 設計思想:分離の原則 (Separation of Concerns)

致命的なミスを防ぐための鉄則は、「判定ロジック(Predicate)」と「実行ロジック(Action)」を完全に分離することである。

多くの初級者は、`For Each` ループの中で「ファイルが存在するか」「条件に合致するか」を判定し、その直後に `DeleteFile` を呼び出す。これは設計として極めて脆弱だ。判定の瞬間に「正しい」と判断したファイルが、実行の瞬間に条件から外れている、あるいは意図しないディレクトリに侵入している可能性があるからだ。

プロフェッショナルが採用すべきアーキテクチャは以下の二段階プロセスである。

1. Discovery Phase (探索フェーズ):
対象となるファイルシステムを再帰的に走査し、「どのパスに対して」「どのような操作(Delete/Rename/Update)を」行うべきかを、メモリ上のデータ構造(Dictionaryオブジェクト)にマッピングする。この段階では、ファイルシステムに対して一切の変更を加えない。
2. Execution Phase (実行フェーズ):
Discovery Phaseで生成された「実行計画書」を読み込み、ユーザーの承認(またはログ確認)を経て、実際の操作を行う。

2. 実装:Dry-Run対応型 FSOマネージャー

以下に、シミュレーション機能を備えた堅牢なスクリプトの構造を示す。ここでは `Scripting.Dictionary` を活用し、実行計画をメモリ上に保持する手法をとる。

' **
' Module: SecureFileSystemManager
' Description: Dry-Run機構を備えた非破壊的ファイル操作エンジン
' Author: Legendary Chief Architect
' **

Option Explicit

' 操作種別の定数定義
Const ACTION_NONE   = 0
Const ACTION_DELETE = 1
Const ACTION_RENAME = 2

' グローバル変数:実行計画を格納するDictionary
' Key: 対象ファイルパス, Value: 操作種別
Dim objPlanMap

Sub Main()
    Dim strTargetDir, strLogFile, objFSO, objPlan
    
    ' 環境依存の設定(実運用では外部設定ファイルや引数から取得すべき)
    strTargetDir = "C:\Temp\TargetData" ' 操作対象ディレクトリ
    strLogFile   = "C:\Temp\Operation_Plan.log"
    
    Set objFSO = CreateObject("Scripting.FileSystemObject")
    Set objPlanMap = CreateObject("Scripting.Dictionary")

    ' 1. ターゲットの存在確認
    If Not objFSO.FolderExists(strTargetDir) Then
        WScript.Echo "Error: Target directory not found."
        WScript.Quit 1
    End If

    ' 2. Discovery Phase: 実行計画の策定
    WScript.Echo "Phase 1: Scanning file system..."
    ScanDirectory objFSO, strTargetDir

    ' 3. Plan Review: 計画のログ出力
    WScript.Echo "Phase 2: Writing execution plan to log..."
    WritePlanToLog strLogFile

    ' 4. Confirmation: ユーザーへの最終確認
    ' ※バッチ実行時は、この部分をコマンドライン引数等でスキップ可能にする設計にせよ
    Dim intResponse
    intResponse = MsgBox("Execution plan generated. Review the log file." & vbCrLf & _
                         "Proceed with actual operations?", vbYesNo + vbQuestion, "Confirm Execution")

    If intResponse = vbYes Then
        ' 5. Execution Phase: 実際の操作
        WScript.Echo "Phase 3: Executing operations..."
        ExecutePlan objFSO
        WScript.Echo "Operation completed successfully."
    Else
        WScript.Echo "Operation aborted by user. No changes were made."
    End If

    ' オブジェクトの明示的解放(メモリリーク防止とリソースの確実な返却)
    Set objPlanMap = Nothing
    Set objFSO = Nothing
End Sub

' ------------------------------------------------------------------------------
' Function: ScanDirectory (再帰的探索)
' 目的: 条件に合致するファイルを特定し、実行計画(Dictionary)に登録する
' ------------------------------------------------------------------------------
Sub ScanDirectory(objFSO, strPath)
    Dim objFolder, objSubFolder, objFile
    
    ' エラーハンドリング:アクセス権限のないフォルダをスキップ
    On Error Resume Next
    Set objFolder = objFSO.GetFolder(strPath)
    If Err.Number <> 0 Then
        WScript.Echo "Warning: Access denied to folder: " & strPath
        Err.Clear
        Exit Sub
    End If
    On Error GoTo 0

    ' ファイルの走査
    For Each objFile In objFolder.Files
        ' 【ここにビジネスロジックを記述】
        ' 例: 拡張子が .tmp のファイルを削除対象とする
        If LCase(objFSO.GetExtensionName(objFile.Path)) = "tmp" Then
            ' 実行計画に登録(パスをキー、操作種別を値とする)
            If Not objPlanMap.Exists(objFile.Path) Then
                objPlanMap.Add objFile.Path, ACTION_DELETE
            End If
        End If
    Next

    ' サブフォルダの再帰走査
    For Each objSubFolder In objFolder.SubFolders
        ScanDirectory objFSO, objSubFolder.Path
    Next
End Sub

' ------------------------------------------------------------------------------
' Sub: WritePlanToLog
' 目的: 策定された計画をテキストファイルに書き出し、人間が監査可能にする
' ------------------------------------------------------------------------------
Sub WritePlanToLog(strLogFile)
    Dim objFSO, objLog, strKey, varAction
    Set objFSO = CreateObject("Scripting.FileSystemObject")
    ' 既存のログを上書きせず、新しいログを作成
    Set objLog = objFSO.CreateTextFile(strLogFile, True)

    objLog.WriteLine "=== FILE OPERATION EXECUTION PLAN ==="
    objLog.WriteLine "Generated at: " & Now
    objLog.WriteLine "--------------------------------------"

    If objPlanMap.Count = 0 Then
        objLog.WriteLine "No matching files found. No actions planned."
    Else
        For Each strKey In objPlanMap.Keys
            varAction = objPlanMap(strKey)
            Select Case varAction
                Case ACTION_DELETE: objLog.WriteLine "[DELETE] " & strKey
                Case ACTION_RENAME: objLog.WriteLine "[RENAME] " & strKey
                Case Else:          objLog.WriteLine "[UNKNOWN] " & strKey
            End Select
        Next
    End If

    objLog.WriteLine "--------------------------------------"
    objLog.WriteLine "Total planned actions: " & objPlanMap.Count
    objLog.Close
    Set objLog = Nothing
End Sub

' ------------------------------------------------------------------------------
' Sub: ExecutePlan
' 目的: 策定された計画に基づき、実際の破壊的操作を実行する
' ------------------------------------------------------------------------------
Sub ExecutePlan(objFSO)
    Dim strPath, varAction
    
    ' DictionaryのKeysをループ
    For Each strPath In objPlanMap.Keys
        varAction = objPlanMap(strPath)
        
        On Error Resume Next ' 個別の操作エラーが全体を止めないよう制御
        Select Case varAction
            Case ACTION_DELETE
                If objFSO.FileExists(strPath) Then
                    objFSO.DeleteFile strPath, True ' True = Read-only属性も強制削除
                End If
            ' Case ACTION_RENAME: ... (実装が必要な場合)
        End Select
        
        If Err.Number <> 0 Then
            ' エラーログの出力など、実運用ではここでのエラーハンドリングが極めて重要
            ' WScript.Echo "Error executing action on: " & strPath & " - " & Err.Description
            Err.Clear
        End If
        On Error GoTo 0
    Next
End Sub

' スクリプト実行開始
Main

3. プロフェッショナルのための技術的知見

3.1 オブジェクトのライフサイクル管理

VBScriptにおいて、`Set obj = Nothing` による明示的な解放は、単なる「行儀の良さ」ではない。特に大規模なファイル走査を行う際、COMオブジェクトの参照カウントが適切に減少しない場合、メモリ使用量が指数関数的に増大し、OSのメモリ管理に負荷をかける。`ScanDirectory` のような再帰関数内では、ループの終端でローカル変数を確実に解放する意識を持つべきだ。

3.2 エラーハンドリングの戦略

`On Error Resume Next` は、諸刃の剣である。
本設計のように、「探索フェーズ」と「実行フェーズ」でエラーの扱いを変えるのが定石だ。

  • 探索フェーズ: アクセス権限エラー(Permission Denied)は、スキップしてログに残すべきだが、致命的なエラー(ディスクの読み取り不能など)は即座に停止すべきである。
  • 実行フェーズ: 一つのファイルの削除に失敗したからといって、残りの数千件の処理を止めるべきではない。個別のエラーをキャッチし、ログに記録しながら、次のタスクへ進む「非停止型」の設計が求められる。

3.3 パフォーマンスの重み

`FileSystemObject` は非常に便利だが、COMインターフェースを経由するため、大量のファイル(数十万件規模)を扱う場合はオーバーヘッドが無視できなくなる。もし速度がボトルネックとなる場合は、VBScriptの限界を認め、PowerShellへの移行、あるいはC#を用いたネイティブなWindows API(`FindFirstFile` / `FindNextFile`)の呼び出しを検討すべきだ。

結論

「動くコード」を書くのは初級者であり、「安全なコード」を書くのがプロフェッショナルである。

Dry-Run機構の実装は、一見すると手間が増えるように思えるかもしれない。しかし、一度の誤操作によるデータ損失のコストに比べれば、この数時間の設計・実装コストは、極めて安価な「保険」である。我々エンジニアが、システムの安定稼働に対して負うべき責任を、忘れてはならない。

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