PowerShell 技能连载 - Windows 事件日志分析

适用于 PowerShell 5.1 及以上版本

Windows 事件日志是排查系统问题、监控安全事件的重要数据源。无论是排查服务崩溃、追踪登录失败,还是审计权限变更,事件日志都记录着关键的操作痕迹。传统的”事件查看器”图形界面虽然直观,但面对大规模日志筛选和批量分析时效率极低。

PowerShell 提供了强大的事件日志查询能力,可以像操作数据库一样对日志进行精确筛选和统计。本文将介绍如何使用 Get-WinEvent 高效查询事件日志,以及如何构建自动化的日志分析脚本。

基础查询

Get-WinEvent 是查询事件日志的核心 cmdlet,比旧版的 Get-EventLog 性能更好且功能更强。

1
2
3
4
# 查询最近 10 条系统日志
Get-WinEvent -LogName 'System' -MaxEvents 10 |
Select-Object TimeCreated, Id, LevelDisplayName, Message |
Format-Table -AutoSize
1
2
3
4
5
6
TimeCreated          Id LevelDisplayName Message
----------- -- ---------------- -------
2025/4/8 9:45:12 7036 信息 Windows Update 服务已停止...
2025/4/8 9:44:30 7036 信息 DNS Client 服务已停止...
2025/4/8 9:43:15 1001 信息 Windows 错误报告...
2025/4/8 9:42:00 7024 错误 服务意外终止...

可以查看系统中所有可用的日志列表。

1
2
3
4
5
6
7
# 列出所有已注册的事件日志
Get-WinEvent -ListLog '*' |
Where-Object { $_.RecordCount -gt 0 } |
Select-Object LogName, RecordCount, FileSize |
Sort-Object RecordCount -Descending |
Select-Object -First 10 |
Format-Table -AutoSize

使用 FilterHashtable 高效筛选

直接通过管道筛选 Get-WinEvent 的输出效率很低,因为 PowerShell 会先获取所有事件再过滤。推荐使用 -FilterHashtable 参数在服务端进行筛选,大幅提升查询速度。

1
2
3
4
5
6
7
8
9
10
# 查询过去 24 小时内的系统错误日志
$startTime = (Get-Date).AddDays(-1)

Get-WinEvent -FilterHashtable @{
LogName = 'System'
Level = 2 # 2=错误,3=警告,4=信息
StartTime = $startTime
} |
Select-Object TimeCreated, Id, Message |
Format-Table -AutoSize -Wrap

Level 的值含义如下:

级别
1 关键 (Critical)
2 错误 (Error)
3 警告 (Warning)
4 信息 (Information)
0 详细 (Verbose)

按 EventId 精确筛选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查询所有登录失败事件(EventId 4625)
Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4625
} -MaxEvents 20 |
ForEach-Object {
# 从 XML 中提取详细信息
$xml = [xml]$_.ToXml()
$ns = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$ns.AddNamespace('evt', 'http://schemas.microsoft.com/win/2004/08/events/event')

[PSCustomObject]@{
时间 = $_.TimeCreated
用户 = $xml.SelectSingleNode('//evt:Data[@Name="TargetUserName"]', $ns).InnerText
来源 IP = $xml.SelectSingleNode('//evt:Data[@Name="IpAddress"]', $ns).InnerText
失败原因 = $xml.SelectSingleNode('//evt:Data[@Name="FailureReason"]', $ns).InnerText
}
} | Format-Table -AutoSize
1
2
3
4
5
时间                用户      来源 IP        失败原因
---- ---- ------- --------
2025/4/8 8:15:23 admin 10.0.0.55 %%2313
2025/4/8 8:12:01 testuser 192.168.1.100 %%2310
2025/4/7 22:30:45 backup 172.16.0.10 %%2313

错误模式识别与统计

对于运维人员来说,识别反复出现的错误模式比逐条查看日志更有价值。下面展示如何对事件日志进行统计分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 统计过去 7 天系统错误日志的分布
$startTime = (Get-Date).AddDays(-7)

Get-WinEvent -FilterHashtable @{
LogName = 'System'
Level = 2
StartTime = $startTime
} |
Group-Object -Property Id |
Sort-Object Count -Descending |
Select-Object -First 10 Count, Name |
ForEach-Object {
# 获取该 EventId 的描述
$sampleEvent = Get-WinEvent -FilterHashtable @{
LogName = 'System'
Id = [int]$_.Name
} -MaxEvents 1

[PSCustomObject]@{
出现次数 = $_.Count
事件ID = $_.Name
示例来源 = $sampleEvent.ProviderName
}
} | Format-Table -AutoSize

