【実務・中級編】【実務中級者向け】FSOで取得した全ファイルパスの文字数をチェックしWindows制限に近づいている長尺パスを事前検出するリスト化バッチ – VBScript (Visual Basic Scripting Edition)解析バイブル

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

【実務直撃】ファイルサーバー移行の致命傷を防ぐ:FSOによる「長尺パス」検知エンジンの設計と実装

ファイルサーバーの移行プロジェクトにおいて、エンジニアが最も恐れるべきは「移行完了後に発覚する、アプリケーションからのファイルアクセス不能」である。

その原因の筆頭が、Windowsの伝統的な制約であるMAX_PATH(260文字制限)だ。

「移行作業自体は成功した。コピーも完璧だ。しかし、一部の基幹システムがファイルを読み込めない……」

もし君がこのような事態を招いたなら、それは設計段階での「予見」が不足していたことを意味する。本記事では、FileSystemObject (FSO) を駆使して、移行前に「爆弾」となる長尺パスを特定し、リスト化する堅牢なツールをどのように構築すべきか、その極限の設計思想を伝授する。

1. なぜ「単なる再帰処理」では実務に耐えられないのか

初心者は、フォルダを再帰的に辿るスクリプトをこう書く。
「フォルダを見つけたら、その中のファイルを確認し、またその中のフォルダへ……」

しかし、実務の現場(エンタープライズ環境)はそんなに甘くない。以下の3つの壁が君のスクリプトを即座に破壊する。

1. 権限の壁 (Access Denied):
システムフォルダや他ユーザーのフォルダに触れた瞬間、スクリプトは実行時エラーで停止する。
2. パスの壁 (The Irony of MAX_PATH):
パス長をチェックするためにFSOを使おうとしても、「パスが長すぎてFSOがそのフォルダ自体を開けない」という、本末転倒な事態(Catch-22)が発生する。
3. リソースの壁 (Memory & Time):
数百万ファイル規模のストレージを走査する場合、メモリ管理を誤ればスクリプトは低速化し、最終的にクラッシュする。

プロの設計者は、これらのリスクを「例外処理」と「設計判断」によってあらかじめ封じ込める。

2. プロダクション品質の設計指針

今回実装するツールには、以下の3点を設計要件として組み込む。

  • 堅牢な例外ハンドリング: `On Error Resume Next` を乱用せず、権限エラーを「検知してスキップし、ログに残す」という制御を行う。
  • しきい値の戦略的設定: 260文字ギリギリを狙うのではなく、余裕を持った「警戒ライン(例: 240文字)」を設定する。
  • ストリームへの逐次書き出し: 全データをメモリに溜め込まず、発見した瞬間にCSVへ書き出すことで、メモリ消費を最小限に抑える。

3. 実装例:LongPathDetector.vbs

以下のコードは、単なるコピペ用ではない。各プロセスに「意図」を持たせた、保守性の高いプロダクションコードだ。

' ==============================================================================
' Script Name: LongPathDetector.vbs
' Description: ファイルサーバー移行前の長尺パス検知ツール
' Author: Chief Architect
' ==============================================================================
Option Explicit

' --- 設定セクション ---
Const TARGET_ROOT    = "C:\TestFolder"        ' 走査対象ルート
Const OUTPUT_CSV     = "C:\Temp\LongPaths.csv" ' 結果出力先
Const PATH_THRESHOLD = 240                    ' 警告を出す文字数しきい値
Const LOG_FILE       = "C:\Temp\ScanLog.txt"   ' エラーログ出力先

' --- オブジェクト変数 ---
Dim objFSO, objOutputFile, objLogFile
Dim intLongPathCount

Set objFSO = CreateObject("Scripting.FileSystemObject")

' メインルーチン開始
Main

Sub Main()
    On Error Resume Next
    
    ' 出力ファイルの初期化
    Set objOutputFile = objFSO.CreateTextFile(OUTPUT_CSV, True)
    If Err.Number <> 0 Then
        MsgBox "出力ファイルの作成に失敗しました: " & Err.Description, vbCritical
        WScript.Quit
    End If
    
    ' CSVヘッダーの書き込み
    objOutputFile.WriteLine "PathLength,FullPath,Type"

    ' ログファイルの初期化
    Set objLogFile = objFSO.CreateTextFile(LOG_FILE, True)
    
    intLongPathCount = 0
    
    WScript.Echo "スキャンを開始します: " & TARGET_ROOT
    
    ' 再帰処理の開始
    ScanFolder objFSO.GetFolder(TARGET_ROOT)
    
    ' 後処理
    objOutputFile.Close
    objLogFile.Close
    
    WScript.Echo "スキャン完了。" & vbCrLf & _
                 "検出された長尺パス数: " & intLongPathCount & vbCrLf & _
                 "結果: " & OUTPUT_CSV
