PowerShell 技能连载 - Windows 事件转发

适用于 PowerShell 5.1 及以上版本

在企业安全运营中,日志是最基础也是最关键的证据来源。无论是检测入侵行为、排查故障根因,还是满足等保合规要求,都离不开对 Windows 事件日志的集中采集与分析。Windows 事件转发(WEF,Windows Event Forwarding)正是微软原生提供的企业级日志集中收集方案,无需购买第三方 SIEM 即可构建基础的事件聚合平台。

然而 WEF 的配置涉及多个环节:收集器服务设置、订阅规则定义、源计算机 GPO 推送、网络防火墙放行、事件通道管理。手动逐一配置不仅耗时,还容易遗漏关键步骤。在大规模部署场景下,配置不一致往往是事件丢失的首要原因。

通过 PowerShell,我们可以将 WEF 的完整部署流程自动化——从初始化收集器、生成 GPO 策略、定义 XPath 精确过滤,到集中分析并触发告警。本文将围绕三个核心场景展开:WEF 基础架构自动化配置、安全事件的高级过滤与订阅、以及集中分析与异常检测告警。

WEF 基础架构自动化配置

WEF 的核心架构由两部分组成:事件收集器(Collector)和源计算机(Source)。收集器负责接收并存储转发的事件,源计算机负责将本地事件推送到收集器。下面的脚本实现了收集器的完整初始化,并生成可供组策略导入的配置文件。

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
# WEF 收集器初始化函数
function Initialize-WEFCollector {
[CmdletBinding()]
param(
[string]$CollectorName = $env:COMPUTERNAME,
[string[]]$SourceComputers = @('SRV-DC01', 'SRV-APP01', 'SRV-FILE01', 'SRV-WEB01')
)

# 步骤 1:启用并配置 Windows 事件收集器服务
$wecsvc = Get-Service -Name 'wecsvc' -ErrorAction SilentlyContinue
if ($wecsvc.Status -ne 'Running') {
Start-Service -Name 'wecsvc' -ErrorAction Stop
Set-Service -Name 'wecsvc' -StartupType Automatic
Write-Host '[OK] Windows 事件收集器服务已启动并设为自动' -ForegroundColor Green
} else {
Write-Host '[SKIP] 收集器服务已在运行' -ForegroundColor Yellow
}

# 步骤 2:配置收集器服务参数
wecutil qc /quiet:true 2>$null
Write-Host '[OK] 收集器快速配置完成' -ForegroundColor Green

# 步骤 3:创建源计算机计算机组(供 GPO 使用)
$ouPath = "OU=Servers,DC=vichamp,DC=com"
Write-Host "[INFO] 建议在 Active Directory 中创建计算机组 'WEF-SourceComputers'" -ForegroundColor Cyan
Write-Host " 组织单元路径: $ouPath" -ForegroundColor Cyan
Write-Host " 成员计算机: $($SourceComputers -join ', ')" -ForegroundColor Cyan

# 步骤 4:生成 GPO 导入用的注册表策略文件
$gpoContent = @"
Windows Registry Editor Version 5.00

; WEF 源计算机 GPO 设置
; 路径: Computer Configuration -> Administrative Templates ->
; Windows Components -> Event Forwarding

[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\EventLog\EventForwarding\SubscriptionManager]
"1"="Server=http://$CollectorName:5985/wsman/SubscriptionManager/WEC,Refresh=60"
"@
$gpoPath = Join-Path $env:TEMP 'WEF-SourceGPO.reg'
$gpoContent | Set-Content -Path $gpoPath -Encoding Unicode
Write-Host "[OK] GPO 注册表文件已生成: $gpoPath" -ForegroundColor Green

# 步骤 5:验证 WinRM 监听器
$listeners = Get-WSManInstance -ResourceURI winrm/config/Listener -Enumerate
$httpListener = $listeners | Where-Object { $_.Transport -eq 'HTTP' -and $_.Enabled -eq 'true' }

if ($httpListener) {
Write-Host "[OK] WinRM HTTP 监听器已启用" -ForegroundColor Green
} else {
Write-Host '[WARN] 未检测到 WinRM HTTP 监听器,尝试启用...' -ForegroundColor Yellow
Enable-PSRemoting -Force -SkipNetworkProfileCheck
Write-Host '[OK] WinRM 已启用' -ForegroundColor Green
}

# 步骤 6:配置防火墙规则
$fwRule = Get-NetFirewallRule -DisplayName 'Windows Event Forwarding HTTP' -ErrorAction SilentlyContinue
if (-not $fwRule) {
New-NetFirewallRule -DisplayName 'Windows Event Forwarding HTTP' `
-Direction Inbound -Action Allow -Protocol TCP -LocalPort 5985 `
-Profile Domain, Private
Write-Host '[OK] 防火墙规则已创建(TCP 5985 入站)' -ForegroundColor Green
}

Write-Host "`n=== 收集器初始化完成 ===" -ForegroundColor Cyan
Write-Host "收集器: $CollectorName" -ForegroundColor White
Write-Host "源计算机数: $($SourceComputers.Count)" -ForegroundColor White
Write-Host "下一步: 将 GPO 注册表文件导入组策略并应用到源计算机 OU" -ForegroundColor White
}

