【実務・中級編】【上級プロフェッショナル向け】ネットワーク共有上の全ディレクトリをFSOで探索し「Permission Denied」が発生するアクセス拒否パスのセキュリティ監視監査 – VBScript (Visual Basic Scripting Edition)解析バイブル

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

権限不足を「成果」に変える:VBScriptによるネットワーク共有監査エンジンの設計思想

ファイルサーバーのセキュリティ監査。多くの開発者はここで、単純な再帰処理スクリプトを書き、ネットワークの負荷に耐えられず、あるいは権限エラーの嵐に飲み込まれて、結局「何も取れなかった」という結果に終わる。

もし君が、単なる「フォルダ巡回スクリプト」を作ろうとしているなら、今すぐその思考を捨ててほしい。我々プロフェッショナルが構築すべきは、「アクセス不能な領域」という不確実性を、正確な「セキュリティ・インテリジェンス」へと変換する監査エンジンだ。

本稿では、VBScript (FileSystemObject) を用い、ネットワーク共有上の全ディレクトリを走査し、実行ユーザーがアクセス拒否(Err.Number = 70)されたパスを正確に特定・記録する、堅牢なアーキテクチャについて解説する。

1. なぜ、一般的な再帰スクリプトは現場で「使い物にならない」のか

初心者が書くコードには、決定的な欠陥が3つある。

1. エラーハンドリングの欠如: `On Error Resume Next` を冒頭に記述し、エラーをすべて握りつぶす。これでは「権限不足」と「ネットワーク切断」の区別がつかず、監査レポートの信頼性がゼロになる。
2. スタックオーバーフローへの無配慮: 深すぎるディレクトリ階層において、再帰呼び出しがリソースを食いつぶす。
3. I/Oコストの無視: ネットワーク越しに大量のオブジェクト(FSOオブジェクト)を生成・破棄する際のオーバーヘッドを計算に入れていない。

我々が目指すのは、「エラーを失敗として扱うのではなく、検知すべきイベントとして扱う」設計だ。

2. プロフェッショナルが採用すべき設計戦略

エラーの分類(Error Classification)

`Err.Number = 70` (Permission Denied) は、本ツールにおいては「失敗」ではなく「重要な検知対象」である。一方で、`Err.Number = 76` (Path not found) やネットワーク遅延によるタイムアウトは、スクリプトの継続を脅かす「システムエラー」として区別しなければならない。

局所的エラーハンドリング(Scoped Error Handling)

`On Error Resume Next` は、「エラーが発生する可能性のある最小単位のメソッド呼び出し」の直前で、直後で解除するのが鉄則だ。これを大規模な関数全体に適用するのは、設計放棄と同義である。

出力設計:CSVへのストリーム書き出し

メモリ上にエラーパスをすべて蓄積してからファイルに書き出す手法は、数百万のディレクトリが存在するエンタープライズ環境ではメモリ不足を招く。逐次、ファイルへ書き出す「ストリーム型」のロギングを採用すべきだ。

3. 実装:堅牢なセキュリティ監査エンジン

以下に、プロダクション環境での利用を想定したコードを示す。このコードは、再帰処理、エラーの厳密な分類、およびCSVレポートへの即時書き出しを実装している。

' ==============================================================================
' Security Audit Engine: Network Share Permission Scanner
' Author: Chief Architect
' Description: ネットワーク共有を走査し、アクセス拒否されたパスを抽出する。
' ==============================================================================

Option Explicit

' 定数定義:マジックナンバーを排除し、保守性を高める
Const ERR_PERMISSION_DENIED = 70
Const ERR_PATH_NOT_FOUND    = 76
Const LOG_FILE_EXTENSION    = ".csv"

' グローバル変数
Dim objFSO, objLogFile
Dim strRootPath, strLogPath

' ------------------------------------------------------------------------------
' メインエントリーポイント
' ------------------------------------------------------------------------------
Sub Main()
    ' 設定:監査対象のルートパスとログ出力先
    strRootPath = "\\YourCompanyServer\SharedData" ' TODO: 環境に合わせて変更
    strLogPath  = "Security_Audit_Report_" & Replace(Replace(Now, ":", "-"), " ", "_") & LOG_FILE_EXTENSION

    Set objFSO = CreateObject("Scripting.FileSystemObject")

    ' ログファイルの初期化(BOM付きUTF-8を想定するなら別途検討が必要だが、ここでは簡易的にASCII/ANSIで実装)
    On Error Resume Next
    Set objLogFile = objFSO.CreateTextFile(strLogPath, True)
    If Err.Number <> 0 Then
        WScript.Echo "Error: ログファイルの作成に失敗しました。パスを確認してください。"
        WScript.Quit
    End If
    On Error GoTo 0

    ' CSVヘッダーの書き込み
    objLogFile.WriteLine "Timestamp,ErrorType,Path"

    WScript.Echo "Audit Started: " & strRootPath
    WScript.Echo "Logging to: " & strLogPath

    ' 探索開始
    ScanDirectory strRootPath

    ' クリーンアップ
    objLogFile.Close
    Set objLogFile = Nothing
    Set objFSO = Nothing

    WScript.Echo "Audit Completed. Report generated: " & strLogPath