End Sub

' ------------------------------------------------------------------------------
' 関数: ScanFolder (再帰的走査)
' 引数: objFolder (FileSystemObjectのFolderオブジェクト)
' ------------------------------------------------------------------------------
Sub ScanFolder(objFolder)
    Dim objSubFolder, objFile
    Dim strPath, intLen

    ' 1. フォルダ内のファイルをチェック
    On Error Resume Next
    If Err.Number <> 0 Then
        LogError "Folder Access Denied: " & objFolder.Path
        Err.Clear
        Exit Sub
    End If
    On Error GoTo 0

    ' --- ファイルの走査 ---
    For Each objFile In objFolder.Files
        strPath = objFile.Path
        intLen = Len(strPath)
        
        If intLen >= PATH_THRESHOLD Then
            WriteResult intLen, strPath, "File"
            intLongPathCount = intLongPathCount + 1
        End If
    Next

    ' --- サブフォルダの走査 (再帰) ---
    For Each objSubFolder In objFolder.SubFolders
        strPath = objSubFolder.Path
        intLen = Len(strPath)

        ' フォルダ自体が長い場合も記録
        If intLen >= PATH_THRESHOLD Then
            WriteResult intLen, strPath, "Folder"
            intLongPathCount = intLongPathCount + 1
        End If

        ' 再帰呼び出し
        ScanFolder objSubFolder
    Next
End Sub

' ------------------------------------------------------------------------------
' 関数: WriteResult (CSVへの逐次書き出し)
' ------------------------------------------------------------------------------
Sub WriteResult(length, path, type)
    ' パス内にカンマが含まれる場合を考慮し、ダブルクォーテーションで囲む
    objOutputFile.WriteLine length & ",""" & Replace(path, """", """""") & """," & type
End Sub

' ------------------------------------------------------------------------------
' 関数: LogError (エラーログ記録)
' ------------------------------------------------------------------------------
Sub LogError(msg)
    objLogFile.WriteLine "[" & Now & "] " & msg
End Sub

4. アーキテクトの視点:このコードの「急所」を解説する

このコードが、なぜ「動くだけのコード」と一線を画すのか。その設計の肝を解説する。

① CSV出力におけるエスケープ処理

`WriteResult` サブプロシージャを見てほしい。
`”””” & Replace(path, “”””, “”””””) & “”””` という記述がある。これは、ファイルパス自体にダブルクォーテーションが含まれていた場合に、CSVの構造を破壊しないための「エスケープ処理」だ。実務でこれを怠ると、出力されたCSVをExcelで開いた瞬間に列がズレ、解析作業が地獄に変わる。

② 権限エラーへの「エレガントな退避」

`ScanFolder` 内での `On Error Resume Next` の使い方は極めて限定的だ。
フォルダへのアクセスを試みる直前でエラー制御を有効にし、エラーが発生した場合は `LogError` で記録を残した上で、`Exit Sub` でその枝だけを切り捨てる。これにより、「一部のアクセス拒否によって、スキャン全体が止まる」という致命的な欠陥を回避している。

③ メモリ効率(I/O戦略)

全パスを配列(Array)に格納してから最後に書き出す手法は、小規模なら速いが、大規模ストレージではメモリ不足(Out of Memory)を引き起こす。
本設計では、`objOutputFile.WriteLine` を用いて、「発見した瞬間にディスクへフラッシュする」戦略をとっている。これは、プロフェッショナルが大規模バッチを書く際の鉄則だ。

5. 実務への導入アドバイス

このツールを現場に投入する際は、以下の手順を踏むことを推奨する。

1. Dry Run (試行): まずはテスト用のディレクトリで実行し、CSVが正しく生成されるか、ログに意図しないエラーが多発していないかを確認せよ。
2. 閾値の調整: 240文字で検知した後、そのリストを基に「どの程度のリスクがあるか(アプリケーションの仕様)」を検討せよ。
3. 根本解決への接続: リストアップされたパスは、単に短くするだけでなく、「フォルダ構造の再設計」や「シンボリックリンクの検討」など、移行計画の重要なインプットとして活用せよ。

「自動化は、単に作業を楽にするためのものではない。リスクを可視化し、意思決定の精度を上げるための武器である。」

この精神を忘れずに、君のコードを書き上げてほしい。

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