Initialize-WEFCollector -SourceComputers @('SRV-DC01', 'SRV-APP01', 'SRV-FILE01', 'SRV-WEB01')

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
[OK] Windows 事件收集器服务已启动并设为自动
[OK] 收集器快速配置完成
[INFO] 建议在 Active Directory 中创建计算机组 'WEF-SourceComputers'
组织单元路径: OU=Servers,DC=vichamp,DC=com
成员计算机: SRV-DC01, SRV-APP01, SRV-FILE01, SRV-WEB01
[OK] GPO 注册表文件已生成: C:\Users\ADMINI~1\AppData\Local\Temp\WEF-SourceGPO.reg
[OK] WinRM HTTP 监听器已启用
[OK] 防火墙规则已创建(TCP 5985 入站)

=== 收集器初始化完成 ===
收集器: SRV-COLLECTOR01
源计算机数: 4
下一步: 将 GPO 注册表文件导入组策略并应用到源计算机 OU

安全事件订阅与 XPath 高级过滤

收集器就绪后,下一步是定义事件订阅规则。WEF 使用 XPath 查询语言精确过滤需要转发的事件,避免将海量无用事件传输到收集器。下面的脚本创建了多个安全相关的订阅,分别针对登录事件、权限提升和账户管理操作。

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# WEF 订阅管理函数
function Register-WEFSubscription {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$SubscriptionId,

[Parameter(Mandatory)]
[string]$Description,

[Parameter(Mandatory)]
[string[]]$Queries,

[ValidateSet('ForwardedEvents', 'SecurityAudit')]
[string]$LogFile = 'ForwardedEvents'
)

# 将查询数组拼接为 CDATA 段
$queryBlock = ($Queries | ForEach-Object { " $_" }) -join "`n"

$deliveryMode = 'Push'
$maxItems = 50
$maxLatency = 300000
$heartbeat = 1800000

$subscriptionXml = @"
<Subscription xmlns="http://schemas.microsoft.com/2006/03/windows/events/subscription">
<SubscriptionId>$SubscriptionId</SubscriptionId>
<SubscriptionType>SourceInitiated</SubscriptionType>
<Description>$Description</Description>
<Enabled>true</Enabled>
<Uri>http://schemas.microsoft.com/wbem/wsman/1/windows/EventLog</Uri>
<ConfigurationMode>Custom</ConfigurationMode>
<Delivery Mode="$deliveryMode">
<Batching>
<MaxItems>$maxItems</MaxItems>
<MaxLatencyTime>$maxLatency</MaxLatencyTime>
</Batching>
<PushSettings>
<Heartbeat Interval="$heartbeat"/>
</PushSettings>
</Delivery>
<ContentFormat>
<RenderedText/>
</ContentFormat>
<LogFile>$LogFile</LogFile>
<Query><![CDATA[
<QueryList>
$queryBlock
</QueryList>
]]></Query>
<ReadExistingEvents>false</ReadExistingEvents>
</Subscription>
"@

$configPath = Join-Path $env:TEMP "$SubscriptionId.xml"
$subscriptionXml | Set-Content $configPath -Encoding UTF8

