【VBA】日付をキーにした複数ファイルの収集

処理の流れ

設定シート(Excel)から条件を読み込み → バリデーション → ネットワーク上のフォルダをスキャン → 条件に合うPDFをローカルにコピー → ログ記録、という一連の流れです。

スキャンの仕組み

まず Dir("*.pdf") でファイル名だけを高速に一括取得し、次にファイル名が除外パターン(LL* など)に一致するものをスキップ。残ったファイルだけ FSO.GetFile で作成日を取得して日付範囲内かチェックします。除外パターンに一致したファイルはFSO呼び出し自体をスキップするので、ネットワーク越しのアクセスを減らせます。

安全対策

コピー先が \\ で始まるネットワークパスだとエラーで停止(共有フォルダへの誤コピー防止)。実行前に対象ファイル一覧・件数・除外件数を確認ダイアログで表示し、デフォルトが「いいえ」なので手が滑っても実行されません。

設定シートで指定できる項目

コピー元パス、コピー先ベースパス、日付フォルダ名(2026_0302 等)、ファイルパターン、作成日 From/To、サブフォルダ検索(デフォルトOFF)、除外ファイル名パターン(複数指定可)の7項目です。

ログ

実行ごとに「ログ」シートへ追記され、各ファイルの成功/失敗とエラー内容が記録されます。


'==============================================================================
' PDFデータ コピーツー
'
' 機能: 設定シートの条件に基づきコピー元フォルダから対象ファイルを
'       コピー先のローカルフォルダへコピーする
'
' 高速化: Dir()でファイル名を一括取得後FSO.GetFileで作成日チェック
' 安全性: コピー先ローカル確認、実行前確認ダイアログ、ログ記
'==============================================================================
Option Explicit
Option Compare Text  ' 大文字小文字を区別しない(Like演算子・文字列比較

Private m_fso As Object
Private m_copyList As Collection
Private m_excludePatterns() As String
Private m_excludeCount As Long
Private m_fileExt As String
Private m_dateFrom As Date
Private m_dateTo As Date
Private m_totalScanned As Long
Private m_excludedFileCount As Long  ' 除外されたファイル数カウン

