PowerShell Get-EventLogで効率的にイベントログをクエリ

すべてのWindowsシステム管理者は、おそらくWindowsイベントログに精通しているでしょう。このPowerShellのコマンドレットを使用すると、システム管理者は複数のコンピュータ上で一度に多くのイベントを解析することができます。イベントビューアでクリックして適切なフィルタを探したり、重要なイベントが保存されている場所を特定するために時間を費やす必要がなくなります。ただし、Get-EventLogには欠点もあります。

Get-EventLogコマンドレットは、すべての最新バージョンのWindows PowerShellで利用できます。最も単純な使い方では、このコマンドレットはクエリするイベントログを指定し、そのイベントログ内のすべてのイベントを表示します。

しかし、最初にイベントログの名前がわからない場合はどうでしょうか?その場合、まずローカルコンピュータで利用可能なすべてのイベントログを特定する必要があります。そのためには、Get-EventLog -Listコマンドを使用します。

Get-EventLog -List

ご覧の通り、現在私のローカルシステムにはいくつかのイベントログがありますが、他のイベントログはどこにあるのか疑問に思うかもしれません。イベントビューアの「アプリケーションとサービスログ」には数十の他のイベントログが表示されていますが、なぜここに表示されていないのでしょうか。

それらのイベントが必要な場合、残念ながらGet-EventLogは機能しません。その代わりに、Get-WinEventを確認する必要があります。この時点でGet-EventLogコマンドレットはレガシーコマンドレットと考えられるかもしれませんが、私はまだ頻繁に使用しているのは単純に使いやすいからです。

Get-EventLogを使用したイベントのクエリ

今、利用可能なすべてのイベントログを知ったので、そのイベントログ内のイベントを読むことができます。たとえば、Applicationイベントログのすべてのイベントを表示したい場合、Get-EventLogというコマンドレットでLogNameパラメータを指定する必要があります。そうすることで、そのイベントログ内のすべてのイベントが返されます。

Get-EventLog -LogName Application

デフォルトでは、出力には6つのプロパティしか表示されません:

  • Index
  • Time
  • EntryType
  • Source
  • InstanceId
  • Message

実際には、Get-EventLogは16のプロパティを返します。6つしか表示されないのは、PowerShellの書式設定ルールによるものです。以下は、Get-EventLogをパイプしてSelect-Objectに渡し、すべてのプロパティを選択した実際の出力の例です。

Get-EventLog -LogName Application | Select-Object -Property * EventID

Get-EventLogでのフィルタリング

イベントを検索する際には、すべてのイベントを必要とするわけではありません。代わりに、いくつかのイベントのみが必要です。その場合、特定のイベントをフィルタリングする必要があります。Get-EventLogには、タイムスタンプ、エントリータイプ、イベントID、メッセージ、ソース、ユーザー名に基づいてフィルタリングする方法がいくつかあります。これにより、イベントを見つけるためのほとんどの方法がカバーされます。

フィルタリングをデモンストレーションするために、定期的にイベントをクエリすることがあり、最新の10件のイベントを見つけたい場合、Newestパラメータを使用して、表示するイベント数を指定します。例えば、Get-EventLog -LogName Application -Newest 10は、最新の10件のイベントのみを返します。

特定の時点以降のすべてのイベントを検索したい場合には、Afterパラメータを使用します。Afterパラメータは日付/時刻を受け取るため、例えば、1/26/19 10:17 AM以降に発生したApplicationログ内のイベントのみを検索したい場合は、Get-EventLog -LogName Application -After '1/26/19 10:17'とします。同様のプロセスを逆に行い、特定の日付よりも前に発生したイベントを選択する場合は、Beforeパラメータを使用します。

Get-EventLogには、タイムスタンプに基づいていないフィルタリング方法がたくさんあります。イベントID(インスタンスID)やメッセージなどの他の属性に基づいてイベントをフィルタリングすることもできます。例えば、IDが916のイベントを探している場合、InstanceIdパラメータに916を渡します。

PS> Get-EventLog -LogName Application -InstanceId 916

フィルタを組み合わせることもできます。たとえば、メッセージにsvchostという文字列が含まれるイベントが多く返される場合、Get-EventLogMessageパラメータを追加し、ワイルドカードsvchostを指定します。

PS> Get-EventLog -LogName Application -InstanceId 916 -Message '*svchost*'

ボーナススクリプト!

Get-EventLogを実世界のスクリプトで使う素晴らしい例が必要ですか?そうであれば、ラッキーです!以下は、Get-EventLogの高度な使用例であり、今日すぐにダウンロードして使用できます!

<#
.SYNOPSIS
このスクリプトは、指定した開始時間と終了時間の間にあるすべてのイベントログまたはテキストログのエントリをWindowsコンピュータで検索します。
.DESCRIPTION
このスクリプトは、指定された時間枠内で最終書き込み時刻を持つすべてのテキストログ、または時間枠内に日時参照を含む行があるすべてのテキストログを列挙します。この情報を一連のファイルに記録し、興味深いログファイルをさらなる分析のためにコピーします。
.EXAMPLE
PS> .\Get-EventsFromTimeframe.ps1 -StartTimestamp '01-29-2014 13:25:00' -EndTimeStamp '01-29-2014 13:28:00' -ComputerName COMPUTERNAME

