和風スパゲティのレシピ

日本語でコーディングするExcelVBA

フォルダ内のファイル一覧を取得する汎用関数

フォルダ内の複数のファイルへ同じ処理を行う場合、

ファイルを走査するループ
┗ 各ファイルへの処理

という構成でコードを書きがちですが、これを変えて

まずは対象ファイルのリストを取得
そのリスト内の全ファイルへ処理

このように処理を二段階にして作ることもできます。


こうすることで、

  • ファイルの検索とファイルへの処理を別々に実装できる
  • 処理が途中で停止したときにも再開しやすい
  • リストを手で作って渡すなど応用が利きやすい
  • ループが長大にならないためコードが読みやすい

等の様々な利点を得ることができます。


リスト化にはいろいろ種類があり、
例えば「ファイルパス」を文字列として持つ配列などが便利でしょう。

今回はこの「対象ファイルのリスト化」を行う部分を汎用関数化したいと思います。

ファイル一覧取得関数の仕様

今回はFileSystemObjectCollectionを使用して実装します。

「対象のフォルダパス」と「検索条件」を引数として渡すと、
「対象ファイルのFileオブジェクト」をリスト化して返してくれる関数にしました。


実際の使用例として、
「名前が2025から始まるPDFファイルのうち作成日が10月より前のファイルを削除」
してみましょう。

↓こんな風に使うことができる関数になります。

Sub 名称が2025から始まるPDFファイルのうち作成日が10月以前のファイルを削除する()
    
    ' 汎用関数で対象ファイル群をCollectionとして取得
    Dim Clct対象ファイル As Collection
    Set Clct対象ファイル = GetCollectionフォルダ内ファイルリスト( _
        "C:\Users\wfsp\Desktop\テストフォルダ", "2025*", "PDF")
    
    ' 対象ファイルのうち作成日が10月以前のものを削除
    Dim 対象ファイル As File
    For Each 対象ファイル In Clct対象ファイル
        
        If Month(対象ファイル.DateCreated) <= 10 Then
            対象ファイル.Delete
        End If
        
    Next
    
End Sub

なかなか便利そうではないでしょうか?

関数の引数には、

  • 対象のフォルダパス:C:\Users\wfsp\Desktop\テストフォルダ
  • 検索条件:2025*
  • 対象拡張子:PDF

を渡しており、これに合致するコレクションを作っているのがすぐわかります。


その後コレクション内をFor Eachで回しているのですが、
ここでFileオブジェクトになっていることが活き、

  • DateCreatedプロパティで作成日時を確認可能
  • Deleteメソッドでそのファイルを簡単に削除可能

と、情報の取得やファイル操作をそのまま実行することができます。


このようなリスト化する関数は対象をオブジェクトとして取得しておくことで、
その後の処理をかなり書きやすくしてくれる効果があります。

気に入ったら皆さんもお持ち帰りいただき、
自分なりにカスタマイズしてみてください。


またその他の機能として第4引数で「下層フォルダまで探しに行くか」を指定でき、
Trueを渡せばフォルダ内のフォルダも再帰的に検索してくれるようになります。

こちらも必要に応じて使用してください。


なお、本関数はFileSystemObjectを使用しておりますので、
使用する場合はScripting Runtimeの参照設定をONにしてください。

参照設定についてはこちらをどうぞ。

ソースコード

Public FSO As New FileSystemObject

' フォルダ内のファイルリストの取得
Function GetCollectionフォルダ内ファイルリスト(Path対象フォルダ As String _
    , Optional ByVal ファイルLike条件 As String = "*", Optional 対象拡張子 As String = "xlsx" _
    , Optional is下層フォルダまで取得 As Boolean = False) As Collection
    
    ' ファイルが存在しなくても空のコレクションは返す(Nothingは返さない)
    Set GetCollectionフォルダ内ファイルリスト = New Collection
    
    ' 返り値用コレクション
    Dim Clct対象ファイル As New Collection
    
    ' 対象フォルダを取得
    If FSO.FolderExists(Path対象フォルダ) = False Then Exit Function
    Dim 対象フォルダ As Folder
    Set 対象フォルダ = FSO.GetFolder(Path対象フォルダ)

    ' 指定フォルダ内のすべてのファイルを走査
    Dim ファイル As File
    For Each ファイル In 対象フォルダ.Files
        
        ' 条件を満たすファイルの情報をCollectionに格納して配列へ格納
        If ファイル.Name Like ファイルLike条件 & "." & 対象拡張子 Then
            Clct対象ファイル.Add ファイル
        End If

    Next ' 指定フォルダ内のすべてのファイルを走査
    
    ' 下層フォルダも取得する場合の再帰処理
    If is下層フォルダまで取得 Then
        
        Dim 子フォルダ As Folder
        For Each 子フォルダ In 対象フォルダ.SubFolders
            
            ' 子フォルダへ本関数を再帰実行してDictionaryを取得
            Dim Clct子フォルダ対象ファイル As Collection
            Set Clct子フォルダ対象ファイル = GetCollectionフォルダ内ファイルリスト _
                (子フォルダ.Path, ファイルLike条件, 対象拡張子, True)
            
            ' 子フォルダのDictionaryを本関数のDictionaryと結合
            For Each ファイル In Clct子フォルダ対象ファイル
                Clct対象ファイル.Add ファイル
            Next
    
        Next ' 指定フォルダ内のすべてのファイルを走査
        
    End If
    
    ' 最終結果を返す
    Set GetCollectionフォルダ内ファイルリスト = Clct対象ファイル
End Function

コードの解説

FileSystemObjectを使った教科書的なコードです。

特別な処理は特に行っていません。


設計の話になりますが、今回の返り値をCollectionとしているのは、
For Each ステートメントに「Fileオブジェクト」を渡すためです。


というのもFor Each ステートメントは、

  • 配列の中身を回すときはVariant型限定
  • Collectionの中身を回すときはObject型もOK

という違いがあり、↓のように書けるのはCollectionだけなのです。

Dim 対象ファイル As File ' ← これを型指定できるのはCollectionだけ
For Each 対象ファイル In Clct対象ファイル
    
    If Month(対象ファイル.DateCreated) <= 10 Then
        対象ファイル.Delete
    End If
    
Next

 
これがもし返り値が「一次元配列」になっていると、

Dim 対象ファイル As Variant ' ← ここがかわる
For Each 対象ファイル In Clct対象ファイル

こう書く必要が出るということです。


こうなるとFileオブジェクトの選択肢が出てくれなくなるため、
例えば.DateCreatedは手打ちになるし、打ち間違えてもエラーが出なくなります。

これではせっかくFileオブジェクトにした意味がありませんよね。


特定のオブジェクト型をFor Each文で扱おうと思った場合は、
Collection限定となることを覚えておいてください。