忍者ブログ
VBAによる実用アプリケーションの構築、およびGAS(Google Apps Script)やOffice Scriptへのモダンな移行パスを検証・解説するテックブログ。現場で即戦力となるコードモジュールや再利用可能な実用部品を継続的に提供します。

【VBA】Dir 関数の罠を撃退!エラーとファイルを完全判別する「安全なフォルダ存在確認関数」(テストコード付き)

前回の「ファイル存在確認」に続き、今回は「指定したフォルダ(ディレクトリー)が存在するかどうか」を判定する処理を解説します。

「フォルダがあるか確認するだけなら Dir(path, vbDirectory) で一発では?」と思われがちですが、実はVBA標準の Dir 関数でフォルダ確認を行うと、ファイル存在確認以上に凶悪な誤判定やエラーに直面します。

今回は、フォルダ存在確認で陥りがちな罠を整理し、「契約による設計(DbC)」のコメントフォーマットに則った安全な汎用関数を作成します。

1. フォルダ存在確認で陥りやすい「3つの罠」

  • 罠①: Dir(path, vbDirectory) は「ファイル」もヒットしてしまう
    Dir("C:\Test\sample.txt", vbDirectory) のように、ファイルパスを渡しても sample.txt という文字列が返ってきます。つまり Dir の第2引数に vbDirectory を指定しても「フォルダのみを検索する」という意味にはならず、ファイルも判定対象に含まれてしまうため誤判定の原因になります。
  • 罠②: パス末尾の \(スラッシュ・バックスラッシュ)による挙動の違い
    GetAttr や Dir を使う際、C:\TestFolder\ のように末尾に \ が付いていると実行時エラー(エラー53: ファイルが見つかりません)になるというVBA独自の癖があります。ただし、C:\ や D:\ といったドライブルートだけは末尾の \ が必須であるため、一括で削除すると崩れてしまいます。
  • 罠③: 権限不足やネットワーク切断によるクラッシュ
    ファイル時と同様、アクセス権限がない共有フォルダや、切断されたネットワークドライブ(\\Server\Share)を指定すると、Dir や GetAttr が実行時エラー 70 や 76 でマクロを強制終了させます。

2. 実装:安全なフォルダ存在確認関数 (FolderExists)

これらの罠をすべてクリアし、ファイルとの誤認識を防ぎながら安全に Boolean(True / False)を返す関数です。