この例では、StartTimestampとEndTimestampの間のすべてのイベントログエントリと、$ FileExtensionパラメータ内の拡張子を持つファイルのいずれかが時間枠内で最後に書き込まれたり、時間枠内で日時参照を含む行を含むものを検索します。
.PARAMETER StartTimestamp
イベントの検索を開始する最も早い日時。
.PARAMETER EndTimestamp
イベントの検索を開始する最新の日時。
.PARAMETER Computername
検索するリモート(またはローカル)コンピュータの名前。
.PARAMETER OutputFolderPath
イベントを含むテキストファイルが格納されるフォルダのパス。
.PARAMETER LogAuditFilPath
ログファイル、行番号、および一致タイプをドキュメント化するテキストファイルへのパス。
.PARAMETER EventLogsOnly
イベントログのみを検索する場合に使用するスイッチパラメータ。
.PARAMETER LogFilesOnly
ファイルシステム上のログのみを検索する場合に使用するパラメータ。
.PARAMETER ExcludeDirectory
ファイルシステム上で検索しないフォルダパスを指定します。
.PARAMETER FileExtension
検索するファイルの1つ以上のカンマ区切りのファイル拡張子のセットを指定します。これはデフォルトで 'log、txt、wer' 拡張子です。
#>
[CmdletBinding(DefaultParameterSetName = 'Neither')]
param (
    [Parameter(Mandatory)]
    [datetime]$StartTimestamp,
    [Parameter(Mandatory)]
    [datetime]$EndTimestamp,
    [Parameter(ValueFromPipeline,
        ValueFromPipelineByPropertyName)]
    [string]$ComputerName = 'localhost',
    [Parameter()]
 [string]$OutputFolderPath = ".\$Computername",
 [Parameter(ParameterSetName = 'LogFiles')]
 [string]$LogAuditFilePath = "$OutputFolderPath\LogActivity.csv",
 [Parameter(ParameterSetName = 'EventLogs')]
 [switch]$EventLogsOnly,
    [Parameter(ParameterSetName = 'LogFiles')]
    [switch]$LogFilesOnly,
    [Parameter(ParameterSetName = 'LogFiles')]
 [string[]]$ExcludeDirectory,
 [Parameter(ParameterSetName = 'LogFiles')]
 [string[]]$FileExtension = @('log', 'txt', 'wer')
)
 
begin {
 if (!$EventLogsOnly.IsPresent) {
 ## マッチした基準に合致するログファイルを保存するためのローカルディレクトリを作成する
 $LogsFolderPath = "$OutputFolderPath\logs"
 if (!(Test-Path $LogsFolderPath)) {
 mkdir $LogsFolderPath | Out-Null
 }
 }
 
 function Add-ToLog($FilePath,$LineText,$LineNumber,$MatchType) {
 $Audit = @{
 'FilePath' = $FilePath;
 'LineText' = $LineText
 'LineNumber' = $LineNumber
 'MatchType' = $MatchType
 }
 [pscustomobject]$Audit | Export-Csv -Path $LogAuditFilePath -Append -NoTypeInformation
 }
}
 