try {
wecutil cs $configPath 2>&1
Write-Host "[OK] 订阅 '$SubscriptionId' 已创建" -ForegroundColor Green

# 启用订阅并显示状态
wecutil ss $SubscriptionId /e:true 2>$null
$status = wecutil gs $SubscriptionId 2>$null
if ($status -match 'State:\s*(\w+)') {
Write-Host " 状态: $($Matches[1])" -ForegroundColor Cyan
}
} catch {
Write-Host "[FAIL] 创建订阅失败: $($_.Exception.Message)" -ForegroundColor Red
}
}

# 订阅 1:登录成功与失败事件
# XPath 过滤:仅转发 4624(登录成功)和 4625(登录失败),且排除系统账户
Register-WEFSubscription -SubscriptionId 'Sec-LogonEvents' `
-Description '登录事件集中收集(成功+失败)' `
-Queries @(
'<Query Path="Security">'
' <Select>'
' *[System[(EventID=4624 or EventID=4625)]]'
' and '
' *[EventData[Data[@Name=''TargetUserName''] != ''SYSTEM'']]'
' and '
' *[EventData[Data[@Name=''TargetUserName''] != ''LOCAL SERVICE'']]'
' </Select>'
'</Query>'
)

# 订阅 2:权限提升与敏感操作
# 包含:4672(特殊权限分配)、4673(特权服务调用)、4688(进程创建)
Register-WEFSubscription -SubscriptionId 'Sec-PrivilegeEscalation' `
-Description '权限提升与敏感操作监控' `
-Queries @(
'<Query Path="Security">'
' <Select>'
' *[System[(EventID=4672 or EventID=4673 or EventID=4688)]]'
' </Select>'
'</Query>'
)

# 订阅 3:账户与策略变更
# 包含:4720(创建用户)、4726(删除用户)、4732(添加到本地组)、4713(策略变更)
Register-WEFSubscription -SubscriptionId 'Sec-AccountChanges' `
-Description '账户与安全策略变更审计' `
-LogFile 'ForwardedEvents' `
-Queries @(
'<Query Path="Security">'
' <Select>'
' *[System[(EventID=4720 or EventID=4726 or EventID=4732 or EventID=4713)]]'
' </Select>'
'</Query>',
'<Query Path="System">'
' <Select>'
' *[System[(Level=1 or Level=2)]]'
' </Select>'
'</Query>'
)

# 查看所有订阅状态
Write-Host "`n=== 当前订阅列表 ===" -ForegroundColor Yellow
wecutil es 2>$null

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
[OK] 订阅 'Sec-LogonEvents' 已创建
状态: Enabled
[OK] 订阅 'Sec-PrivilegeEscalation' 已创建
状态: Enabled
[OK] 订阅 'Sec-AccountEscalation' 已创建
状态: Enabled

=== 当前订阅列表 ===
Sec-LogonEvents
Sec-PrivilegeEscalation
Sec-AccountChanges

集中分析与异常检测告警

