Windowsのイベントログの内容をメールで送信したい場合、今まではNotifEventLogSecond
を使っていました(現在では開発終了)。
NotifEventLogSecond - With nothing better to do
ソースコードは公開されています。
With nothing better to do
今後どうしようかと考えましたが、PowerShellでも同様のことができるとの記載があったため、
- 実現するために使うもの
- PowerShellの
Get-WinEvent
コマンドレットとタスクスケジューラ
- PowerShellの
- 対象のイベントログのレベル
- 重大・エラー・警告の3つ
- メールの送信タイミング
- イベントログに出力された時にメール送信
- 一日分のイベントログ内容をメール送信
という形で試してみることにしました。
なお、Get-EventLog
でもイベントログを取得できますが、両者の違いは以下が参考になりました。
遥佐保の技術メモ:[PowerShell]不定期イベントログを捕まえる!nmcapとPowerShellで必要なイベントログのみ取得する(2/2) - livedoor Blog(ブログ)
なお、Windows Server2008 x64ではFilterHashtableオプションがないため、Get-WinEventでは作りにくいかと思います。
Get-WinEvent -FilterHashtable on Windows 2008 x64
目次
環境
- Windows10 x64
- PowerShell v5.0
メールの内容について
NotifEventLogSecond
を使っていた際は、
- 日時
- レベル
- ログの名前
- ソース
- タスクのカテゴリ
- イベントID
- キーワード
- ユーザー
- コンピューター
- オペコード
- メッセージ
を送信していたため、PowerShellでも同じようにしたいと考えました。
Get-WinEvent
コマンドレットの戻り値は System.Diagnostics.Eventing.Reader.EventLogRecord
オブジェクトのようでした。
Get-WinEvent - TechNet
MSDNによると、EventLogRecordからほぼ同じような内容が取得できそうでした。
EventLogRecord クラス (System.Diagnostics.Eventing.Reader) - MSDN
ただ、
- ユーザー名
- メッセージ
についてはそのままでは取得できなかったので、調べてみました。
ユーザー名
以下を参考に、UserIdからユーザー名へと変換するのが良さそうでした。
ドメイン名、ユーザ名と関連するSIDを相互変換するPowerShellスクリプト - YOMON8.NET
(New-Object System.Security.Principal.SecurityIdentifier($event.UserId)).Translate([System.Security.Principal.NTAccount]).Value
メッセージ
EventLogRecordクラスのプロパティにはメッセージそのものはありませんでした。そのため、Properties
プロパティに含まれているのかなと考えました。
EventLogRecord.Properties プロパティ (System.Diagnostics.Eventing.Reader) - MSDN
試してみたところ、
# 本来は、「サービス "BITS" (DLL "C:\Windows\System32\bitsperf.dll") の Open プロシージャに失敗しました。...」が欲しい $log_property = "" foreach ($p in $event.properties){ $log_property += $p.value } # => BITSC:\Windows\System32\bitsperf.dll82 0 0 0 0 0 0 0
メッセージではありませんでした。
PowerShell独自のプロパティの追加があるのかなと考え、以下を参考に.NETのプロパティ以外を表示してみます。
$event | Get-Member -Force | Where-Object { ($_.MemberType -ne "Method" -and $_.MemberType -ne "Property" ) } # 結果 pstypenames CodeProperty psadapted MemberSet psbase MemberSet psextended MemberSet psobject MemberSet PSStandardMembers MemberSet Message NoteProperty
NotePropertyとしてMessage
が追加されていました。
Messageプロパティの中身を見たところ、イベントログのメッセージと同じだったため、これを使うことにします。
イベントログに出力された時にメール送信
検知方法について
「イベントログに出力された時」を検知する場合の方法として、
- PowerShellにて、イベント発生まで待機
- タスクスケジューラにて、以下の設定を行って検知
- トリガーを
イベント時
と設定 - カスタムイベントフィルターを設定
- トリガーを
がありました。
個人的な興味として、タスクスケジューラと組み合わせてみたかったため、今回は後者の方法で実装します。
Get-WinEventのフィルタについて
Get-WinEvent
コマンドレットには取得時のフィルタが必要です。フィルタの方法としては、
Get-EventLog | Where-Object
Get-EventLog -FilterHashTable
がありますが、前者だと全件取得後の絞込によりパフォーマンスが厳しいため、後者を使います。
イベントログから特定のイベントを抽出 at SE の雑記
フィルタとして使用するハッシュは、
$filter = @{ LogName = $log_name; Level = 1,2,3; StartTime = $start_date; EndTime = $end_date; }
とし、それぞれ
- LogNameは、PowerShellスクリプトの引数として、ログ名をカンマで結合したものを与える
- Levelは、
1
(重大)、2
(エラー)、3
(警告)の3つ - StartTimeは、イベントログで呼ばれた時から30秒前
$start_date = (Get-Date).AddSeconds(-30)
- イベント発生してから30秒以上経過してからの起動はないものと想定
- EndTimeは、現在時刻 (
Get-Date
)
とします。
既知のイベントログエラーの除外について
Get-WinEvent
コマンドレットでは、除外フィルターの設定が見当たりませんでした。そのため、以下を参考に除外リストを作り、除外することにしました。
UranuxTech blog: [PowerShell] Get-WinEventで必要なログのみを取得する
# 除外リスト $exclusions = @( @{ Id = 9999; LogName = "Application"; } ) # Get-WinEventの戻り値$eventsから、既知のものを除外 foreach($e in $exclusions){ $events = $events | Where-Object { -not ( $_.Id -eq $e.Id -and $_.LogName -eq $e.LogName )} }
文字列中の変数展開について
文字列に変数を埋め込む場合、
$val = "ほげ" "値 $val" # => 値 ほげ
としますが、そのままではオブジェクトのプロパティは展開できません。
方法を探したところ、以下にありました。
リテラル - 文字列 | powershell チートシート - Qiita
$val.name = "ほげ" "値 $($val.name)" #=> 値 ほげ
変数を$()
で囲めば良いようです。
全体のコード
上記を踏まえたコードは以下の通りです。
$mail = @{ from = "example+from@gmail.com"; to = "example+to@gmail.com"; smtp_server = "smtp.gmail.com"; smtp_port = 587; user = "example+to@gmail.com"; password = "1234"; } function Send-Gmail($mail, $msg){ $password = ConvertTo-SecureString $mail["password"] -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential $mail["user"], $password $host_name = [Net.Dns]::GetHostName() Send-MailMessage -To $mail["to"] ` -From $mail["from"] ` -SmtpServer $mail["smtp_server"] ` -Credential $credential ` -Port $mail["smtp_port"] ` -Subject "$host_name ログ" ` -Body $msg ` -Encoding UTF8 ` -UseSsl } # オブジェクトのプロパティを示すため、カッコでくくる # 念のため、呼ばれた前30秒のログを取得する $start_date = (Get-Date).AddSeconds(-30) $end_date = Get-Date # 除外するイベントログ情報 $exclusions = @( @{ Id = 9999; LogName = "Application"; } ) # ログ名は、引数にカンマ区切りでセットする # 例) System,Application $log_name = $args[0] -split "," # FilterHashTable用のフィルタ $filter = @{ LogName = $log_name; Level = 1,2,3; StartTime = $start_date; EndTime = $end_date; } # FilterHashTableでフィルタした後のイベントを取得 $events = Get-WinEvent -FilterHashTable $filter # 除外指定されているイベント情報は送信しない foreach($e in $exclusions){ $events = $events | Where-Object { -not ( $_.Id -eq $e.Id -and $_.LogName -eq $e.LogName )} } $msg = @" 対象イベント件数: $($events.Count) -------------------------------------------------- "@ foreach($event in $events){ $log_task_category = if ($event.Task){ "$($event.TaskDisplayName) ($($event.Task))" } else { "なし" } $log_edited_user_id = if ($event.UserId){ "($($event.UserId))" } else { "" } $log_user_name = if ($event.UserId) { # http://yomon.hatenablog.com/entry/2015/06/19/183522 (New-Object System.Security.Principal.SecurityIdentifier($event.UserId)).Translate([System.Security.Principal.NTAccount]).Value } else { "" } $log_property = "" foreach ($p in $event.properties){ $log_property += $p.value } $msg += @" 日時: $($event.TimeCreated.ToString("yyyy/MM/dd HH:mm:ss")) レベル: $($event.LevelDisplayName) ($($event.Level)) ログの名前: $($event.LogName) ソース: $($event.ProviderName) タスクのカテゴリ: $log_task_category イベントID: $($event.Id) キーワード: $($event.KeywordsDisplayNames) ユーザー: $($log_user_name) $log_edited_user_id コンピューター: $($event.MachineName) オペコード: $($event.OpcodeDisplayName) ($($event.Opcode)) プロパティ: $log_property メッセージ: $($event.Message) -------------------------------------------------- "@ } Send-Gmail $mail $msg
テストしてみます。Write-EventLog
でイベントログへ書き込みます。
PowerShellでイベントログに情報を出力 at SE の雑記
Write-EventLog -LogName Application -EntryType Error -Source Application -EventId 1001 -Message "test event"
その後PowerShellスクリプトを実行し、イベントログの内容がメール送信されることを確認します。
一日分のイベントログ内容をメール送信
基本はイベント発生ごとと同じです。
変更箇所は、
- StartTimeを、
(Get-Date).AddDays(-1)
へと変更 - LogNameを、イベントビューアーのカスタムビューにある
管理イベント
で設定されているものへと変更
です。
管理イベントで設定されているイベント名は、管理イベントのプロパティ > フィルターの編集 > XMLタブ
を選択し、
<QueryList> <Query Id="0" Path="Application"> <Select Path="Application">*[System[(Level=1 or Level=2 or Level=3)]]</Select> <Select Path="Security">*[System[(Level=1 or Level=2 or Level=3)]]</Select> <Select Path="System">*[System[(Level=1 or Level=2 or Level=3)]]</Select> ... </Query> </QueryList>
を確認します。この中の<Select>
タグのPath
属性値がログ名になります(例: Application、Security、System など)。
そのため、それらを配列にして、LogNameに渡します。
$log_name = @( "Application", "Security", "System" ) $filter = @{ LogName = $log_name; # ... }
タスクスケジューラの設定
以下を参考に、PowerShell向けのタスクを追加します。
Tech TIPS:WindowsのタスクスケジューラーでPowerShellのスクリプトを実行する際には「パス」に注意 - @IT
イベントログに出力された時にメール送信
タブ名 | 項目 | 値 |
---|---|---|
全般 | セキュリティオプション | ●ユーザーがログオンしているかどうかにかかわらず実行する を選択 |
〃 | 〃 | ■最上位の特権で実行する にチェック |
トリガー | タスクの開始 | イベント時 |
〃 | 設定 | カスタム |
トリガーのフィルター | イベントレベル | 重大、エラー、警告 |
〃 | ●ログごと | 選択 |
〃 | イベントログ | Application,システム |
操作 | 操作 | プログラムの開始 |
〃 | プログラム/スクリプト | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe |
〃 | 引数の追加 | -Command "D:\Sandbox\path\to\send_log_by_event.ps1 System,Application" *1 |
一日分のイベントログ内容をメール送信
タブ名 | 項目 | 値 |
---|---|---|
全般 | セキュリティオプション | ●ユーザーがログオンしているかどうかにかかわらず実行する を選択 |
〃 | 〃 | ■最上位の特権で実行する にチェック |
トリガー | タスクの開始 | スケジュールに従う |
〃 | 設定 | 毎日(任意の時間をセット) |
〃 - フィルター | イベントレベル | 重大、エラー、警告 |
〃 - フィルター | ●ログごと | 選択 |
〃 - フィルター | イベントログ | Application,システム |
操作 | 操作 | プログラムの開始 |
〃 | プログラム/スクリプト | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe |
〃 | 引数の追加 | -Command "D:\Sandbox\path\to\send_log_per_day" |
ソースコード
GitHubに上げました。
PowerShell_misc/send_event_log_mail at master · thinkAmi/PowerShell_misc
ディレクトリの中にあるファイルはそれぞれ、
send_log_when_handled_event.ps1
(イベントログに出力された時にメール送信)send_daily_log.ps1
(一日分のイベントログ内容をメール送信)
です。
その他参考
- Use FilterHashTable to Filter Event Log with PowerShell | Hey, Scripting Guy! Blog
- Reading the Event Log with Windows PowerShell
- Use Custom Views from Windows Event Viewer in PowerShell | Hey, Scripting Guy! Blog
*1:PowerShellの引数は、「.ps1 + 半角スペース + 引数」として設定します