今日の柴犬は、眠そうな顔をしています。もうすぐ寝そうです。
概要
「PowerShell」「バッチ .bat」とファイルのバックアップについて考えてみましたが、「EXCEL」で作るとどうなるか試してみました。
フォームも考えましたが、フォルダーにより情報が数百になることがありますので、シートで進めることにしました。
ダイヤログなどが使え、シートにバックアップ対象のフォルダーの中身をシートに書き出すこともできます。
ですので、一覧から選択することもできますのでかなり細かなことができます。
とりあえず、動きましたので記録します。
説明は後日徐々に追記したいと考えています。
今回お世話になった本です。
EXCELシート
次のようなエクセルシートを考えてみました。
セル「$C$1」の「フォルダーの一覧」をダブルクリックするとフォルダーを選択するダイヤログが開きますので選択します。
選択が完了すると選択したフォルダーの中にあるサブフォルダーとファイル名を探査しシートに表示します。
セル「$E$1」の「コピー先」をダブルクリックすると同様にダイヤログが開き、フォルダーを選択します。
選択が完了すると選択したフォルダーそのものがその下に表示されます。サブフォルダーではありません。
最上位にあるチェックボックスはすべてのチェックボックスにチェックを「入れる」または「外す」に使います。
セル「$A$1」の「コピー」をダブルクリックするとコピーを開始します。
VBエディターのコード
全てのコードは「ThisWorkbook」の中に記述しています。
シートのセルのダブルクリック時の直前に働くようにしてます。
「Cancel = True」はダブルクリックでセルが編集状態になるのをキャンセルするためです。
Option Explicit Private Sub Workbook_SheetBeforeDoubleClick(ByVal Sh As Object, _ ByVal Target As Range, _ Cancel As Boolean) Dim BaseCol As Long: BaseCol = 3 Dim PasteCol As Long: PasteCol = 5 Dim FolderPath As String Select Case Sh.Name Case "Sheet1" Select Case Target.Value Case "フォルダーの一覧" Cancel = True FolderPath = SelectedFolder If FolderPath = "" Then Exit Sub End If Sh.Activate Call WriteFolderCell(Sh.Name, BaseCol, FolderPath) Call ShmakeCB(Sh.Name, BaseCol) Case "コピー先" Cancel = True FolderPath = SelectedFolder If FolderPath = "" Then Exit Sub End If Sh.Activate Call WriteFolderCell(Sh.Name, PasteCol, FolderPath, 1) Case "コピー" Cancel = True 'コピーを実行します。 Call CopyRun(Sh.Name, BaseCol, BaseCol + 1, PasteCol) End Select End Select End Sub
ダイヤログを開きフォルダーを選択するようにして、選択したフォルダーのパスを含めた名前を返す関数にしてます。
'ダイヤログを表示します。 Public Function SelectedFolder() As String Dim RES As String RES = "" With Application.FileDialog(msoFileDialogFolderPicker) If .Show = True Then RES = .SelectedItems(1) End If End With SelectedFolder = RES End Function
オプションの仮引数を使って引数を渡さない場合、オプションの仮引数「0」になるようにしています。
引数を渡す場合、オプションの仮引数はその値になります。
「count」が「0」の場合は、選択したフォルダー・ファイル情報を書き出し、「指定値」がある場合は、コピー先フォルダーの書き出し回数を指定したものとしています。
データを1行毎にシートに書きこむのは時間がかかるので、配列にデータをセットして、配列を複写する方法を行っています。
'引数でフォルダーパスを渡し内部フォルダーをシートに書き込みます。 Public Sub WriteFolderCell(ByVal ShName As String, _ ByVal WriteCol As Long, _ ByVal FolderPath As String, _ Optional ByVal count As Long = 0) Dim FSO As Object Dim Folders As Object Dim Folder As Object Dim Files As Object Dim File As Object Dim Data() As String Dim DataR() As String Dim buf As String Dim EndRow As Long Dim iRow As Long Dim I As Long Dim J As Long Dim AddInd As Long '既存のフォルダーデータを消去します。 With Worksheets(ShName) .Range(.Cells(2, _ WriteCol), _ .Cells(ColEndRow(ShName, WriteCol), _ WriteCol + 1)).ClearContents End With 'フォルダーデータを得るためオブジェクトを作成します。 Set FSO = CreateObject("Scripting.FileSystemObject") Set Folders = FSO.GetFolder(FolderPath).SubFolders '配列が満杯になったら追加する配列の数を 200 とします。 AddInd = 200 'VBAの配列は2次元の場合、列方向のみにしか追加できませんので行列が逆転するようにします。 redim Data(WriteCol To WriteCol + 1, 2 To AddInd) '配列に書き込みます。 iRow = 2 If count = 0 Then ReDim Data(WriteCol To WriteCol + 1, 2 To iRow + AddInd) For Each Folder In Folders With Worksheets(ShName) Data(WriteCol, iRow) = Folder.Path '配列が満杯か確認します。 If iRow = UBound(Data, 2) Then ReDim Preserve Data(WriteCol To WriteCol + 1, 2 To iRow + AddInd) End If iRow = iRow + 1 End With Next Folder Set Files = FSO.GetFolder(FolderPath).Files For Each File In Files With Worksheets(ShName) Data(WriteCol + 1, iRow) = File.Path If iRow = UBound(Data, 2) Then ReDim Preserve Data(WriteCol To WriteCol + 1, 2 To iRow + AddInd) End If iRow = iRow + 1 End With Next File Else If count > 0 Then ReDim Data(WriteCol To WriteCol, iRow To iRow + count - 1) For I = 1 To count With Worksheets(ShName) Data(WriteCol, iRow) = FolderPath iRow = iRow + 1 End With Next I End If End If '配列をシートに複写するため、行列の逆転を戻します。 ReDim Preserve Data(LBound(Data, 1) To UBound(Data, 1), LBound(Data, 2) To iRow - 1) ReDim DataR(LBound(Data, 2) To iRow - 1, LBound(Data, 1) To UBound(Data, 1)) For I = LBound(Data, 1) To UBound(Data, 1) For J = LBound(Data, 2) To UBound(Data, 2) DataR(J, I) = Data(I, J) Next J Next I '配列をシートに複写します。 With Worksheets(ShName) .Range(.Cells(LBound(DataR, 1), _ LBound(DataR, 2)), _ .Cells(UBound(DataR, 1), _ UBound(DataR, 2))).Value = DataR End With 'オブジェクトの参照を破棄します。 If Not Files Is Nothing Then Set Files = Nothing End If Set Folders = Nothing Set FSO = Nothing End Sub
フォルダー・ファイル情報がある行にシェイプを作成します。
シェイプの名前は分離記号を「-」とし、その後「行番号」を続け、「行番号」を紐づけた形式にしています。
'シェイプ作成処理の入口です。 Public Sub ShmakeCB(ByVal ShName As String, _ ByVal FilePathCol As Long) Dim EndRow As Long Dim I As Long '最終行を取得します。 EndRow = ColEndRow(ShName, FilePathCol) '既存の一部を除いてシェイプを削除します。 Call CelldeleteCB(ShName, "test") '判定列の個々のセルに値があれば、左横のセルにチェックボックスを作成します。 'シェイプの名前は「test-」に行番号を結合したものとします。 For I = 2 To EndRow If CellExistVal(ShName, I, FilePathCol) Or _ CellExistVal(ShName, I, FilePathCol + 1) Then Call CellmakeCB(Worksheets(ShName).Cells(I, FilePathCol - 1), "test-" & I, "") End If Next I End Sub
作成するセルの位置大きさに合わせてチェックボックスを作成します。
'個々のシェイプを作成します。 Public Sub CellmakeCB(ByRef obCell As Range, _ ByVal CBName As String, _ ByVal MacName As String) Dim Sh As Worksheet Set Sh = obCell.Parent '作成するセルの大きさに合わせてチェックボックスを作ります。 With Sh.CheckBoxes.Add(obCell.Left, _ obCell.Top, _ obCell.Width, _ obCell.Height) .Name = CBName .Caption = "" .Value = xlOn .OnAction = MacName End With End Sub
作成の更新の前に、既存のシェイプを一括削除します。
'指定のシェイプを削除します。 Public Sub CelldeleteCB(ByVal ShName As String, _ ByVal CBName As String) Dim Sh As Worksheet Dim Sp As Shape Dim ObjRange As Object Dim shp() As String Dim icount As Long Set Sh = Worksheets(ShName) '削除の対象となるシェイプの一覧表を取得します。 icount = 0 For Each Sp In Sh.Shapes If InStr(1, Sp.Name, CBName) > 0 Then ReDim Preserve shp(icount) shp(icount) = Sp.Name icount = icount + 1 End If Next Sp '削除の対象を選択して削除を実行します。 If icount > 0 Then Set ObjRange = Sh.Shapes.Range(shp) If Not ObjRange Is Nothing Then ObjRange.Select ObjRange.Delete Set ObjRange = Nothing End If End If End Sub
指定列の最終行を返す関数です。
書式、数式により値がなくても最終行とされるので、とりあえず得られた最終行を遡って値がある最終行を求めています。
'シートの指定する列番号の最終行を返す関数 Public Function ColEndRow(ByVal ShName As String, _ ByVal ColNum As Long) As Long Dim RES As Long Dim buf As Long RES = 0 With Sheets(ShName) RES = .Cells(.Rows.count, ColNum).End(xlUp).Row buf = .Cells(.Rows.count, ColNum + 1).End(xlUp).Row If RES < buf Then RES = buf End If '上で得られた値は、書式で設定があるときの最終行で空である場合がある Do While .Cells(RES, ColNum).Value = "" And _ .Cells(RES, ColNum + 1).Value = "" And _ RES > 1 RES = RES - 1 Loop End With ColEndRow = RES End Function
指定するセルに値があるかを返す関数です。
'シートのセル cells(Row, Col) に値の有無を返す関数 Public Function CellExistVal(ByVal ShName As String, _ ByVal Rownum As Long, _ ByVal ColNum As Long) As Boolean Dim RES As Long RES = False RES = (Worksheets(ShName).Cells(Rownum, ColNum).Value <> "") CellExistVal = RES End Function
フォルダー・ファイル情報からコピー先のホルダーに複写します。
チェックボックスにチェックが入ったもののすべての行番号を配列にセットした後、配列から順次値と取り出して複写を実行しています。
'コピーを実行します Public Sub CopyRun(ByVal ShName As String, _ ByVal FolderCol As Long, _ ByVal FileCol As Long, _ ByVal PasteCol As Long) Dim Sh As Worksheet Dim Sp As Shape Dim val As Variant Dim FSO As Object Dim EndRow As Long Dim iRow As Long Dim I As Long Dim Copytarget As String Dim PasteFolder As String Dim PasteFile As String Dim list() As Long '最終行を取得します。 EndRow = ColEndRow(ShName, FolderCol) Set Sh = Worksheets(ShName) 'チェックボックスにチェックがはいったもののシェイプを拾います。 'そしてシェイプ名に紐づけられた行番号を配列に格納します。 I = 0 ReDim list(0 To EndRow - 1) For Each Sp In Sh.Shapes If InStr(1, Sp.Name, "test-") > 0 Then If Sh.CheckBoxes(Sp.Name).Value = xlOn Then val = Split(Sp.Name, "-") list(I) = val(1) I = I + 1 End If End If Next Sp '少なくとも一つなければなりません。 If I = 0 Then Exit Sub End If '余分な配列を削除します。 ReDim Preserve list(0 To I - 1) 'フォルダーデータを得るためオブジェクトを作成します。 Set FSO = CreateObject("Scripting.FileSystemObject") iRow = 2 With Worksheets(ShName) PasteFolder = .Cells(iRow, PasteCol).Value PasteFile = .Cells(iRow, PasteCol).Value & "\" '順次コピーを実行します。 For I = 0 To UBound(list) Copytarget = .Cells(list(I), FolderCol).Value If Copytarget <> "" Then If Dir(Copytarget, vbDirectory) <> "" Then FSO.GetFolder(Copytarget).Copy PasteFolder End If End If Copytarget = .Cells(list(I), FileCol).Value If Copytarget <> "" Then If Dir(Copytarget, vbNormal) <> "" Then FSO.GetFile(Copytarget).Copy PasteFile End If End If Next I End With 'オブジェクトの参照を破棄します。 Set FSO = Nothing End Sub
最上位のチェックボックスのマクロ
標準モジュールを追加してその中に記述しています。
行っていることは、単に最上位のチェックボックスの状態の値を代入しているだけです。
Option Explicit Public Sub CBValSet_Click() Dim Sp As Shape With ActiveSheet For Each Sp In .Shapes If InStr(1, Sp.Name, "test") > 0 Then .CheckBoxes(Sp.Name).Value = .CheckBoxes("CBValSet").Value End If Next Sp End With End Sub