按时间段统计事件趋势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 按小时统计错误事件数量
$startTime = (Get-Date).AddDays(-1)

Get-WinEvent -FilterHashtable @{
LogName = 'System'
Level = 2
StartTime = $startTime
} |
Group-Object { $_.TimeCreated.ToString('yyyy-MM-dd HH:00') } |
Sort-Object Name |
ForEach-Object {
[PSCustomObject]@{
时间段 = $_.Name
错误数 = $_.Count
分布 = ('#' * [math]::Min($_.Count, 50))
}
} | Format-Table -AutoSize
1
2
3
4
5
6
7
8
时间段         错误数  分布
------ ------ ----
2025-04-07 10:00 3 ###
2025-04-07 11:00 7 #######
2025-04-07 12:00 2 ##
2025-04-07 14:00 12 ############
2025-04-07 15:00 5 #####
2025-04-08 08:00 4 ####

日志导出与归档

定期归档日志可以防止日志文件过大导致查询变慢,也便于历史回溯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 导出指定时间范围的安全日志为 CSV
$exportParams = @{
LogName = 'Security'
StartTime = (Get-Date).AddDays(-30)
EndTime = (Get-Date).AddDays(-7)
}

$exportPath = "C:\Logs\Security_Export_$(Get-Date -Format 'yyyyMMdd').csv"

Get-WinEvent @exportParams |
Select-Object TimeCreated, Id, LevelDisplayName, Message |
Export-Csv -Path $exportPath -NoTypeInformation -Encoding UTF8

Write-Host "日志已导出到:$exportPath"
$fileSize = (Get-Item $exportPath).Length / 1MB
Write-Host "文件大小:$([math]::Round($fileSize, 2)) MB"

使用 FilterXML 进行复杂查询

FilterHashtable 无法满足复杂查询需求时,可以使用 XML 精确定义查询条件。在”事件查看器”中可以先手动筛选,然后切换到”XML”选项卡复制查询语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 使用 XML 筛选:查询多个事件源的组合条件
$query = @'
<QueryList>
<Query Id="0" Path="System">
<Select Path="System">
*[System[Provider[@Name='Service Control Manager'] and (Level=2 or Level=3)]]
and
*[System[TimeCreated[@SystemTime&gt;='2025-04-01T00:00:00']]]
</Select>
</Query>
</QueryList>
'@

Get-WinEvent -FilterXml $query |
Select-Object TimeCreated, Id, LevelDisplayName, Message |
Format-Table -AutoSize -Wrap

构建自动化监控脚本

将日志查询整合到监控脚本中,可以实现自动告警。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 监控脚本:检测磁盘相关错误并在超过阈值时发出警告
$threshold = 5
$lookbackMinutes = 30

$diskErrors = Get-WinEvent -FilterHashtable @{
LogName = 'System'
Level = 2
StartTime = (Get-Date).AddMinutes(-$lookbackMinutes)
} | Where-Object { $_.Message -match 'disk|storage|volume' }

if ($diskErrors.Count -ge $threshold) {
$body = "过去 $lookbackMinutes 分钟内检测到 $($diskErrors.Count) 条磁盘相关错误:`n`n"
$diskErrors | ForEach-Object {
$body += "[$($_.TimeCreated)] EventId: $($_.Id) - $($_.Message.Substring(0, [math]::Min(100, $_.Message.Length)))...`n"
}
Write-Warning "磁盘错误数量超过阈值!"
Write-Host $body
# 实际环境中可调用 Send-MailMessage 或其他通知接口
} else {
Write-Host "磁盘状态正常,过去 $lookbackMinutes 分钟内共 $($diskErrors.Count) 条错误。"
}

注意事项

  • 查询安全日志需要管理员权限,请以”以管理员身份运行”启动 PowerShell
  • Get-WinEvent-FilterHashtableWhere-Object 管道筛选性能好数十倍,应优先使用
  • 查询大量日志时,合理使用 -MaxEvents 参数限制返回数量,避免内存占用过高
  • 对于已归档的 .evtx 文件,可以使用 Get-WinEvent -Path 直接查询
  • 在 PowerShell 7 中,Get-WinEvent 跨平台可用(需安装相应模块),但在 Linux/macOS 上功能有限
  • 建议将常用的日志查询封装成函数,便于在日常运维中复用

PowerShell 技能连载 - Windows 事件日志分析

http://blog.vichamp.com/2025/04/08/powershell-event-log-analysis/

作者

Victor Woo

发布于

2025-04-08

更新于

2026-04-30

许可协议