はじめに

ログファイルをgrepした結果をOutlookでメール送信するサンプルスクリプトをVBSで作成。
ポイントは以下の2点。

  • Windowsでgrepをどのように実現するか
  • メール本文にログファイル全文をどう書くか

Windowsでgrepをどのように実現するか

Windowsでgrepする方法はfindstrコマンドで実現可能で、正規表現も検索文字列に指定できる。

findstr [パラメータ] 検索文字列 [ファイル名群]

しかしfindstrはVBSの関数ではないため、 CreateObject("WScript.Shell") してから Exec("%ComSpec% /c (コマンド)") しなければいけない。

2点目でも同じ問題が起こるのだが、Execでコマンド実行した結果が標準出力に出て、それを取得しようとしたときに出力サイズによっては問題が発生する。

メール本文にログファイル全文をどう書くか

標準出力に4KB(4096byte)以上出力しバッファがいっぱいになっている状態では、Exec.StdOutで読み込みを行おうとしてもデッドロックがかかってしまい、スクリプトが止まってしまう。

同期読み込み操作は、呼び出したスクリプトの読み込みと、子プロセスの書き込みに依存しています。これがデッドロック状態を起こしうるのです。
子プロセスのストリームを読み込むとき、子プロセスに依存した形になります。子プロセスがストリームに書き込むか、ストリームを閉じるまで、呼び出しは完了しません。
一方、子プロセスがストリームのバッファ(4KB)をいっぱいにしてしまうとき、親プロセスの読み込みに依存しています。
親プロセスがバッファから完全に読み込むか、ストリームを閉じるまで、子プロセスの書き込み操作は待ち状態に入って完了しません。

引用:http://p2pquake.ddo.jp/mskb/archives/261

findstrの結果だけなら問題ないことも多いが、ログファイル全文となると簡単に4KBを超えてしまうので、 以下のようにEndOfStreamに到達するまで常にReadLineする必要がある。

Do While Not(Exec.StdOut.AtEndOfStream )
  stdout = stdout & Exec.StdOut.ReadLine & kaigyo
Loop

ログファイルの内容自体は、テキストファイルなどの内容を標準出力に表示するコマンドtypeを使用している。

サンプルスクリプト

mail.vbs

Option Explicit
' 引数 logname: ログファイル名(拡張子.logは含めない。logname*.logが処理される)
'      grepStr: grepする文字列
' 使用例:cscript mail.vbs logsample エラー
' 仕様:%TMP%\logname.logファイルについて、grepStrを検索する。
'       検索できれば、ログ全文を本文に、【失敗】lognameを件名にセットしてメール送信する。
'       検索できなければ、本文は空で、【成功】lognameを件名にセットしてメール送信する。
'       ログファイルはC:\logbackup\に日付と時分をファイル名に付与して退避する。
'
'*************************************************************
Dim outlook, item, mailBody, mailSubject, mailAddress, mailTo, mailCc, kaigyo

'*************************************
'* 個人情報
'*************************************
' 自分のメールアドレス
mailAddress = "me@example.com"
' 送信先:TO
mailTo = "you@example.com"
' 送信先:CC
mailCc = ""

'*************************************
'* 初期処理
'*************************************
Set outlook = CreateObject("Outlook.Application")
Set item = outlook.CreateItem(0)

' 改行コード
kaigyo = vbCrLf

'*************************************
'* コマンドライン引数(パラメータ)の取得
'*************************************
Dim oParam
Set oParam = WScript.Arguments
Dim logName, grepStr
logName = oParam(0)
grepStr = oParam(1)

'*************************************
'* メール本文
'*************************************
Dim WShell, Exec
Set WShell = CreateObject("WScript.Shell")
Set Exec = WShell.Exec("%ComSpec% /c (type %TMP%\" & logName & "*.log | findstr " & grepStr & ")")
Dim stdout
Do While Not(Exec.StdOut.AtEndOfStream )
  stdout = stdout & Exec.StdOut.ReadLine
Loop

If Len(stdout) <> 0 Then
    Set Exec = WShell.Exec("%ComSpec% /c (type %TMP%\" & logName & "*.log)")
    Do While Not(Exec.StdOut.AtEndOfStream )
        mailBody = mailBody & Exec.StdOut.ReadLine & kaigyo
    Loop
End IF

'*************************************
'* メール件名
'*************************************
mailSubject = logName
If Len(stdout) = 0 Then
    mailSubject = "【成功】" & mailSubject
Else
    mailSubject = "【失敗】" & mailSubject
End If

'*************************************
'* メール内の設定
'*************************************
item.To = mailTo
item.Cc = mailCc
item.Subject = mailSubject
item.Body = mailBody
item.Display

'*************************************
'* 送信処理
'*************************************
item.Send

'*************************************
'* 終了処理
'*************************************
'ファイルの退避
WShell.Exec("%ComSpec% /c (move %TMP%\" & logName & "*.log C:\logbackup\" & logName _
    & Year(Now) & Right("0" & Month(Now), 2) & Right("0" & Day(Now), 2) & Right("0" & Hour(Now), 2) & Right("0" & Minute(Now), 2) _
    & ".log)")

Set item = Nothing
Set outlook = Nothing
WScript.Quit 0