3流プログラマのメモ書き

元開発職→社内SE→派遣で営業支援→開発戻り浦島太郎状態の三流プログラマのIT技術メモ書き。 このメモが忘れっぽい自分とググってきた技術者の役に立ってくれれば幸いです。

robocopyでメールも送れるようにバックアップスクリプトを作ってみた

robocopy単体でも有用なツールなのですが、データのバックアップを運用するとなるとバックアップ失敗時にメールを送ってくれるようにしてもらうと楽になります。
一からスクリプト書くの面倒だなあと思ってググってたらrobocopy.exeを使ったWindows用バックアップスクリプト | kurimon.meにドンピシャなコードが載ってました。ブロガーさんに感謝です。

ほぼコピペですが、バックアップ対象を別ファイルに持たしてバッチファイルから取得し、robocopy実行後古いログファイルを削除するという場合のコードを載せておきます。
なお、robocopyからの戻り値はビットマスクとなるので、エラーと判断するのは、戻り値が8以上という判定にしたほうが良いかもしれません。

設定ファイル

robocopy_bkup_conf.txt に / 区切りでバックアップ元、バックアップ先、ログファイル名の設定を書きます。(ログファイルは途中までは共通とするためユニーク部分だけ記述)
以下のような感じです。

robocopy_bkup_conf.txt(/を区切り文字として バックアップ元、バックアップ先、ログファイル名 を設定)
\\server01\share\user/D:\bkup\user/user.txt
\\server02\nas\hoge/D:\bkup\hoge/hoeg.txt

バッチファイル

以下がタスクスケジューラに登録するバッチです。
設定ファイルよりバックアップ元、バックアップ先、ログファイル名を取得し、robocopyを実行するVBScriptを呼び出し後、古いログファイルを削除するVBScriptを呼び出します。

@echo off

rem 日付を取得
set YYYYMMDD=%DATE:/=%

rem コピー元、コピー先、ログファイル名の設定ファイル呼び出し
FOR /F "tokens=1,2,3 delims=/" %%A IN (robocopy_bkup_conf.txt) DO (
   rem VBスクリプトを実行 %%A:バックアップ元 %%B:バックアップ先 %%C:ログファイル名
   cscript "robocopy.vbs" %%A %%B D:\diff\log\robocopyLog_%YYYYMMDD%_%%C
)

rem 古いログファイル削除
cscript "LogOldFile_Del.vbs"

robocopyを実行するVBScript

実際のrobocopyコマンドを叩き、結果をメールで送信する部分のVBScriptです。

Option Explicit

Dim objNetWork ,  objShell
Dim strSubject  

'必要なオブジェクトをセット
Set objNetWork = WScript.CreateObject("WScript.Network")  'ホスト名、ユーザ名の取得
Set objShell = CreateObject("WScript.Shell")              'ネットワークドライブのマウントとrobocopyコマンド

'robocopy.batから渡された引数をチェックし、正しく取得できなければエラーメールを送信して終了
If WScript.Arguments.Count <> 3 Then
    strSubject = "[Robocopy_BackupError][" & CStr(Now()) & "]" & objNetWork.UserName & "@" & objNetWork.ComputerName 
    SendMail( strSubject , "Host:" & objNetWork.ComputerName & "のバックアップ処理に失敗しました" & vbCrLf & "Error:引数が不正です「robocopy.bat」を確認してください")
End If
 
'引数を取得
strLocalDir = WScript.Arguments.Item(0)   'コピー元ディレクトリ
strRemoteDir = WScript.Arguments.Item(1)  'コピー先ディレクトリ
strLogFile = WScript.Arguments.Item(2)  'ログファイル

WScript.Echo strLocalDir
WScript.Echo strRemoteDir

'robocopyがインストールされているか確認。robocopyを引数なしで実行しエラーとなるかどうかで、コマンドがあるかどうか判断。
On Error Resume Next
strRobocopyStatus = objShell.Run("robocopy.exe ",7,True)
If Err.Number <> 0 Then
    On Error Goto 0
    strSubject = "[Robocopy_BackupError][" & CStr(Now()) & "]" & objNetWork.UserName & "@" & objNetWork.ComputerName
    SendMail( strSubject ,  "エラー:robocopyがインストールされていません。")
    WScript.Quit