process {
 
    ## ユーザーがイベントログエントリを検索することを希望する場合にのみ実行する
    if (!$LogFilesOnly.IsPresent) {
 ## 少なくとも1つのイベントを含むすべてのイベントログ名を検索する
 $Logs = (Get-WinEvent -ListLog * -ComputerName $ComputerName | where { $_.RecordCount }).LogName
 $FilterTable = @{
 'StartTime' = $StartTimestamp
 'EndTime' = $EndTimestamp
 'LogName' = $Logs
 }
 
 ## 開始時間と終了時間の間にあるすべてのイベントログのイベントを検索する
 $Events = Get-WinEvent -ComputerName $ComputerName -FilterHashtable $FilterTable -ea 'SilentlyContinue'
 Write-Verbose "Found $($Events.Count) total events"
 
 ## プロパティをよりわかりやすい形式に変換し、各イベントをイベントログテキストファイルに追加する
 $LogProps = @{ }
 [System.Collections.ArrayList]$MyEvents = @()
 foreach ($Event in $Events) {
 $LogProps.Time = $Event.TimeCreated
 $LogProps.Source = $Event.ProviderName
 $LogProps.EventId = $Event.Id
 if ($Event.Message) {
 $LogProps.Message = $Event.Message.Replace("<code>n", '|').Replace("</code>r", '|')
 }
 $LogProps.EventLog = $Event.LogName
 $MyEvents.Add([pscustomobject]$LogProps) | Out-Null
 }
 $MyEvents | sort Time | Export-Csv -Path "$OutputFolderPath\eventlogs.txt" -Append -NoTypeInformation
 }
 
 ## ユーザーがログファイルを検索することを希望する場合にのみ実行する
 if (!$EventLogsOnly.IsPresent) {
        ## リモートコンピュータのリモート管理共有をすべて列挙する。物理ドライブを列挙する代わりにこれを行うのは、
        ## ドライブが存在しても共有が存在しない場合、ドライブにアクセスできないためです。
 $Shares = Get-WmiObject -ComputerName $ComputerName -Class Win32_Share | where { $_.Path -match '^\w{1}:\\$' }
 [System.Collections.ArrayList]$AccessibleShares = @()
 ## すべてのリモート管理共有にアクセスできるようにする
 foreach ($Share in $Shares) {
 $Share = "\\$ComputerName\$($Share.Name)"
 if (!(Test-Path $Share)) {
 Write-Warning "Unable to access the '$Share' share on '$Computername'"
 } else {
 $AccessibleShares.Add($Share) | Out-Null 
 }
 }
 
 $AllFilesQueryParams = @{
 Path = $AccessibleShares
 Recurse = $true
 Force = $true
 ErrorAction = 'SilentlyContinue'
 File = $true
 }
 ## $ExcludeDirectoryパラメータで指定されたディレクトリをログファイルを検索しないように追加する
 if ($ExcludeDirectory) {
 $AllFilesQueryParams.ExcludeDirectory = $ExcludeDirectory 
 }
 ## date/time形式のいくつかの異なる文字列を検索するための正規表現文字列を作成する
 ## これは、各テキストファイルで日時文字列を検索するために使用されます
 ## TODO:Jan、Feb、Marなどに一致させる機能を追加する
 $DateTimeRegex = "($($StartTimestamp.Month)[\\.\-/]?$($StartTimestamp.Day)[\\.\-/]?[\\.\-/]$($StartTimestamp.Year))|($($StartTimestamp.Year)[\\.\-/]?$($StartTimestamp.Month)[\\.\-/]?[\\.\-/]?$($StartTimestamp.Day))"
 ## クエリパラメータに一致するすべてのコンテンツを持つファイルを列挙する
 Get-ChildItem @AllFilesQueryParams | where { $_.Length -ne 0 } | foreach {
 try {
 Write-Verbose "Processing file '$($_.Name)'"
 ## 最終書き込み時刻が時間枠内にある場合、ファイルを記録する。これにより、日時タイムスタンプを記録しないかもしれない
 ## が、ユーザーが検索しようとしているイベントに関連するかもしれないログファイルを見つけることができます。
 if (($_.LastWriteTime -ge $StartTimestamp) -and ($_.LastWriteTime -le $EndTimestamp)) {
 Write-Verbose "Last write time within timeframe for file '$($_.Name)'"
 Add-ToLog -FilePath $_.FullName -MatchType 'LastWriteTime'
 }
 ## 検索しようとしている拡張子のセットに一致し、実際にプレーンテキストファイルである場合。
 ## 解析する前に、Get-Contentを使用して実際にプレーンテキストであることを再確認します。
 if ($FileExtension -contains $_.Extension.Replace('.','') -and !((Get-Content $_.FullName -Encoding Byte -TotalCount 1024) -contains 0)) {
 ## テキストファイルの内容を時間枠内の日付に言及するかどうかをチェックする
 Write-Verbose "Checking log file '$($_.Name)' for date/time match in contents"
 $LineMatches = Select-String -Path $_.FullName -Pattern $DateTimeRegex
 if ($LineMatches) {
 Write-Verbose "Date/time match found in file '$($_.FullName)'"
 ## 一致するすべての行を監査ファイルに記録する。
 foreach ($Match in $LineMatches) {
 Add-ToLog -FilePath $_.FullName -LineNumber $Match.LineNumber -LineText $Match.Line -MatchType 'Contents'
 }
 ## 出力ログディレクトリ内でリモートコンピュータ上の同じパスを作成し、
 ## イベントが時間枠内にあるログファイルをそのパスにコピーする。
 ## これは、255文字を超えるファイルパスが存在する場合は機能しません。
 $Trim = $_.FullName.Replace("\\$Computername\", '')
 $Destination = "$OutputFolderPath\$Trim"
 if (!(Test-Path $Destination)) {
 ## TODO:長いパスサポートが実装された場合にエラーアクションを削除する
 mkdir $Destination -ErrorAction SilentlyContinue | Out-Null
 }
 Copy-Item -Path $_.FullName -Destination $Destination -ErrorAction SilentlyContinue -Recurse
 }
 }
 } catch {
 Write-Warning $_.Exception.Message 
 }
 }
 }
}

サマリー

「Get-EventLog」コマンドレットは、一般的なイベントログのクエリを迅速に実行する必要がある場合に便利なコマンドです。使い方は簡単で、基本的なフィルタリング機能も提供しています。ただし、より詳細なイベントログの調査が必要な場合は、「Get-WinEvent」コマンドの方がおそらくより適していますが、少し使い方が難しく、XPathなどの構文を知る必要があります。

Source:
https://adamtheautomator.com/get-eventlog/