适用于 PowerShell 5.1 及以上版本(Windows),需要管理员权限
在企业环境中,每台 Windows 服务器都在本地记录事件日志。当需要排查跨服务器的问题时,逐台登录查看日志效率极低。Windows 事件转发(WEF,Windows Event Forwarding)可以将多台服务器的事件日志集中转发到收集器服务器,实现集中化日志管理。结合 PowerShell 的日志查询能力,可以构建强大的安全监控和故障排查平台。
本文将讲解如何使用 PowerShell 配置和管理 Windows 事件转发。
事件日志查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| $events = Get-WinEvent -LogName "Application" -MaxEvents 10 -ErrorAction SilentlyContinue $events | Select-Object TimeCreated, LevelDisplayName, ProviderName, @{N='Message'; E={if ($_.Message.Length -gt 80) { $_.Message.Substring(0, 80) + "..." } else { $_.Message }}} | Format-Table -AutoSize -Wrap
$errors = Get-WinEvent -FilterHashtable @{ LogName = "Application" Level = 2 StartTime = (Get-Date).AddHours(-24) } -ErrorAction SilentlyContinue
Write-Host "过去 24 小时应用错误:$($errors.Count) 条" -ForegroundColor Red
$logons = Get-WinEvent -FilterHashtable @{ LogName = "Security" Id = 4624 StartTime = (Get-Date).AddHours(-1) } -MaxEvents 10 -ErrorAction SilentlyContinue
if ($logons) { $logonDetails = foreach ($event in $logons) { $xml = [xml]$event.ToXml() $targetUser = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'TargetUserName' }).'#text' $logonType = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'LogonType' }).'#text' $sourceIP = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'IpAddress' }).'#text'
[PSCustomObject]@{ Time = $event.TimeCreated.ToString('HH:mm:ss') User = $targetUser Type = $logonType SourceIP = $sourceIP } } $logonDetails | Format-Table -AutoSize }
$boots = Get-WinEvent -FilterHashtable @{ LogName = "System" Id = 12 } -MaxEvents 5 -ErrorAction SilentlyContinue
Write-Host "`n最近系统启动:" -ForegroundColor Cyan $boots | ForEach-Object { Write-Host " $($_.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss'))" }
|
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12
| 过去 24 小时应用错误:12 条
Time User Type SourceIP ---- ---- ---- -------- 08:15:30 admin 2 192.168.1.100 08:20:45 svc_app 3 127.0.0.1 08:35:10 admin 10 10.0.1.50
最近系统启动: 2025-08-14 06:00:00 2025-08-07 06:00:00 2025-08-01 02:30:00
|
事件转发配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| function Initialize-EventCollector {
$wecSvc = Get-Service -Name "Wecsvc" -ErrorAction SilentlyContinue if ($wecSvc.Status -ne 'Running') { Start-Service -Name "Wecsvc" Set-Service -Name "Wecsvc" -StartupType Automatic Write-Host "Windows 事件收集器服务已启动" -ForegroundColor Green }
$subscriptionXml = @" <Subscription xmlns="http://schemas.microsoft.com/2006/03/windows/events/subscription"> <SubscriptionId>SecurityEvents</SubscriptionId> <SubscriptionType>SourceInitiated</SubscriptionType> <Description>安全事件集中收集</Description> <Enabled>true</Enabled> <Uri>http://schemas.microsoft.com/wbem/wsman/1/windows/EventLog</Uri> <ConfigurationMode>Custom</ConfigurationMode> <Delivery Mode="Push"> <Batching> <MaxItems>20</MaxItems> <MaxLatencyTime>300000</MaxLatencyTime> </Batching> <PushSettings> <Heartbeat Interval="3600000"/> </PushSettings> </Delivery> <ContentFormat> <RenderedText/> </ContentFormat> <LogFile>ForwardedEvents</LogFile> <Query> <![CDATA[ <QueryList> <Query Path="Security"> <Select>*[System[(EventID=4624 or EventID=4625 or EventID=4648 or EventID=4672)]]</Select> </Query> <Query Path="System"> <Select>*[System[Level=1 or Level=2 or Level=3]]</Select> </Query> </QueryList> ]]> </Query> <ReadExistingEvents>false</ReadExistingEvents> </Subscription> "@
$configPath = "$env:TEMP\SecurityEvents.xml" $subscriptionXml | Set-Content $configPath -Encoding UTF8
try { wecutil cs $configPath 2>&1 Write-Host "安全事件订阅已创建" -ForegroundColor Green } catch { Write-Host "创建订阅失败:$($_.Exception.Message)" -ForegroundColor Red } }
function Get-ForwardedEvents { param( [int]$Hours = 1, [int]$MaxEvents = 50 )
$events = Get-WinEvent -FilterHashtable @{ LogName = "ForwardedEvents" StartTime = (Get-Date).AddHours(-$Hours) } -MaxEvents $MaxEvents -ErrorAction SilentlyContinue
if (-not $events) { Write-Host "无转发事件" -ForegroundColor Yellow return }
$summary = $events | Group-Object ProviderName | Sort-Object Count -Descending Write-Host "转发事件概览(过去 $Hours 小时):" -ForegroundColor Cyan $summary | Select-Object Count, Name | Format-Table -AutoSize
return $events }
Get-ForwardedEvents -Hours 4 -MaxEvents 100
|
执行结果示例:
1 2 3 4 5 6 7
| 安全事件订阅已创建 转发事件概览(过去 4 小时): Count Name ----- ---- 45 Microsoft-Windows-Security-Auditing 23 Service Control Manager 12 Microsoft-Windows-Kernel-General
|
安全事件分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| function Get-FailedLogonReport { param([int]$Hours = 24)
$failedLogons = Get-WinEvent -FilterHashtable @{ LogName = "ForwardedEvents" Id = 4625 StartTime = (Get-Date).AddHours(-$Hours) } -ErrorAction SilentlyContinue
if (-not $failedLogons) { Write-Host "无登录失败事件" -ForegroundColor Green return }
$report = foreach ($event in $failedLogons) { $xml = [xml]$event.ToXml() $data = @{} $xml.Event.EventData.Data | ForEach-Object { $data[$_.Name] = $_.'#text' }
[PSCustomObject]@{ Time = $event.TimeCreated.ToString('yyyy-MM-dd HH:mm:ss') Computer = $event.MachineName Target = $data['TargetUserName'] SourceIP = $data['IpAddress'] LogonType = $data['LogonType'] Failure = $data['SubStatus'] } }
Write-Host "登录失败报告(过去 $Hours 小时):" -ForegroundColor Red Write-Host "总计:$($report.Count) 次失败" -ForegroundColor Yellow
$byIP = $report | Group-Object SourceIP | Sort-Object Count -Descending | Select-Object -First 10 Write-Host "`n按来源 IP 聚合:" -ForegroundColor Cyan $byIP | Select-Object Count, Name | Format-Table -AutoSize
$byUser = $report | Group-Object Target | Sort-Object Count -Descending | Select-Object -First 5 Write-Host "按目标用户聚合:" -ForegroundColor Cyan $byUser | Select-Object Count, Name | Format-Table -AutoSize }
Get-FailedLogonReport -Hours 24
|
执行结果示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 登录失败报告(过去 24 小时): 总计:45 次失败
按来源 IP 聚合: Count Name ----- ---- 23 192.168.1.100 12 10.0.1.50 5 203.0.113.42 3 172.16.0.5 2 192.168.1.200
按目标用户聚合: Count Name ----- ---- 30 administrator 8 admin 5 root 2 sa
|
注意事项
- WinRM 依赖:WEF 依赖 WinRM 服务,确保源计算机已启用 WinRM
- 防火墙规则:源计算机需要允许 WinRM 出站连接(默认端口 5985/5986)
- 订阅类型:SourceInitiated(源发起)适合大规模部署,CollectorInitiated(收集器发起)适合少量服务器
- 事件量控制:大量服务器的事件转发可能产生海量数据,使用精确的 XPath 查询过滤
- 存储规划:收集器服务器需要足够的磁盘空间存储转发的事件
- GPO 部署:企业环境中通过组策略配置 WEF,比手动配置更高效一致