'==============================================================================
' メインマクロ: PDFデータコピー実
'==============================================================================
Public Sub PDFデータコピー実行()

    Dim ws As Worksheet
    On Error Resume Next
    Set ws = ThisWorkbook.Sheets("設定")
    On Error GoTo 0
    If ws Is Nothing Then
        MsgBox "「設定」シートが見つかりません。", vbCritical, "エラー"
        Exit Sub
    End If

    '----------------------------------------------------------------------
    ' 設定値の読
    '----------------------------------------------------------------------
    Dim srcPath As String:    srcPath = Trim(CStr(ws.Range("C4").Value))
    Dim dstBase As String:    dstBase = Trim(CStr(ws.Range("C5").Value))
    Dim dateFolder As String: dateFolder = Trim(CStr(ws.Range("C6").Value))
    m_fileExt = Trim(CStr(ws.Range("C7").Value))
    If m_fileExt = "" Then m_fileExt = "*.pdf"

    ' 日付の読込と検
    If Not IsDate(ws.Range("C8").Value) Then
        MsgBox "作成日(From)に有効な日付を入力してください。", vbCritical, "入力エラー"
        ws.Range("C8").Select
        Exit Sub
    End If
    If Not IsDate(ws.Range("C9").Value) Then
        MsgBox "作成日(To)に有効な日付を入力してください。", vbCritical, "入力エラー"
        ws.Range("C9").Select
        Exit Sub
    End If
    m_dateFrom = CDate(ws.Range("C8").Value)
    m_dateTo = CDate(ws.Range("C9").Value)

    Dim recurse As Boolean
    On Error Resume Next
    recurse = CBool(ws.Range("C10").Value)
    On Error GoTo 0

    '----------------------------------------------------------------------
    ' 除外ファイル名パターンの読
    '----------------------------------------------------------------------
    m_excludeCount = 0
    ReDim m_excludePatterns(0 To 99)
    Dim r As Long: r = 13
    Do While Trim(CStr(ws.Cells(r, 3).Value)) <> ""
        m_excludePatterns(m_excludeCount) = Trim(CStr(ws.Cells(r, 3).Value))
        m_excludeCount = m_excludeCount + 1
        r = r + 1
        If m_excludeCount > 99 Then Exit Do
    Loop

    '----------------------------------------------------------------------
    ' バリデーショ
    '----------------------------------------------------------------------
    ' パス末尾の \ を除
    If Len(srcPath) > 0 And Right(srcPath, 1) = "\" Then srcPath = Left(srcPath, Len(srcPath) - 1)
    If Len(dstBase) > 0 And Right(dstBase, 1) = "\" Then dstBase = Left(dstBase, Len(dstBase) - 1)

    ' 必須項目チェッ
    If srcPath = "" Then
        MsgBox "コピー元フォルダが未入力です。", vbCritical, "入力エラー"
        ws.Range("C4").Select: Exit Sub
    End If
    If dstBase = "" Then
        MsgBox "コピー先ベースフォルダが未入力です。", vbCritical, "入力エラー"
        ws.Range("C5").Select: Exit Sub
    End If
    If dateFolder = "" Then
        MsgBox "日付フォルダ名が未入力です。", vbCritical, "入力エラー"
        ws.Range("C6").Select: Exit Sub
    End If

    ' コピー先がローカルか確認(ネットワークパス \\... を拒否
    If Left(dstBase, 2) = "\\" Then
        MsgBox "【安全確認エラー】" & vbCrLf & vbCrLf & _
               "コピー先にネットワークパス(\\...)が指定されています。" & vbCrLf & _
               "誤って共有フォルダに保存しないよう、" & vbCrLf & _
               "コピー先はローカルフォルダ(例: C:\Data)を指定してください。", _
               vbCritical, "安全確認エラー"
        ws.Range("C5").Select
        Exit Sub
    End If
    If Len(dstBase) < 2 Or Mid(dstBase, 2, 1) <> ":" Then
        MsgBox "コピー先はドライブレター付きのローカルパスを指定してください。" & vbCrLf & _
               "例: C:\Users\ユーザー名\Desktop\Archive", vbCritical, "入力エラー"
        ws.Range("C5").Select
        Exit Sub
    End If

    ' 日付の整合性チェッ
    If m_dateFrom > m_dateTo Then
        MsgBox "作成日の From が To より後の日付になっています。", vbCritical, "入力エラー"
        Exit Sub
    End If

    ' コピー元の存在確
    Set m_fso = CreateObject("Scripting.FileSystemObject")
    If Not m_fso.FolderExists(srcPath) Then
        MsgBox "コピー元フォルダが見つかりません:" & vbCrLf & srcPath, vbCritical, "エラー"
        Exit Sub
    End If

    '----------------------------------------------------------------------
    ' ファイルスキャン(高速化: Dir()で名前取得 → FSO で作成日チェック
    '----------------------------------------------------------------------
    Set m_copyList = New Collection
    m_totalScanned = 0
    m_excludedFileCount = 0

    Application.StatusBar = "ファイルスキャン中... (ネットワークフォルダの場合、時間がかかることがあります)"
    DoEvents

    Dim startTime As Double: startTime = Timer

    ScanFolder srcPath, recurse

    Dim elapsed As Double: elapsed = Timer - startTime
    Application.StatusBar = False

    '----------------------------------------------------------------------
    ' スキャン結果の確
    '----------------------------------------------------------------------
    If m_copyList.Count = 0 Then
        MsgBox "条件に一致するファイルが見つかりませんでした。" & vbCrLf & vbCrLf & _
               "スキャンファイル数: " & m_totalScanned & vbCrLf & _
               "スキャン時間: " & Format(elapsed, "0.0") & "" & vbCrLf & vbCrLf & _
               "設定を確認してください:" & vbCrLf & _
               "  - ファイルパターン: " & m_fileExt & vbCrLf & _
               "  - 作成日範囲: " & Format(m_dateFrom, "yyyy/mm/dd") & "" & Format(m_dateTo, "yyyy/mm/dd"), _
               vbInformation, "結果"
        Exit Sub
    End If

    Dim dstPath As String: dstPath = dstBase & "\" & dateFolder

    '----------------------------------------------------------------------
    ' 確認ダイアログ(ファイル一覧表示
    '----------------------------------------------------------------------
    Dim msg As String
    msg = "以下の条件でコピーを実行します。よろしいですか?" & vbCrLf & vbCrLf
    msg = msg & "【コピー元】" & vbCrLf & "  " & srcPath & vbCrLf
    msg = msg & "【コピー先】" & vbCrLf & "  " & dstPath & vbCrLf
    msg = msg & "【対象】 " & m_copyList.Count & " 件 (スキャン: " & m_totalScanned & " 件 / " & Format(elapsed, "0.0") & "秒)" & vbCrLf
    msg = msg & "【作成日】 " & Format(m_dateFrom, "yyyy/mm/dd") & "" & Format(m_dateTo, "yyyy/mm/dd") & vbCrLf
    If m_excludeCount > 0 Then
        msg = msg & "【除外ファイル】 " & m_excludedFileCount & " 件除外済 (パターン: "
        Dim p As Long
        For p = 0 To m_excludeCount - 1
            If p > 0 Then msg = msg & ", "
            msg = msg & m_excludePatterns(p)
        Next p
        msg = msg & ")" & vbCrLf
    End If
    msg = msg & vbCrLf & "--- 対象ファイル ---" & vbCrLf

    Dim i As Long
    Dim maxShow As Long: maxShow = 25
    For i = 1 To WorksheetFunction.Min(m_copyList.Count, maxShow)
        msg = msg & "  " & m_fso.GetFileName(CStr(m_copyList(i))) & vbCrLf
    Next i
    If m_copyList.Count > maxShow Then
        msg = msg & "  ... 他 " & (m_copyList.Count - maxShow) & " ファイル" & vbCrLf
    End If

    If MsgBox(msg, vbYesNo + vbQuestion + vbDefaultButton2, "コピー実行確認") <> vbYes Then
        MsgBox "キャンセルしました。", vbInformation, "中断"
        Exit Sub
    End If

    '----------------------------------------------------------------------
    ' コピー実
    '----------------------------------------------------------------------
    ' コピー先フォルダ作
    If Not m_fso.FolderExists(dstPath) Then
        CreateFolderRecursive dstPath
    End If

    Dim copied As Long: copied = 0
    Dim failed As Long: failed = 0
    Dim logData As Collection
    Set logData = New Collection

    For i = 1 To m_copyList.Count
        Dim srcFile As String: srcFile = CStr(m_copyList(i))
        Dim fileName As String: fileName = m_fso.GetFileName(srcFile)
        Dim dstFile As String: dstFile = dstPath & "\" & fileName

        Application.StatusBar = "コピー中... (" & i & "/" & m_copyList.Count & ") " & fileName
        DoEvents

        On Error Resume Next
        m_fso.CopyFile srcFile, dstFile, True
        If Err.Number = 0 Then
            copied = copied + 1
            logData.Add Array(Format(Now, "yyyy/mm/dd hh:nn:ss"), "OK", fileName, srcFile, "")
        Else
            failed = failed + 1
            logData.Add Array(Format(Now, "yyyy/mm/dd hh:nn:ss"), "NG", fileName, srcFile, Err.Description)
            Err.Clear
        End If
        On Error GoTo 0
    Next i

    Application.StatusBar = False

    '----------------------------------------------------------------------
    ' ログ書
    '----------------------------------------------------------------------
    WriteLog ws, logData, srcPath, dstPath

    '----------------------------------------------------------------------
    ' 完了メッセー
    '----------------------------------------------------------------------
    Dim icon As VbMsgBoxStyle
    icon = IIf(failed > 0, vbExclamation, vbInformation)

    Dim doneMsg As String
    doneMsg = "コピー完了!" & vbCrLf & vbCrLf & _
              "成功: " & copied & " ファイル" & vbCrLf & _
              "失敗: " & failed & " ファイル" & vbCrLf & vbCrLf & _
              "コピー先: " & dstPath & vbCrLf & vbCrLf & _
              "コピー先フォルダを開きますか?"

    If MsgBox(doneMsg, vbYesNo + icon, "処理完了") = vbYes Then
        Shell "explorer.exe """ & dstPath & """", vbNormalFocus
    End If

    Set m_fso = Nothing
    Set m_copyList = Nothing

End Sub

'==============================================================================
' フォルダスキャン(再帰対応
' Dir() でファイル名を一括取得し、FSO.GetFile で作成日チェッ
' ※ Dir() はネストできないため、ファイル名→サブフォルダ名の順に収集してから処
'==============================================================================
Private Sub ScanFolder(ByVal folderPath As String, ByVal recurse As Boolean)

    '--- ステップ1: Dir() でファイル名を高速収集 ---
    Dim fileNames As Collection
    Set fileNames = New Collection

    On Error Resume Next
    Dim fn As String
    fn = Dir(folderPath & "\" & m_fileExt)
    If Err.Number <> 0 Then
        Err.Clear
        On Error GoTo 0
        Exit Sub
    End If
    On Error GoTo 0

    Do While fn <> ""
        fileNames.Add fn
        fn = Dir()
    Loop

    '--- ステップ2: ファイル名除外 → 作成日でフィルタ ---
    Dim i As Long
    Dim dateTo_end As Date
    dateTo_end = DateAdd("d", 1, m_dateTo)  ' To の日も含む(翌日未満で判定

    For i = 1 To fileNames.Count
        m_totalScanned = m_totalScanned + 1

        ' ファイル名が除外パターンに一致 → スキッ
        If IsExcludedFile(CStr(fileNames(i))) Then
            m_excludedFileCount = m_excludedFileCount + 1
            GoTo NextFile
        End If

        Dim fullPath As String
        fullPath = folderPath & "\" & fileNames(i)

        On Error Resume Next
        Dim f As Object
        Set f = m_fso.GetFile(fullPath)
        If Err.Number = 0 Then
            Dim createdDate As Date
            createdDate = f.DateCreated
            If Err.Number = 0 Then
                If createdDate >= m_dateFrom And createdDate < dateTo_end Then
                    m_copyList.Add fullPath
                End If
            End If
        End If
        Err.Clear
        On Error GoTo 0
        Set f = Nothing

NextFile:
        ' 進捗表示(50件ごと
        If m_totalScanned Mod 50 = 0 Then
            Application.StatusBar = "スキャン中... " & m_totalScanned & " ファイル処理 / " & m_copyList.Count & " 件該当"
            DoEvents
        End If
    Next i

    '--- ステップ3: サブフォルダ収集(再帰する場合のみ) ---
    If recurse Then
        Dim subFolders As Collection
        Set subFolders = New Collection

        ' FSO経由でサブフォルダを取得(Dir()より確実
        On Error Resume Next
        Dim parentFolder As Object
        Set parentFolder = m_fso.GetFolder(folderPath)
        If Err.Number <> 0 Then
            Err.Clear
            On Error GoTo 0
            Exit Sub
        End If
        On Error GoTo 0

        Dim subFolder As Object
        For Each subFolder In parentFolder.SubFolders
            subFolders.Add subFolder.Name
        Next subFolder
        Set parentFolder = Nothing

        '--- ステップ4: サブフォルダを再帰処理 ---
        For i = 1 To subFolders.Count
            ScanFolder folderPath & "\" & subFolders(i), True
        Next i
    End If

End Sub

'==============================================================================
' 除外ファイル判定(Like演算子 / Option Compare Text で大文字小文字区別なし
' ファイル名(拡張子含む)に対してパターンマッ
'==============================================================================
Private Function IsExcludedFile(ByVal fileName As String) As Boolean
    Dim i As Long
    For i = 0 To m_excludeCount - 1
        If fileName Like m_excludePatterns(i) Then
            IsExcludedFile = True
            Exit Function
        End If
    Next i
    IsExcludedFile = False
End Function

'==============================================================================
' フォルダ再帰作
'==============================================================================
Private Sub CreateFolderRecursive(ByVal path As String)
    If m_fso.FolderExists(path) Then Exit Sub

    Dim parent As String
    parent = m_fso.GetParentFolderName(path)
    If Not m_fso.FolderExists(parent) Then
        CreateFolderRecursive parent
    End If

    m_fso.CreateFolder path
End Sub

'==============================================================================
' ログ書込(「ログ」シートに追記
'==============================================================================
Private Sub WriteLog(ByVal wsSetting As Worksheet, ByVal logData As Collection, _
                     ByVal srcPath As String, ByVal dstPath As String)

    Dim wsLog As Worksheet
    On Error Resume Next
    Set wsLog = ThisWorkbook.Sheets("ログ")
    On Error GoTo 0

    ' ログシートがなければ作
    If wsLog Is Nothing Then
        Set wsLog = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
        wsLog.Name = "ログ"
        wsLog.Range("A1").Value = "日時"
        wsLog.Range("B1").Value = "結果"
        wsLog.Range("C1").Value = "ファイル名"
        wsLog.Range("D1").Value = "フルパス"
        wsLog.Range("E1").Value = "備考"
        wsLog.Range("A1:E1").Font.Bold = True
    End If

    ' AutoFilterが有効な場合は解除(書込エラー防止
    On Error Resume Next
    If wsLog.AutoFilterMode Then wsLog.AutoFilterMode = False
    On Error GoTo 0

    ' 最終行の次から書
    Dim startRow As Long
    startRow = wsLog.Cells(wsLog.Rows.Count, 1).End(xlUp).Row + 1
    If startRow < 2 Then startRow = 2

    ' 空行を1行挟む(2回目以降の実行時に見やすく
    If startRow > 2 Then startRow = startRow + 1

    ' 実行情報ヘッダー(先頭に = を使わない → 数式誤認防止
    wsLog.Cells(startRow, 1).Value = "[実行] " & Format(Now, "yyyy/mm/dd hh:nn:ss")
    wsLog.Cells(startRow, 2).Value = srcPath
    wsLog.Cells(startRow, 3).Value = dstPath
    wsLog.Range(wsLog.Cells(startRow, 1), wsLog.Cells(startRow, 5)).Font.Bold = True
    wsLog.Range(wsLog.Cells(startRow, 1), wsLog.Cells(startRow, 5)).Interior.Color = RGB(230, 230, 230)
    startRow = startRow + 1

    ' 各ファイルのロ
    Dim i As Long
    For i = 1 To logData.Count
        Dim d As Variant: d = logData(i)
        wsLog.Cells(startRow, 1).Value = d(0)  '
        wsLog.Cells(startRow, 2).Value = d(1)  '
        wsLog.Cells(startRow, 3).Value = d(2)  ' ファイル
        wsLog.Cells(startRow, 4).Value = d(3)  ' フルパ
        wsLog.Cells(startRow, 5).Value = d(4)  '

        ' NG の場合は赤文
        If d(1) = "NG" Then
            wsLog.Range(wsLog.Cells(startRow, 1), wsLog.Cells(startRow, 5)).Font.Color = RGB(204, 0, 0)
        End If

        startRow = startRow + 1
    Next i

    ' 列幅調
    wsLog.Columns("A:E").AutoFit

    ' 設定シートに戻
    wsSetting.Activate

End Sub

'==============================================================================
' ユーティリティ: 日付フォルダ名を今日の日付に更
'==============================================================================
Public Sub 日付フォルダ名を今日に更新()
    Dim ws As Worksheet
    On Error Resume Next
    Set ws = ThisWorkbook.Sheets("設定")
    On Error GoTo 0
    If ws Is Nothing Then Exit Sub

    ws.Range("C6").Value = Format(Date, "yyyy\_mmdd")
    MsgBox "日付フォルダ名を「" & ws.Range("C6").Value & "」に更新しました。", vbInformation, "更新完了"
End Sub

'==============================================================================
' ユーティリティ: コピー先フォルダを開
'==============================================================================
Public Sub コピー先フォルダを開く()
    Dim ws As Worksheet
    On Error Resume Next
    Set ws = ThisWorkbook.Sheets("設定")
    On Error GoTo 0
    If ws Is Nothing Then Exit Sub

    Dim dstPath As String
    dstPath = Trim(CStr(ws.Range("C5").Value)) & "\" & Trim(CStr(ws.Range("C6").Value))

    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")

    If fso.FolderExists(dstPath) Then
        Shell "explorer.exe """ & dstPath & """", vbNormalFocus
    Else
        MsgBox "フォルダが見つかりません:" & vbCrLf & dstPath, vbExclamation, "エラー"
    End If
End Sub

コメント

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