End If
On Error Goto 0

 
'バックアップ(第二引数が7なのでウィンドウを最小化したまま。 /TEEオプション付けてるので進捗を表示します。コマンドプロンプト最小化しないと描画に時間取られるので最小化してます)
'strRobocopyStatus = objShell.Run("cmd /c robocopy.exe " & strLocalDir & " " & strRemoteDir & " " & " /E /COPYALL  /R:2 /W:5 /FFT /TEE /NDL /NP /LOG:" & strLogFile ,7,True)
'ミラーバックアップの場合
strRobocopyStatus = objShell.Run("cmd /c robocopy.exe " & strLocalDir & " " & strRemoteDir & " " & " /MIR /COPYALL  /R:2 /W:5 /FFT /NDL /NP /LOG:" & strLogFile ,7,True)

Dim strMsg , strStatus ,iRobocopyStatus , strInfo
iRobocopyStatus = CInt(strRobocopyStatus)
strStatus = "------------------------------" & vbCrLf  & "Status: " & vbCrLf

strInfo = "バックアップ元:" & strLocalDir & vbCrLf & "バックアップ先:"& strRemoteDir & vbCrLf

'robocopyからの戻り値を論理積を取って詳細メッセージ生成
If (iRobocopyStatus AND 16) = 16 Then
    strStatus = strStatus & "***FATAL ERROR***"
    strMsg = strMsg & "Code16:重大なエラー。Robocopyは,ファイルをひとつもコピーしませんでした。これは,使用法の間違いか転送元または転送先ディレクトリへのアクセス権が不十分なために発生するエラーです。" & vbCrLf 
End If
If (iRobocopyStatus AND 8) = 8 Then
    strStatus = strStatus & " FAIL "
    strMsg = strMsg & "Code8:いくつかのファイルまたはディレクトリがコピーできませんでした(コピーエラーが発生し,リトライ上限を上回りました)。以降のエラーをチェックしてください。" & vbCrLf 
End If
If (iRobocopyStatus AND 4) = 4 Then
    strStatus = strStatus & " MISM "
    strMsg = strMsg & "Code4:いくつかMismatched(転送元のファイルと同名のディレクトリが転送先にある,あるいはその逆)ファイルまたはディレクトリがみつかりました。ログを調べてください。多分掃除が必要です。" & vbCrLf 
End If
If (iRobocopyStatus AND 2) = 2 Then
    strStatus = strStatus & " XTRA "
    strMsg = strMsg & "Code2:いくつかのExtra(転送元に存在しないのに転送先に存在するファイル)ファイルまたはディレクトリがみつかりました。出力ログを調べてください。掃除が必要かもしれません。" & vbCrLf 
End If
If (iRobocopyStatus AND 1) = 1 Then
    strStatus = strStatus & " COPY "
    strMsg = strMsg & "Code1:一つ以上のファイルが,うまくコピーされました(つまり新しいファイルは転送先に届きました)。" & vbCrLf 
End If
If iRobocopyStatus = 0 Then
    strMsg = strMsg & "Code0:エラーは発生せず,コピーもされませんでした。転送元と転送先ディレクトリツリーは,完全に同期しています。" & vbCrLf 
End If

strStatus = & vbCrLf  & "---------------------------------"  

'robocopyからの戻り値によって処理を振り分ける8以上が致命的なエラー
If iRobocopyStatus >= 8 Then
    strSubject = "[Robocopy_BackupError][" & CStr(Now()) & "]" & objNetWork.UserName & "@" & objNetWork.ComputerName
    SendMail(strSubject , "Host:" & objNetWork.ComputerName & "のバックアップ処理に致命的なエラーが発生しました。ログファイルを確認してください。" & vbCrLf & _
    vbCrLf & strStatus & vbCrLf &  strInfo & vbCrLf & vbCrLf & "詳細情報:" & vbCrLf & strMsg )
Else
    strSubject = "[Robocopy_BackupLog][" & CStr(Now()) & "]" & objNetWork.UserName & "@" & objNetWork.ComputerName
    SendMail(strSubject , "Host:" & objNetWork.ComputerName & "のバックアップ処理正常に終了しました。" & vbCrLf & _
    vbCrLf & strStatus & vbCrLf &  strInfo & vbCrLf & vbCrLf & "詳細情報:" & vbCrLf & strMsg )
End If

Set objNetWork = Nothing