' ==============================================================================
' [機能名] 安全なフォルダ存在確認
'
' [処理内容]
'   ・指定されたパスに「フォルダ」が存在するかどうかを確認します。
'   ・ファイルパスが渡された場合や、アクセス権限不足・パス不正の場合は False を返します。
'
' [引数]
'   @param  {String} folderPath : 必須 : 確認対象のフォルダパス
'
' [事前条件 (Requires)]
'   ・特になし(空文字やファイルパス、末尾 \ あり/なし いずれも受け付けます)。
'
' [事後条件 (Ensures)]
'   ・指定パスにアクセス可能で、かつ「フォルダ」が存在する場合 : True を返す。
'   ・フォルダが存在しない、ファイルである、または権限不足でアクセス不可の場合 : False を返す。
'
' [戻り値]
'   @return {Boolean} フォルダの存在判定結果
'
' [例外処理 (Throws)]
'   ・アクセス権限エラー(70)やパス不在(53/76)等は内部で捕捉し、[WARNING] ログを出力して False を返す。
'
' [履歴]
'   2026/10/08  [作成者名]  新規作成
' ==============================================================================
Public Function FolderExists(ByVal folderPath As String) As Boolean
    ' 前後の空白を除去
    Dim cleanPath As String
    cleanPath = Trim(folderPath)
    
    ' 1. 空文字チェック
    If cleanPath = "" Then
        FolderExists = False
        Exit Function
    End If
    
    ' 2. パス区切り文字を '\' に統一 (/ を \ に変換)
    cleanPath = Replace(cleanPath, "/", "\")
    
    ' 3. 末尾の '\' の正規化
    ' ドライブルート (例: "C:\" や "D:\") 以外で、末尾に '\' がある場合は除去する
    ' (※ GetAttr は通常フォルダの末尾に '\' がついているとエラーになるため)
    If Right$(cleanPath, 1) = "\" And Not cleanPath Like "?:\" Then
        cleanPath = Left$(cleanPath, Len(cleanPath) - 1)
    End If
    
    ' 4. GetAttr による属性確認(エラーを内部でキャッチ)
    On Error Resume Next
    Dim fileAttr As VbFileAttribute
    fileAttr = GetAttr(cleanPath)
    
    Dim errNum As Long: errNum = Err.Number
    Dim errDesc As String: errDesc = Err.Description
    On Error GoTo 0
    
    ' エラーが発生した場合(パス未存在、アクセス権限不足など)
    If errNum <> 0 Then
        ' 存在しないパスの場合は静かに False(権限エラー等の場合はログを残す)
        If errNum <> 53 And errNum <> 76 Then
            Debug.Print "[WARNING] フォルダアクセス不可 (Err:" & errNum & " - " & errDesc & ") Path: " & cleanPath
        End If
        FolderExists = False
        Exit Function
    End If
    
    ' 5. ビット演算による「フォルダ属性」の確定
    ' 対象がファイルではなく、本当にフォルダ (vbDirectory) か判定
    If (fileAttr And vbDirectory) = vbDirectory Then
        FolderExists = True
    Else
        ' 存在するが「ファイル」だった場合は False
        FolderExists = False
    End If
End Function

3. 単体テスト用コード

正常なフォルダ、ドライブルート、末尾 \ 付きパス、存在しないフォルダ、そして「あえてファイルパスを渡すケース」などを網羅したテストプロシージャです。
エントリーポイント用のアスペクト(CAspect)を併用し、テスト実行ログを自動記録します。

' ==============================================================================
' [機能名] FolderExists 関数の網羅的テスト
' ==============================================================================
Public Sub Test_FolderExists()
    ' エントリーポイント用アスペクト(実行時間・ログ自動化)
    Dim aspect As New CAspect: aspect.Begin "Test_FolderExists"
    
    Debug.Print "=================================================="
    Debug.Print " FolderExists 単体テスト開始"
    Debug.Print "=================================================="
    
    ' 1. 正常系:自ブックが存在するフォルダ(末尾 \ なし)
    Dim validFolderPath As String
    validFolderPath = ThisWorkbook.Path
    
    If FolderExists(validFolderPath) Then
        Debug.Print "[PASS] 正常系: 既存フォルダ判定 OK"
    Else
        Debug.Print "[FAIL] 正常系: 既存フォルダ判定 NG"
    End If
    
    ' 2. 正常系:末尾に '\' が付いたフォルダパス
    If FolderExists(validFolderPath & "\") Then
        Debug.Print "[PASS] 表記ゆれ系: 末尾\付きフォルダ判定 OK"
    Else
        Debug.Print "[FAIL] 表記ゆれ系: 末尾\付きフォルダ判定 NG"
    End If
    
    ' 3. 正常系:ドライブルート (C:\)
    If FolderExists("C:\") Then
        Debug.Print "[PASS] ドライブルート: C:\ 判定 OK"
    Else
        Debug.Print "[FAIL] ドライブルート: C:\ 判定 NG"
    End If
    
    ' 4. 異常系:存在しないフォルダ
    If Not FolderExists("C:\NonExistentFolder_9999") Then
        Debug.Print "[PASS] 異常系: 存在しないフォルダ判定 OK"
    Else
        Debug.Print "[FAIL] 異常系: 存在しないフォルダ判定 NG"
    End If
    
    ' 5. 誤認識防止系:フォルダではなく「ファイルパス」を渡した場合
    Dim currentFilePath As String
    currentFilePath = ThisWorkbook.FullName
    
    If Not FolderExists(currentFilePath) Then
        Debug.Print "[PASS] ファイル排除系: ファイルパスの誤認排除 OK"
    Else
        Debug.Print "[FAIL] ファイル排除系: ファイルをフォルダと誤認識 NG"
    End If
    
    Debug.Print "=================================================="
End Sub

4. 実行結果(イミディエイトウィンドウ)

10:00:00 [START] Test_FolderExists (User: developer)
==================================================
 FolderExists 単体テスト開始
==================================================
[PASS] 正常系: 既存フォルダ判定 OK
[PASS] 表記ゆれ系: 末尾\付きフォルダ判定 OK
[PASS] ドライブルート: C:\ 判定 OK
[PASS] 異常系: 存在しないフォルダ判定 OK
[PASS] ファイル排除系: ファイルパスの誤認排除 OK
==================================================
10:00:00 [END]   Test_FolderExists (0.001s)

5. まとめ

  • GetAttr + ビット演算 (fileAttr And vbDirectory) が最強:Dir 関数に頼らず GetAttr を使うことで、ファイルとフォルダを厳密に区別できます。
  • 末尾の \ トリムは「ドライブルート除外」が必須:C:\Test の末尾 \ は削る必要がありますが、C:\ の \ を削ると C:(カレントドライブ相対パス)に意味が変わってしまうため、Not cleanPath Like "?:\" で条件分岐するのがポイントです。
  • エラーは関数内で隠蔽し、事後条件をシンプルに保つ:アクセス権限不足やネットワーク未接続などの予期せぬエラーをすべて内部で安全に処理(False を返却)することで、呼び出し側は If FolderExists(path) Then と書くだけで安全に後続処理へ進むことができます。

PR