権限不足を「成果」に変える: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のパワークエリに取り込み、「どの管理部門のフォルダに権限の不整合が多いか」を可視化せよ。「ツールを作ること」が目的ではない。「インサイトを提供すること」が我々の真の任務である。
結論:
優れたエンジニアは、エラーを恐れない。エラーを、データとして制御する。
この設計思想を胸に、堅牢な自動化ツールを構築してほしい。
