和風スパゲティのレシピ

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

フォルダを統合する(同名のフォルダに移動する)

同じ名前のフォルダがある場所にフォルダを移動すると、

フォルダの統合ウィンドウ

こんな表示が出て、同名のファイルたちを統合できます。


こんな処理をVBAでやりたいとなった場合、
残念ながらこれを一発でやる機能は用意されていません。


よって、

  1. フォルダ内のすべてのファイルをループ
  2. 移動先に同名ファイルがあるか判定しスキップ/上書きして移動
  3. すべて終わったら元フォルダを削除

という手順で地道に実行する必要があります。

それを実行するコードを紹介します。


今回は「MoveFolderメソッド」でフォルダ移動のコードを書きましたが、
「CopyFolderメソッド」でも全く同じ処理ができます。


元フォルダを残してフォルダを統合したい場合は、
本記事のコードを単純に置換してご利用ください。


なお、本コードにはFileSystemObject(以下FSO)を使用します。

FSOについての解説はこちらを参照ください。
www.limecode.jp

移動先にファイルがあればスキップしてフォルダを統合する

上書きはせずスキップ(元フォルダのファイルは削除)する場合がこちらです。

Sub フォルダを移動して統合する_スキップ版()
    
    Dim FSO As New FileSystemObject
    
    Dim Fromフォルダ As Folder
    Set Fromフォルダ = FSO.GetFolder("C:\Users\○○\Desktop\親フォルダ1\対象フォルダ")
    Dim Toフォルダ As Folder
    Set Toフォルダ = FSO.GetFolder("C:\Users\○○\Desktop\親フォルダ2\対象フォルダ")
    
    ' 移動元フォルダにサブフォルダがあれば警告してExit
    If Fromフォルダ.SubFolders.Count > 0 Then
        MsgBox "サブフォルダがあるフォルダには実行できません。"
        Exit Sub
    End If
    
    ' 移動元フォルダのすべてのファイルをループ
    Dim Fromファイル As File
    For Each Fromファイル In Fromフォルダ.Files
        
        ' 移動先に同名ファイルがない場合のみ移動を実行
        If FSO.FileExists(Toフォルダ.Path & "\" & Fromファイル.Name) = False Then
            Fromファイル.Move Toフォルダ.Path & "\"
        End If
        
    Next
    
    ' 最後に移動元フォルダを削除
    Fromフォルダ.Delete
    
End Sub

※ FSOの基本に忠実なコードのため解説は割愛します。

移動先のファイルを上書きしてフォルダを統合する

移動時に上書きを行う場合はちょっとした小ワザが使えます。

MoveFolderメソッドには「上書き」の設定はないのですが、
CopyFolderメソッドには第3引数「OverWriteFiles」が用意されています。


これでは移動ではなくコピーなのですが、
コピー後にコピー元を消すことで疑似的に移動を実現できます。

Sub フォルダを移動して統合する_上書き版()
    
    Dim FSO As New FileSystemObject
    
    Dim Fromフォルダ As Folder
    Set Fromフォルダ = FSO.GetFolder("C:\Users\○○\Desktop\親フォルダ1\対象フォルダ")
    Dim Toフォルダ As Folder
    Set Toフォルダ = FSO.GetFolder("C:\Users\○○\Desktop\親フォルダ2\対象フォルダ")
    
    ' 移動元フォルダにサブフォルダがあれば警告してExit
    If Fromフォルダ.SubFolders.Count > 0 Then
        MsgBox "サブフォルダがあるフォルダには実行できません。"
        Exit Sub
    End If

    ' 上書きコピーしてから元のフォルダを消す
    FSO.CopyFolder Fromフォルダ.Path, Toフォルダ.Path, OverWriteFiles:=True
    Fromフォルダ.Delete

End Sub

ものすごくコードが短くなりましたね。


なお、この方法でOverWriteFilesをFalseにすれば、
スキップ版も実行できる気がしますが残念ながらできません。


OverWriteFilesをFalseにした場合はスキップではなくエラーになり、
On Error Resume Nextを設定してもCopyが実行されなくなるだけです。
※ 同名ファイルだけスキップして他はCopyという器用な動きには当然なりません。


よってこの手法が使えるのは「移動元で上書き」する場合のみです。


この条件を勘違いしそうで怖いという場合は、
小ワザに頼らないFor Each版も載せておきますのでこちらを採用して下さい。

Sub フォルダを移動して統合する_上書き版()
    
    Dim FSO As New FileSystemObject
    
    Dim Fromフォルダ As Folder
    Set Fromフォルダ = FSO.GetFolder("C:\Users\○○\Desktop\親フォルダ1\対象フォルダ")
    Dim Toフォルダ As Folder
    Set Toフォルダ = FSO.GetFolder("C:\Users\○○\Desktop\親フォルダ2\対象フォルダ")
    
    ' 移動元フォルダにサブフォルダがあれば警告してExit
    If Fromフォルダ.SubFolders.Count > 0 Then
        MsgBox "サブフォルダがあるフォルダには実行できません。"
        Exit Sub
    End If
    
    ' 移動元フォルダのすべてのファイルをループ
    Dim Fromファイル As File
    For Each Fromファイル In Fromフォルダ.Files
        
        ' 移動先に同名ファイルがあれば削除
        If FSO.FileExists(Toフォルダ.Path & "\" & Fromファイル.Name) Then
            FSO.DeleteFile Toフォルダ.Path & "\" & Fromファイル.Name
        End If
        
        ' 移動を実行
        Fromファイル.Move Toフォルダ.Path & "\"
        
    Next
    
    ' 最後に移動元フォルダを削除
    Fromフォルダ.Delete
    
End Sub

上書き・スキップを各個判定してフォルダを統合する

続いてファイルそれぞれで上書きを判定する場合です。
サンプルとして「更新日が後のファイルを採用」するコードを記載します。

これは手作業にはできない処理になりますね。

Sub フォルダを移動して統合する_更新日判定版()
    
    Dim FSO As New FileSystemObject
    
    Dim Fromフォルダ As Folder
    Set Fromフォルダ = FSO.GetFolder("C:\Users\○○\Desktop\親フォルダ1\対象フォルダ")
    Dim Toフォルダ As Folder
    Set Toフォルダ = FSO.GetFolder("C:\Users\○○\Desktop\親フォルダ2\対象フォルダ")
    
    ' 移動元フォルダにサブフォルダがあれば警告してExit
    If Fromフォルダ.SubFolders.Count > 0 Then
        MsgBox "サブフォルダがあるフォルダには実行できません。"
        Exit Sub
    End If
    
    ' 移動元フォルダのすべてのファイルをループ
    Dim Fromファイル As File
    For Each Fromファイル In Fromフォルダ.Files
        
        ' 移動先に同名ファイルがない場合は移動を実行
        If FSO.FileExists(Toフォルダ.Path & "\" & Fromファイル.Name) = False Then
            Fromファイル.Move Toフォルダ.Path & "\"
        
        ' 同名ファイルがある場合
        Else
            Dim Toファイル As File
            Set Toファイル = FSO.GetFile(Toフォルダ.Path & "\" & Fromファイル.Name)
            
            ' 移動元の更新日が大きい場合は移動先ファイルを削除してから移動
            If Fromファイル.DateLastModified > Toファイル.DateLastModified Then
                Toファイル.Delete
                Fromファイル.Move Toフォルダ.Path & "\"
            End If
            
        End If
        
    Next
    
    ' 最後に移動元フォルダを削除
    Fromフォルダ.Delete
    
End Sub

同名のファイルがあればファイル名に連番を付けてフォルダを統合する

最後にすべてのファイルを残すために、
ファイル名が重複しないよう連番付きにリネームするパターンです。

これも手作業にはできない処理になりますね。

Sub フォルダを移動して統合する_同名は連番付与版()
    
    Dim FSO As New FileSystemObject
    
    Dim Fromフォルダ As Folder
    Set Fromフォルダ = FSO.GetFolder("C:\Users\○○\Desktop\親フォルダ1\対象フォルダ")
    Dim Toフォルダ As Folder
    Set Toフォルダ = FSO.GetFolder("C:\Users\○○\Desktop\親フォルダ2\対象フォルダ")
    
    ' 移動元フォルダにサブフォルダがあれば警告してExit
    If Fromフォルダ.SubFolders.Count > 0 Then
        MsgBox "サブフォルダがあるフォルダには実行できません。"
        Exit Sub
    End If
    
    ' 移動元フォルダのすべてのファイルをループ
    Dim Fromファイル As File
    For Each Fromファイル In Fromフォルダ.Files
        
        ' 移動先に同名ファイルがなくなるまで連番を付与
        If FSO.FileExists(Toフォルダ.Path & "\" & Fromファイル.Name) Then
            Dim i As Long: i = 2
            Do
                Dim リネーム名 As String
                リネーム名 = FSO.GetBaseName(Fromファイル) & " (" & i & ")." _
                              & FSO.GetExtensionName(Fromファイル)
                If FSO.FileExists(Toフォルダ.Path & "\" & リネーム名) = False Then Exit Do
                i = i + 1
            Loop
            
            Fromファイル.Name = リネーム名
            
        End If
        
        ' 移動を実行
        Fromファイル.Move Toフォルダ.Path & "\"
        
    Next
    
    ' 最後に移動元フォルダを削除
    Fromフォルダ.Delete
    
End Sub

 
なお、この「同名ファイルがなくなるまで連番を付与する」処理は、
Workbook.SaveAsなどでも利用するため関数化すると便利です。

Public FSO As New FileSystemObject

Sub フォルダを移動して統合する_同名は連番付与版()
    
    Dim Fromフォルダ As Folder
    Set Fromフォルダ = FSO.GetFolder("C:\Users\○○\Desktop\親フォルダ1\対象フォルダ")
    Dim Toフォルダ As Folder
    Set Toフォルダ = FSO.GetFolder("C:\Users\○○\Desktop\親フォルダ2\対象フォルダ")
    
    ' 移動元フォルダにサブフォルダがあれば警告してExit
    If Fromフォルダ.SubFolders.Count > 0 Then
        MsgBox "サブフォルダがあるフォルダには実行できません。"
        Exit Sub
    End If
    
    ' 移動元フォルダのすべてのファイルをループ
    Dim Fromファイル As File
    For Each Fromファイル In Fromフォルダ.Files

        Fromファイル.Move Toフォルダ.Path & "\" _
            & Get重複がないよう付番したファイル名(Fromファイル.Name, Toフォルダ.Path)
        
    Next
    
    ' 最後に移動元フォルダを削除
    Fromフォルダ.Delete
    
End Sub

' ファイル名に連番を付与する汎用関数
Function Get重複がないよう付番したファイル名(元ファイル名 As String, フォルダパス As String) As String
    
    Dim ベース名 As String: ベース名 = FSO.GetBaseName(元ファイル名)
    Dim 拡張子 As String: 拡張子 = FSO.GetExtensionName(元ファイル名)
    
    Dim 決定ファイル名 As String: 決定ファイル名 = 元ファイル名
    
    If FSO.FileExists(フォルダパス & "\" & 決定ファイル名) Then
        Dim i As Long: i = 2
        Do
            Dim リネーム名 As String
            決定ファイル名 = ベース名 & " (" & i & ")." & 拡張子
            If FSO.FileExists(フォルダパス & "\" & 決定ファイル名) = False Then Exit Do
            i = i + 1
        Loop
    End If
    
    Get重複がないよう付番したファイル名 = 決定ファイル名
    
End Function

本体マクロの「Move」部分が1行になって非常に読みやすくなります。

かなり便利な関数ですので使ってみてください。


ついでですが、

Public FSO As New FileSystemObject

このようにPublic変数でFSOを宣言すれば、
この変数をすべてのプロシージャで使いまわせます

これも便利な手法ですので覚えておきましょう。
www.limecode.jp