End Sub

' ------------------------------------------------------------------------------
' 再帰的ディレクトリ走査エンジン
' ------------------------------------------------------------------------------
Sub ScanDirectory(strPath)
    Dim objFolder, objSubFolder, colSubFolders
    
    ' 1. フォルダへのアクセス試行(ここが最重要ポイント)
    On Error Resume Next
    Set objFolder = objFSO.GetFolder(strPath)
    
    If Err.Number <> 0 Then
        HandleError Err.Number, strPath
        Err.Clear
        On Error GoTo 0
        Exit Sub ' アクセスできないフォルダの配下は探索不能
    End If
    On Error GoTo 0

    ' 2. サブフォルダの列挙
    On Error Resume Next
    Set colSubFolders = objFolder.SubFolders
    If Err.Number <> 0 Then
        ' SubFoldersプロパティへのアクセス自体が拒否されるケース
        HandleError Err.Number, strPath
        Err.Clear
        On Error GoTo 0
        Exit Sub
    End If
    On Error GoTo 0

    ' 3. 再帰処理
    For Each objSubFolder In colSubFolders
        ' 再帰呼び出し
        ScanDirectory objSubFolder.Path
    Next

    ' オブジェクトの解放(メモリリーク防止)
    Set colSubFolders = Nothing
    Set objFolder = Nothing
End Sub

' ------------------------------------------------------------------------------
' エラーハンドリング・ロジック
' ------------------------------------------------------------------------------
Sub HandleError(intErrNum, strTarget)
    Dim strErrorType
    
    Select Case intErrNum
        Case ERR_PERMISSION_DENIED
            strErrorType = "PERMISSION_DENIED"
            ' 権限不足は監査における「重要な発見」であるため、ログに記録
            WriteToLog strErrorType, strTarget
            
        Case ERR_PATH_NOT_FOUND
            strErrorType = "PATH_NOT_FOUND"
            WriteToLog strErrorType, strTarget
            
        Case Else
            strErrorType = "UNKNOWN_ERROR(" & intErrNum & ")"
            WriteToLog strErrorType, strTarget
    End Select
End Sub

' ------------------------------------------------------------------------------
' ロギング・エンジン
' ------------------------------------------------------------------------------
Sub WriteToLog(strType, strPath)
    On Error Resume Next
    ' タイムスタンプ、エラー種別、パスをCSV形式で書き出し
    objLogFile.WriteLine """" & Now & """,""" & strType & """,""" & strPath & """"
    If Err.Number <> 0 Then
        ' ログ書き込み失敗は致命的なため、標準出力へ
        WScript.Echo "Critical Error: Logging failed! " & Err.Description
    End If
    On Error GoTo 0
End Sub

' 実行
Main

4. 実務で勝ち抜くための「極限の知見」

このスクリプトを現場に投入する際、以下の3点を念頭に置け。

オブジェクト・ライフサイクルの厳格管理

VBScriptはCOMオブジェクトの参照カウントに基づき動作する。再帰関数内で `Set objFolder = Nothing` を怠ると、数万のフォルダを巡回する際にメモリ消費が指数関数的に増大し、スクリプトが低速化、あるいはクラッシュする。「使い終わったオブジェクトは即座に捨てる」。これは鉄則だ。

ネットワーク・レイテンシへの耐性

ネットワーク共有へのアクセスは、ローカルディスクとは比較にならないほど不安定だ。`GetFolder` メソッドがタイムアウトを起こす可能性を常に考慮せよ。本コードでは `HandleError` を通じて、タイムアウト等の予期せぬエラーも「Unknown Error」として捕捉し、処理を停止させずに継続するように設計している。

監査レポートの次なるステップ

生成されたCSVは、単なるテキストファイルではない。これをPower BIやExcelのパワークエリに取り込み、「どの管理部門のフォルダに権限の不整合が多いか」を可視化せよ。「ツールを作ること」が目的ではない。「インサイトを提供すること」が我々の真の任務である。

結論:
優れたエンジニアは、エラーを恐れない。エラーを、データとして制御する。
この設計思想を胸に、堅牢な自動化ツールを構築してほしい。

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