【VBA】サーバのファイルをローカルにコピー
Yahoo!知恵袋から、サーバにあるCSVファイルをローカルにコピーしたいというご要望がありましたので、コードを紹介します。
質問
Excel VBAを使い、
サーバー(\ホスト名\D$\work)上の「CSVファイル」を
更新日付の新しい順で「8つ」、
ローカルの指定の場所「C:\local」へコピーし、
1枚のシートに以下の情報を出力させたいです。
①ファイル名
②ファイルの更新時間
③コピーしてきたそれぞのファイルの最終行の値
(最終行のセルの位置はファイルによって異なる)ざっくりとしたイメージは添付画像の感じです。
処理は繋げて書いていただいてもかまいませんが、
ファイルのコピーと、最終行取得は分けていただけると助かります。
難しくなるようであれば都合の良いようにしていただければと思います。出力シートについては、マクロを実行したシートでも良いですし、
シートを新しく一枚追加しても良いです。
直近でスケジュール通り処理がなされている事と、
処理の結果(OK・NG)が最後の行に記載されるのでそこだけを一覧で見れると確認が早く終わるという理由です。なお、Windowsの資格情報でサーバーの管理者権限を登録しているので、
ローカルからファイル共有でサーバーへアクセスすることは可能な状態です。
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q10268370805
この質問文の中にある画像はこちら↓
回答
回答ではコードが長くなってしまいましたので、Googleドライブにテキストにしてアップしました。
標準モジュール↓
https://drive.google.com/file/d/1R15m7oV-JbXuPbr3H3_s__Bx7xIO4czg/view?usp=sharing
クラスモジュール↓
https://drive.google.com/file/d/1LNgAQZzfBwwJdi5QLiMncX6MNJZZ6l6J/view?usp=sharing
ブログでは幸いいくらでもコードを書き連ねてもよいので、ここに全部を載せようと思います。
Option Explicit
'OutputSheet Header
Enum en
サーバー上のファイル名 = 1
ファイル更新時間
最終行の値
theEnd = 最終行の値
End Enum
'サーバー(\\ホスト名\D$\work)上の「CSVファイル」を
'サーバのアドレス
Const cstSrv As String = "\\fs\public\20220928"
Public Sub ファイルコピー()
Dim tmpSh As Worksheet
Dim tmpBk As Workbook
Dim Bk As Workbook
Dim Sh As Worksheet
Const clCnt As Long = 8 '更新日付の新しい順で「8つ」、
Const cstLocal As String = "C:\local" 'ローカルの指定の場所「C:\local」へコピーし、
'Create Temporary
Call CreateTemporary(tmpBk, tmpSh, Bk, Sh)
Dim lListCounts As Long
'Get Server File List
lListCounts = GetServerFileList(tmpSh, cstSrv)
If lListCounts > 0 Then
Dim lLocalListCnt As Long
'Edit File List
lLocalListCnt = EditFileList(Sh, tmpSh, clCnt, lListCounts)
'Copy CSV From Server to LocalFolder
Call CopyCSVFromServer2LocalFolder(Sh, cstSrv, cstLocal, lLocalListCnt)
'1 枚のシートに以下の情報を出力させたいです。
'①ファイル名
'②ファイルの更新時間
'③コピーしてきたそれぞのファイルの最終行の値
'(最終行のセルの位置はファイルによって異なる)
'Get data at each files' last row
Call GetDataAtEachFilesLastRow
End If
'End Program
Call EndProgram(tmpBk, tmpSh, Bk)
End Sub
'Create Temporary
Private Sub CreateTemporary(ByRef tmpBk As Workbook, ByRef tmpSh As Worksheet, ByRef Bk As Workbook, ByRef Sh As Worksheet)
Set tmpBk = Workbooks.Add
Set tmpSh = tmpBk.Worksheets(1)
Set Bk = ThisWorkbook
Set Sh = Bk.Worksheets(1) '←ここでCSVを8件だけリストアップするシートを指定してください。
End Sub
'Get Server File List
Private Function GetServerFileList(ByRef tmpSh As Worksheet, ByRef cstSrv As String) As Long
Dim CSVFiles As Collection
Set CSVFiles = New Collection
Dim stBuf As String
Dim cl As Class1
stBuf = Dir(cstSrv & "\" & "*.csv")
Do While stBuf <> ""
Set cl = New Class1
cl.name = stBuf
cl.FileDateTime = FileDateTime(cstSrv & "\" & stBuf)
CSVFiles.Add cl, stBuf
stBuf = Dir()
Loop
Dim lCnt As Long
lCnt = CSVFiles.Count
Dim Var() As Variant
Dim ii As Long
If lCnt > 0 Then
ReDim Var(1 To 2, 1 To lCnt)
For ii = 1 To lCnt
Var(1, ii) = CSVFiles(ii).name
Var(2, ii) = CSVFiles(ii).FileDateTime
Next ii
With tmpSh
.Cells(1, 1).Resize(lCnt, 2).Value = WorksheetFunction.Transpose(Var)
End With
End If
Set cl = Nothing
Set CSVFiles = Nothing
GetServerFileList = lCnt
End Function
'Edit File List
Private Function EditFileList(ByRef Sh As Worksheet, ByRef tmpSh As Worksheet, ByRef clCnt As Long, ByVal lListCounts As Long)
Dim lRow As Long
With tmpSh
.Cells(1, 1).Resize(lListCounts, 2).Sort Key1:=.Cells(1, 2), Order1:=xlDescending
lRow = .Cells(.Rows.Count, 1).End(xlUp).Row
If lRow >= clCnt Then
.Range(Cells(clCnt + 1, 1), .Cells(lRow, 1)).EntireRow.ClearContents
End If
lRow = .Cells(.Rows.Count, 1).End(xlUp).Row
Sh.Range(Sh.Cells(5, en.サーバー上のファイル名), Sh.Cells(4 + lRow, en.ファイル更新時間)).Value = .Range(.Cells(1, 1), .Cells(lRow, 2)).Value
End With
EditFileList = lRow
End Function
'Copy CSV From Server to LocalFolder
Private Sub CopyCSVFromServer2LocalFolder(ByRef Sh As Worksheet, _
ByRef cstSrv As String, ByRef cstLocal As String, _
ByRef lLocalListCnt As Long)
Dim ii As Long
Dim stFileName As String
With Sh
For ii = 1 To lLocalListCnt
stFileName = .Cells(ii + 4, en.サーバー上のファイル名)
FileCopy cstSrv & "\" & stFileName, cstLocal & "\" & stFileName
Next ii
End With
End Sub
'Get data at each files' last row
Public Sub GetDataAtEachFilesLastRow()
Dim Sh As Worksheet
Dim FSO As Object
Dim stBuf As String
Dim stTarget As String
Dim lRow As Long
Dim lLast As Long
Dim ii As Long
Set Sh = ThisWorkbook.Worksheets(1) '←リストのあるシート名を指定してください。
Set FSO = CreateObject("Scripting.FileSystemObject")
lRow = Sh.Cells(Sh.Rows.Count, 1).End(xlUp).Row
For ii = 5 To lRow
stTarget = cstSrv & "\" & Sh.Cells(ii, en.サーバー上のファイル名).Value
With FSO.OpenTextFile(stTarget, 1)
stBuf = .ReadAll
.Close
End With
lLast = UBound(Split(stBuf, vbCrLf)) - 1
Sh.Cells(ii, en.最終行の値).Value = Split(stBuf, vbCrLf)(lLast)
'↑CSVの仕様が分からないので最終行をまるまるとってくるようにしましたが、
'特定のカラムだけが必要なら、適切な文字列でSplit関数をかますなどしてください。
Next ii
Set Sh = Nothing
Set FSO = Nothing
End Sub
'End Program
Private Sub EndProgram(ByRef tmpBk As Workbook, ByRef tmpSh As Worksheet, ByRef Bk As Workbook)
Application.DisplayAlerts = False
tmpBk.Close SaveChanges:=False
Application.DisplayAlerts = True
Set tmpSh = Nothing
Set tmpBk = Nothing
Set Bk = Nothing
End Sub
↑これがModule1です。
途中でコレクションを使っていまして、コレクションのアイテム用にクラスモジュールを使っています。
クラスモジュールのコードはこちらです↓
Option Explicit
Public name As String
Public FileDateTime As Variant
Javaなどちゃんとした言語の場合はGetとLetを定義するのでしょうが、VBAなんて所詮アマチュアな言語だと思うので、私はいつもこれです(つまり、さぼっています^^;)。
どうやって作ったか
今回このコードを作るときに、途中経過のスクリーンショットをとっておきました。どうやってコーディングしているか、説明しようと思います。
枠組みを作る
最初に枠組みだけ作りました。
だいたいこんな感じかなーーーという枠組みを作り、必要となりそうな変数を引数に適当に放り込んでいます。
コメントは質問者様の文章をそのままコピペして貼り付けています。
私は処理名や変数名を考えるのが苦手なので、質問文にある言葉をなるべく使うようにしています。
この段階では、全部Subです。戻り値なしです。
戻り値は処理の中身を作っていく途中で必要となってくることがありますので、そのときにFunctionに修正すればよいと思っています。
関数に展開する
次に、関数に展開しました。コンパイルエラーにならないようにさっさとやってしまいます。
これは機械的な作業となりますので、この辺の作業のときは私は何も考えていません。
ただ黙々と、自分がロボットになったつもりでてきぱき手を動かすだけです。
引数をByRef参照にするのは、メモリを節約できるからです。
共通で使うならモジュール変数にしてしまえばよいかもしれないのですが、そうするとメモリを喰うと思うので、最終的にはモジュール変数にしたり、Public変数にすることがあるとしても、最初からはしません。
こんな風にちまちまと引数を指定していきます。
型指定は必ず指定しています。
処理をコーディング
次に、関数の中身を細かくコーディングしていきます。
関数の中身を埋めていく作業が一番楽しいのかもしれません。
モノづくりというか、頭をフルに使うところです。
CreateTemporaryというのは、このプログラムの一時的なファイルを作る処理です。
確かこのとき、最後に解放する処理を忘れていたなと思い出し、EndProgramという関数を追加しています。
VBAはきれいに正対称を描くプログラムなので、SETしたなら解放も先に書いてしまうと漏れがないです。
後のコードは見てもらえれば分かると思うので省略します。
コンパイルとデバッグ
コーディングが終わったらコンパイルし、デバッグします。
コンパイルエラーはさくっと終わらせ、デバッグに時間をかけます。
ここまでで1時間半経っていたので、今回はあまりデバッグしている暇がありませんでした。
テストデータを作るのが一番めんどくさいです。
今回はたまたま家計簿のCSVファイルがあったのでそれを使いました。
困ったのがサーバです。CentOSをサーバ代わりに使っているのですが、調子が悪く、熱暴走してしまうのですぐにシャットダウンしないといけず、十分なテストができませんでした。時間もなかったので30分でデバッグは切り上げました(本当はもっとやりたかった)。
デバッグで気を付けていることは、少ないデータ→多いデータの順にやっていくことと、できる限りイレギュラーなデータを試すことです。
まとめ
ファイルをコピーしてリストアップするのは、いろいろな問題を考えないといけないので結構難しいと思います。
今回ファイルをコピーする先のローカルフォルダに同名のファイルがあった場合の挙動とか、リストに以前のデータが残っていたときのクリアコンテンツする挙動とか、盛り込むことができませんでした。
プログラミングは大枠を作ってから詳細を埋めていくやり方は、忙しかったパートタイマー時代に見つけました。
このやり方が自分には一番しっくりきます。
Yahoo知恵袋でこんな質問が来るのは珍しいですね。いつももっとおおざっぱで、的を得ない、なんと答えたらよいのか分からないような質問の方が多いのですが。今回はだいぶしっかりと仕様が分かっている人が質問したので楽でした。
何かのお役に立てば幸いです。
ここまでお読みくださりありがとうございました。
-
前の記事
障害基礎年金本来請求の診断書3か月の根拠 2022.09.28
-
次の記事
雇用保険料の料率変更の根拠 2022.09.30