使用PowerShell Get-EventLog高效查询事件日志

每位Windows系统管理员可能都熟悉Windows事件日志。在PowerShell中使用这个cmdlet允许系统管理员一次性解析许多计算机上的许多事件。这使得系统管理员不再需要在事件查看器中四处点击,试图找出正确的过滤器以及确定关键事件存储在何处。然而,`Get-EventLog`确实有其缺点,你将会看到。

使用Get-EventLog列出事件日志

`Get-EventLog` cmdlet在所有现代版本的Windows PowerShell上都可用。在其最简单的用法中,此cmdlet需要一个要查询的事件日志,然后将显示该事件日志中的所有事件。

但是,如果你不知道事件日志的名称,该怎么办?在这种情况下,我们需要找出本地计算机上所有可用的事件日志。我们通过使用命令`Get-EventLog -List`来实现这一点。

Get-EventLog -List

你可以看到我在我的本地系统上有一些事件日志,但是你可能想知道其他的在哪里?在事件查看器的“应用程序和服务日志”下显示了数十个其他事件日志。为什么它们不在这里呢?

如果你需要这些事件,不幸的是,`Get-EventLog`将无法工作。相反,你需要查看Get-WinEvent。`Get-EventLog` cmdlet目前可以被视为传统的cmdlet,但我仍经常使用它,仅仅因为它非常容易使用。

使用Get-EventLog查询事件

现在我们知道所有可用的事件日志,我们可以阅读该事件日志中的事件。也许我想查看应用程序事件日志中的所有事件。要获取这些事件,我需要使用Get-EventLog并指定LogName参数,该命令将通过返回该事件日志中的所有事件来履行。

Get-EventLog -LogName Application

默认情况下,您在输出中只会看到六个属性:

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

实际上,Get-EventLog返回其中的16个属性。您只看到六个的原因是由于PowerShell格式规则定义了输出。下面是通过将Get-EventLog管道传递到Select-Object并选择所有属性找到的实际输出的示例。

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

使用Get-EventLog进行过滤

在寻找事件时,很可能我们不需要全部事件。相反,我们可能只需要一些。在这种情况下,我们需要过滤特定的事件。Get-EventLog有几种不同的方法可以做到这一点。Get-EventLog cmdlet 可以根据时间戳、输入类型、事件ID、消息、源和用户名进行过滤。这照顾到了查找事件的大多数方式。

为了演示过滤功能,也许我会定期查询事件,并且希望找到最新的十个事件。在这种情况下,我可以使用 Newest 参数,并指定我想要查看的事件数量。 Get-EventLog -LogName Application -Newest 10 将仅返回最新的十个事件。

也许我想要找到特定时间点之后的所有事件。为此,我们有 After 参数。 After 参数接受日期/时间,所以如果我只想查找在应用程序日志中发生在 1/26/19 10:17 AM 之后的事件,我可以这样做 Get-EventLog -LogName Application -After '1/26/19 10:17'。我们也可以执行相同的过程,但选择在某个日期之前发生的事件,是的,你可能已经猜到了,使用 Before 参数。

Get-EventLog 有很多不同的过滤方式,不包括基于时间戳的过滤。我们还可以根据其他属性过滤事件,如事件 ID(实例 ID)和消息,这些通常是常见的搜索属性。也许我知道我正在寻找一个 ID 为 916 的事件;我们会将 916 传递给 InstanceId 参数。

PS> Get-EventLog -LogName Application -InstanceId 916

我们还可以组合过滤器。也许我收到了很多 ID 为 916 的事件,但我只想要那些消息中包含字符串 svchost 的事件。在这种情况下,我们可以向 Get-EventLog 添加 Message 参数,并指定通配符,比如 svchost

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

额外的脚本!

你需要一个在真实脚本中使用 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
 
    此示例查找开始时间戳和结束时间戳之间的所有事件日志条目,并查找在时间范围内最后写入的任何文件或包含时间范围内日期/时间引用的行的文件。
.PARAMETER StartTimestamp
    您希望开始搜索事件的最早日期/时间。
.PARAMETER EndTimestamp
    您希望开始搜索事件的最新日期/时间。
.PARAMETER Computername
    您希望在其上进行搜索的远程(或本地)计算机的名称。
.PARAMETER OutputFolderPath
    包含事件的文本文件的文件夹路径。
.PARAMETER LogAuditFilPath
    用于记录匹配的日志文件、行号和匹配类型的文本文件的路径。
.PARAMETER EventLogsOnly
    如果您只想搜索事件日志,请使用此开关参数。
.PARAMETER LogFilesOnly
    如果您只想搜索文件系统中的日志,请使用此参数。
.PARAMETER ExcludeDirectory
    如果在文件系统上搜索,请指定要跳过的任何文件夹路径。
.PARAMETER FileExtension
    指定一个或多个逗号分隔的文件扩展名集,您想在文件系统中搜索的。默认为 '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 
 }
 ## 创建疯狂的正则表达式字符串,用于搜索多种不同的日期/时间格式。
 ## 这用于尝试在找到的每个文本文件中搜索日期/时间字符串
 ##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 cmdlet 是一个很棒的命令,如果你需要快速查询其中一个常见事件日志,它非常方便。它易于使用,并提供了一些基本的过滤能力。但是,如果您需要进行深入的事件日志调查,Get-WinEvent 命令可能会更好用,但它稍微难以使用,有时需要了解像 XPath 这样的语法。

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