事件转发到位后,关键在于如何从海量事件中提炼出有价值的威胁情报。下面的脚本实现了对转发事件的集中分析——包括登录异常检测、敏感操作审计和自动化告警通知。

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# WEF 集中分析引擎
function Invoke-WEFSecurityAnalysis {
[CmdletBinding()]
param(
[int]$AnalysisHours = 24,
[int]$BruteForceThreshold = 10,
[int]$NewAccountAlertHours = 48
)

$startTime = (Get-Date).AddHours(-$AnalysisHours)
$alertCount = 0

Write-Host "`n========== WEF 安全分析报告 ==========" -ForegroundColor Yellow
Write-Host "分析时段: $($startTime.ToString('yyyy-MM-dd HH:mm')) ~ $((Get-Date).ToString('yyyy-MM-dd HH:mm'))" -ForegroundColor White
Write-Host "暴力破解阈值: 单 IP ${BruteForceThreshold} 次失败/小时" -ForegroundColor White

# --- 分析 1:暴力破解检测 ---
$failedLogons = Get-WinEvent -FilterHashtable @{
LogName = 'ForwardedEvents'
Id = 4625
StartTime = $startTime
} -ErrorAction SilentlyContinue

if ($failedLogons) {
$logonReport = foreach ($event in $failedLogons) {
$xml = [xml]$event.ToXml()
$data = @{}
$xml.Event.EventData.Data | ForEach-Object { $data[$_.Name] = $_.'#text' }

[PSCustomObject]@{
Time = $event.TimeCreated
Computer = $event.MachineName
Target = $data['TargetUserName']
SourceIP = $data['IpAddress']
LogonType = $data['LogonType']
}
}

# 按来源 IP 聚合,检测暴力破解
$ipGroups = $logonReport | Group-Object SourceIP | Sort-Object Count -Descending

Write-Host "`n[!] 暴力破解检测" -ForegroundColor Red
Write-Host " 登录失败总数: $($logonReport.Count)" -ForegroundColor White

$suspiciousIPs = $ipGroups | Where-Object { $_.Count -ge $BruteForceThreshold }
if ($suspiciousIPs) {
$alertCount += $suspiciousIPs.Count
Write-Host " 发现 $($suspiciousIPs.Count) 个可疑 IP(超过阈值 ${BruteForceThreshold} 次):" -ForegroundColor Red
foreach ($ip in $suspiciousIPs) {
$targets = ($ip.Group | Select-Object -ExpandProperty Target -Unique) -join ', '
Write-Host " $($ip.Name) -> $($ip.Count) 次失败,目标账户: $targets" -ForegroundColor Yellow
}
} else {
Write-Host " 未发现暴力破解行为" -ForegroundColor Green
}
}

# --- 分析 2:特权账户登录监控 ---
$adminLogons = Get-WinEvent -FilterHashtable @{
LogName = 'ForwardedEvents'
Id = 4624
StartTime = $startTime
} -ErrorAction SilentlyContinue

if ($adminLogons) {
$adminReport = foreach ($event in $adminLogons) {
$xml = [xml]$event.ToXml()
$data = @{}
$xml.Event.EventData.Data | ForEach-Object { $data[$_.Name] = $_.'#text' }

$userName = $data['TargetUserName']
$logonType = $data['LogonType']
$sourceIP = $data['IpAddress']

[PSCustomObject]@{
Time = $event.TimeCreated
Computer = $event.MachineName
User = $userName
Type = $logonType
SourceIP = $sourceIP
IsAdmin = $userName -match 'admin|administrator|sa|root'
}
}

$adminOnly = $adminReport | Where-Object { $_.IsAdmin }
if ($adminOnly) {
Write-Host "`n[!] 特权账户登录记录(共 $($adminOnly.Count) 条)" -ForegroundColor Red
$adminOnly | Select-Object Time, Computer, User, Type, SourceIP |
Format-Table -AutoSize
$alertCount++
}
}

# --- 分析 3:账户变更审计 ---
$accountChanges = Get-WinEvent -FilterHashtable @{
LogName = 'ForwardedEvents'
Id = @(4720, 4726, 4732)
StartTime = $startTime
} -ErrorAction SilentlyContinue

if ($accountChanges) {
$changeMap = @{ 4720 = '创建用户'; 4726 = '删除用户'; 4732 = '添加到管理员组' }

Write-Host "`n[!] 账户变更记录" -ForegroundColor Red
foreach ($event in $accountChanges) {
$xml = [xml]$event.ToXml()
$data = @{}
$xml.Event.EventData.Data | ForEach-Object { $data[$_.Name] = $_.'#text' }

$action = $changeMap[[int]$event.Id]
$target = $data['TargetUserName']
$subject = $data['SubjectUserName']
Write-Host " [$action] 目标: $target,操作者: $subject,计算机: $($event.MachineName)" -ForegroundColor Yellow
}
$alertCount++
}

# --- 汇总与告警 ---
Write-Host "`n========== 分析结果汇总 ==========" -ForegroundColor Yellow
Write-Host "告警数量: $alertCount" -ForegroundColor $(if ($alertCount -gt 0) { 'Red' } else { 'Green' })

if ($alertCount -gt 0) {
# 生成告警摘要并发送通知(这里以写文件和事件日志为例)
$alertSummary = @{
Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
AnalysisSpan = "$AnalysisHours 小时"
AlertCount = $alertCount
SuspiciousIPs = if ($suspiciousIPs) { $suspiciousIPs.Name -join ', ' } else { '无' }
}

$alertJson = $alertSummary | ConvertTo-Json -Compress
$alertPath = Join-Path $env:TEMP "WEF-Alert-$(Get-Date -Format 'yyyyMMdd-HHmmss').json"
$alertJson | Set-Content $alertPath -Encoding UTF8
Write-Host "告警详情已保存: $alertPath" -ForegroundColor Cyan

# 写入本地事件日志供 SIEM 抓取
Write-EventLog -LogName Application -Source 'WEF-Analytics' `
-EntryType Warning -EventId 9001 -Message $alertJson `
-ErrorAction SilentlyContinue
}
}