'こういう分け方もできる(MSのドキュメントより)
'if strRobocopyStatus 16  echo  ***FATAL ERROR***  & goto end
'if strRobocopyStatus 15  echo FAIL MISM XTRA COPY & goto end
'if strRobocopyStatus 14  echo FAIL MISM XTRA      & goto end
'if strRobocopyStatus 13  echo FAIL MISM      COPY & goto end
'if strRobocopyStatus 12  echo FAIL MISM           & goto end
'if strRobocopyStatus 11  echo FAIL      XTRA COPY & goto end
'if strRobocopyStatus 10  echo FAIL      XTRA      & goto end
'if strRobocopyStatus  9  echo FAIL           COPY & goto end
'if strRobocopyStatus  8  echo FAIL                & goto end
'if strRobocopyStatus  7  echo      MISM XTRA COPY & goto end
'if strRobocopyStatus  6  echo      MISM XTRA      & goto end
'if strRobocopyStatus  5  echo      MISM      COPY & goto end
'if strRobocopyStatus  4  echo      MISM           & goto end
'if strRobocopyStatus  3  echo           XTRA COPY & goto end
'if strRobocopyStatus  2  echo           XTRA      & goto end
'if strRobocopyStatus  1  echo                COPY & goto end
'if strRobocopyStatus  0  echo    --no change--    & goto end
 
 
'メール送信処理
Function SendMail(strSubject , strError)
    Dim strMailAddrTo , strSMTPServ , strMailAddrFrom , iSMTPPort
    '# メールアドレスを設定
    strMailAddrTo = "to@hoge.example.com"
    strSMTPServ = "smtp.hoge.example.com"
    strMailAddrFrom = "from@hoge.example.com"
    iSMTPPort = 25

    Dim objMail
    Set objMail = CreateObject("CDO.Message")
    'メールを送信
    objMail.From = strMailAddrFrom
    objMail.To = strMailAddrTo
    objMail.Subject = strSubject
    objMail.TextBody = strError
    objMail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
    objMail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = strSMTPServ
    objMail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = iSMTPPort
    objMail.Configuration.Fields.Update
    objMail.Send
    Set objMail = Nothing
End Function

古いログファイルを削除するVBScript

ログファイルが、D:\diff\log\robocopyLog_YYYYMMDD_設定名 で保存するようにしているので、だんだんとたまっていきます。
それで、指定日数経った D:\diff\log\ 配下の robocopyLog_ から始まるファイルを削除するVBScriptです。

'logファイルの格納されているフォルダーのパス
Const cFolderPath = "D:\diff\log"
'以下の文字列が先頭にあるファイルを削除する
Dim strLogShareFile
strLogShareFile = "robocopyLog_"
'以下の日数経っているものを削除対象とする
Dim iDate
iDate=120

Dim fso, fl
Dim tod, logDate, buf, diff
Set fso = CreateObject("Scripting.FileSystemObject")

tod = CDate(Split(Now, " ")(0))
For Each fl In fso.GetFolder(cFolderPath).Files
    ' strLogShareFileが先頭につくファイルのみ削除対象とする
    If InStr(1 , fl.Name , strLogShareFile , 1 ) = 1 Then
        buf = fso.GetFile(fl.Path).DateLastModified
        logDate = CDate(Split(buf, " ")(0))
        diff = DateDiff("d", logDate, tod)
        If diff > iDate Then
            fl.Delete
        End If
    End If
Next
Set fso = Nothing

robocopyの戻り値ですが、リソースキット内のドキュメントによると以下のようになるようです。

Hex Bit Value    Decimal Value    Meaning If Set
0x10  16 Serious error. Robocopy did not copy any files. This is either a usage error or an error due to insufficient access privileges on the source or destination directories.
0x08  8  Some files or directories could not be copied (copy errors occurred and the retry limit was exceeded). Check these errors further.
0x04  4  Some Mismatched files or directories were detected. Examine the output log. Housekeeping is probably necessary.
0x02  2  Some Extra files or directories were detected. Examine the output log. Some housekeeping may be needed.
0x01  1  One or more files were copied successfully (that is, new files have arrived).
0x00  0  No errors occurred, and no copying was done. The source and destination directory trees are completely synchronized.

robocopy-j.docにはこの戻り値の日本語訳が書いてあったので、上記スクリプトのエラー文字列も日本語表記にしています。

メール送信は汎用化できると思うので、別ファイルにしてしまったほうが良いかもしれません。

参考:
Robocopyの戻り値を知りたい - 履歴