PowerShell で Windows のイベントログを走査する

経緯

PowerShell で Windows のイベントビューアで閲覧できるイベントログのうち、

  • イベントログ
  • ソースID
  • レベル
  • 時間

で絞り込んで走査したくなったので、やってみました。

成果物 (スニペット)

関数

##
# Find-WinEventLogSpecified: Windows のイベントログから、指定されたログ名、ソースID、レベル、日時範囲に合致する最新のログのオブジェクトから生成した Hashtable を返却する
#
# @param {String} $logName: 検索するログのログ名
# @param {Int} $id: 検索するログのソースID
# @param {Int} $level: 検索するログのレベル
# @param {Int} $addMinutes: 検索するログの日時範囲(*分前)
#
# @return {EventLogRecord} : 指定されたログ名、ソースID、レベル、日時範囲に合致する最新のログのオブジェクトを返却する
#
function Find-WinEventLogSpecified([String]$logName, [Int]$id, [Int]$level, [Int]$addMinutes) {
    # $ErrorActionPreference: 終了しないエラーの取り扱いに関するパラメータ。
    # デフォルト: Continue -> エラーメッセージが表示されるが例外は発生せずに処理継続。
    # Stop とすることで、例外を発生させる。
    # ただし、単純にイベントログがなかった場合 ($category -eq "ObjectNotFound") は正常として処理継続させる。
    $ErrorActionPreference = 'Stop'
    try {
        $eventObj = (Get-WinEvent -Filterhashtable @{LogName=$logName;id=$id;Level=$level;starttime=(Get-Date).AddMinutes($addMinutes);endtime=(Get-Date)})
        if($eventObj.Length -gt 0) {
            $logHashTable = [Hashtable]@{
                'datetime' = $eventObj[0].TimeCreated;
                'id' = $eventObj[0].Id;
                'level' = $eventObj[0].LevelDisplayName;
                'message' = $eventObj[0].Message;
            }
            # 最新のログを返却
            return $logHashTable
        }
        else {
            throw '該当するログが存在しませんでした。'
        }
    }
    catch {
        $category = $Global:Error[0].CategoryInfo.Category.ToString()
        if ($category -eq 'ObjectNotFound' -or $_.Exception.Message -eq '該当するログが存在しませんでした。') {
            # ログがなかっただけ。問題ない。
            $emptyHashTable = [Hashtable]@{
                'datetime' = Get-Date -UFormat "%Y/%m/%d %H:%M:%S";
                'id' = -999;
                'level' = '情報';
                'message' = '該当するログが存在しませんでした。';
            }
            return $emptyHashTable
        } else {
            Show-ErrorMessage 51 $True $_.Exception.Message $logPath
        }
    }
}

使用時

# アプリケーションログの中でソースIDが 1003 、情報レベルで過去1時間以内のログを走査
$logHashtable = Find-WinEventLogSpecified 'Application' 1003 4 -60

if($logHashtable.id -ne -999) {
    Write-Output '条件に合致するログが存在します。'
}
else {
    Write-Output '条件に合致するログは存在しません。'
}

ざっくりこのような感じで実装しました。

備考

処理の実装自体は Get-WinEvent + Filterhashtable でわりとサクッとできたのですが、問題は上演に合致するイベントログが存在しないケース。

試しに PowerShell を起動してワンライナーで確認すると、以下のようになります。

> (Get-WinEvent -Filterhashtable @{LogName='Application';id=1003;Level=4;starttime=(Get-Date).AddMinutes(-9);endtime=(Get-Date)})
Get-WinEvent : 指定した選択条件に一致するイベントが見つかりませんでした。
発生場所 行:1 文字:2
+ (Get-WinEvent -Filterhashtable @{LogName='Application';id=1003;Level= ...
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (:) [Get-WinEvent], Exception
    + FullyQualifiedErrorId : NoMatchingEventsFound,Microsoft.PowerShell.Commands.GetWinEventCommand

標準出力に赤字でイベントが見付からなかったエラーが表示されてしまいます。この表示がされてしまうと驚いてしまうので潰しておきたい。

そこで try { ... } catch { ... } 構文でエラーハンドリングしようと考えたのですが、 PowerShell のこの手のエラー発生時に停止しないで進んでしまうエラーの場合、そのままでは catch で捕まえられないとのこと

この挙動は変数 $ErrorActionPreference によって決まり、デフォルトでは Continue 。これを $ErrorActionPreference = "Stop"Stop に書き換えることでエラーハンドリングできるようになる、ということで一行追加しています。

ただしこれによってイベントログが存在しないだけでも catch 文に入るので、さらに条件分岐でイベントログがない場合は正常として軌道修正……という調整を。

この記事がないと途方に暮れるところでした。この場を借りて感謝を申し上げます。

参考

Get-WinEvent + FilterHashtable

サンプルコード

停止しないエラーへの対処 (例外を投げてキャッチさせる)

(参考) イベントログ出力

(参考) 文字コード変換

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。