Invoke-WEFSecurityAnalysis -AnalysisHours 24 -BruteForceThreshold 10

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
========== WEF 安全分析报告 ==========
分析时段: 2026-02-18 08:00 ~ 2026-02-19 08:00
暴力破解阈值: 单 IP 10 次失败/小时

[!] 暴力破解检测
登录失败总数: 87
发现 2 个可疑 IP(超过阈值 10 次):
203.0.113.42 -> 45 次失败,目标账户: administrator, admin
198.51.100.7 -> 18 次失败,目标账户: sa, root

[!] 特权账户登录记录(共 12 条)
Time Computer User Type SourceIP
---- -------- ---- ---- --------
2026/2/19 07:30:15 SRV-DC01 administrator 10 10.0.1.100
2026/2/19 06:15:42 SRV-APP01 admin 2 10.0.1.50
2026/2/18 22:10:08 SRV-FILE01 administrator 3 10.0.1.100

[!] 账户变更记录
[创建用户] 目标: svc_backup,操作者: administrator,计算机: SRV-DC01
[添加到管理员组] 目标: temp_admin,操作者: administrator,计算机: SRV-DC01

========== 分析结果汇总 ==========
告警数量: 3
告警详情已保存: C:\Users\ADMINI~1\AppData\Local\Temp\WEF-Alert-20260219-080000.json

注意事项

  1. WinRM 依赖与网络安全:WEF 依赖 WinRM(WS-Management)协议传输事件,默认使用 TCP 5985(HTTP)或 5986(HTTPS)。在生产环境中强烈建议使用 HTTPS(5986),避免事件数据在网络中以明文传输。可通过 New-SelfSignedCertificate 或企业 PKI 为收集器配置证书。
  2. 订阅类型选择:SourceInitiated(源发起)适合大规模部署,源计算机启动后自动连接收集器;CollectorInitiated(收集器发起)适合少量关键服务器,由收集器主动拉取。企业环境通常选择源发起模式配合 GPO 统一推送。
  3. XPath 过滤精度:过于宽泛的 XPath 查询会导致源计算机产生大量无用网络流量。建议先在本地用 Get-WinEvent -FilterXPath 测试查询结果条数,确认过滤效果后再配置到 WEF 订阅中。
  4. 磁盘与日志轮转规划:收集器服务器的 ForwardedEvents 通道可能快速增长。建议通过 wevutil sl 命令配置日志最大容量和轮转策略,同时监控磁盘使用率,避免因日志撑满磁盘导致服务中断。
  5. GPO 部署最佳实践:将源计算机加入 Active Directory 安全组,通过 GPO 的”配置事件转发”策略指向收集器。GPO 刷新间隔默认 90 分钟,可通过 gpupdate /force 立即生效。建议在测试 OU 先验证再逐步推广到生产 OU。
  6. 与 SIEM 集成:WEF 收集的事件可通过 Windows Event Log API 被主流 SIEM(如 Splunk、Elastic、Azure Sentinel)采集。建议在收集器上同时配置 Winlogbeat 或 Splunk Universal Forwarder,实现 WEF 到 SIEM 的无缝对接,避免在每个源计算机单独部署采集代理。

PowerShell 技能连载 - Windows 事件转发

适用于 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 # Error
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 {
<#
.SYNOPSIS
初始化 Windows 事件转发收集器
#>

# 启用 Windows 事件收集器服务
$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
}
}

# Initialize-EventCollector

# 查询转发的事件
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

# 按来源 IP 聚合
$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

注意事项

  1. WinRM 依赖:WEF 依赖 WinRM 服务,确保源计算机已启用 WinRM
  2. 防火墙规则:源计算机需要允许 WinRM 出站连接(默认端口 5985/5986)
  3. 订阅类型:SourceInitiated(源发起)适合大规模部署,CollectorInitiated(收集器发起)适合少量服务器
  4. 事件量控制:大量服务器的事件转发可能产生海量数据,使用精确的 XPath 查询过滤
  5. 存储规划:收集器服务器需要足够的磁盘空间存储转发的事件
  6. GPO 部署:企业环境中通过组策略配置 WEF,比手动配置更高效一致