forked from microsoft/msquic
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathInvoke-EtwTraceCollection.ps1
200 lines (160 loc) · 6.83 KB
/
Invoke-EtwTraceCollection.ps1
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
<#
.SYNOPSIS
This script invokes an ETW trace collection
.PARAMETER SessionName
The name of an ETW session.
.PARAMETER SessionName
The name of a netsh tracing scenario to start.
.PARAMETER EtlPath
The name of an ETL file.
.PARAMETER TmfPath
The path of the TMF files (used to convert ETL).
.PARAMETER Start
If set, starts a new netsh.exe trace session for the given NetshScenario.
.PARAMETER Stop
If set, stops a running netsh.exe trace session.
.PARAMETER Flush
If set, flushes the specified SessionName. Also can populate EtlPath
automatically from the ETW session configuration.
.PARAMETER Convert
If set, convert the (either manually specified or automatically populated
by -Flush) EtlPath to text.
.PARAMETER Sanitize
If set, sanitizes IP addresses in the converted ETL file.
#>
param (
[Parameter(Mandatory = $false)]
[string]$SessionName = "",
[Parameter(Mandatory = $false)]
[string]$NetshScenario = "",
[Parameter(Mandatory = $false)]
[string]$EtlPath = "",
[Parameter(Mandatory = $false)]
[string]$TmfPath = "",
[Parameter(Mandatory = $false)]
[switch]$Start,
[Parameter(Mandatory = $false)]
[switch]$Stop,
[Parameter(Mandatory = $false)]
[switch]$Flush,
[Parameter(Mandatory = $false)]
[switch]$Convert,
[Parameter(Mandatory = $false)]
[switch]$Sanitize
)
Set-StrictMode -Version 'Latest'
$PSDefaultParameterValues['*:ErrorAction'] = 'Stop'
$IPv4Pattern = '(((25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))'
$IPv6Pattern = ":(?::[a-f\d]{1,4}){0,5}(?:(?::[a-f\d]{1,4}){1,2}|:(?:(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.){3}(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})))|[a-f\d]{1,4}:(?:[a-f\d]{1,4}:(?:[a-f\d]{1,4}:(?:[a-f\d]{1,4}:(?:[a-f\d]{1,4}:(?:[a-f\d]{1,4}:(?:[a-f\d]{1,4}:(?:[a-f\d]{1,4}|:)|(?::(?:[a-f\d]{1,4})?|(?:(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.){3}(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))))|:(?:(?:(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.){3}(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))|[a-f\d]{1,4}(?::[a-f\d]{1,4})?|))|(?::(?:(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.){3}(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))|:[a-f\d]{1,4}(?::(?:(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.){3}(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))|(?::[a-f\d]{1,4}){0,2})|:))|(?:(?::[a-f\d]{1,4}){0,2}(?::(?:(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.){3}(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))|(?::[a-f\d]{1,4}){1,2})|:))|(?:(?::[a-f\d]{1,4}){0,3}(?::(?:(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.){3}(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))|(?::[a-f\d]{1,4}){1,2})|:))|(?:(?::[a-f\d]{1,4}){0,4}(?::(?:(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.){3}(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))|(?::[a-f\d]{1,4}){1,2})|:))"
# Compile the regex for performance reasons, also ignore case
$RegexOptions = [System.Text.RegularExpressions.RegexOptions]::Compiled
$RegexOptions += [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
$IPv6Regex = [Regex]::new($IPv6Pattern, $RegexOptions)
$IPv4Regex = [Regex]::new($IPv4Pattern, $RegexOptions)
function Format-IPAddresses {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$True)]
$InputData,
[boolean]$Sanitize
)
process {
if (!$Sanitize) {
return $_
}
$ShrunkLine = $InputData
# Skip to first space to bypass date, which is seen as a valid IPv6 address
$Idx = ([string]$InputData).IndexOf(' ');
if ($Idx -ge 0) {
$ShrunkLine = $InputData.Substring($Idx);
}
$Line = $InputData
# Explicitly check IPv4, as the way IPv4 is printed by ETW
# partially matches the IPv6 cases, which doesn't fully match
# and then exits, leaving the address exposed.
$IPv4Matches = $IPv4Regex.Matches($ShrunkLine)
foreach ($Match in $IPv4Matches) {
$Line = $Line.Replace($Match, "REDACTED");
}
$IPv6Matches = $IPv6Regex.Matches($ShrunkLine)
foreach ($Match in $IPv6Matches) {
$Line = $Line.Replace($Match, "REDACTED");
}
return $Line
}
}
# Starts a netsh tracing ETW session for the given scenario.
if ($Start) {
if ($NetshScenario -eq "") {
Write-Error "No NetshScenario argument present"
}
if ($SessionName -eq "") {
Write-Error "No SessionName argument present"
}
if ($EtlPath -eq "") {
$EtlPath = Join-Path $env:temp "$($SessionName).etl"
}
# Start the ETW session running.
$Command = "netsh.exe trace start scenario=$($NetshScenario) sessionname=$($SessionName) tracefile=$($EtlPath)"
Write-Debug $Command
$Result = Invoke-Expression $Command
}
# Stops a netsh tracing session
if ($Stop) {
if ($SessionName -eq "") {
Write-Error "No SessionName argument present"
}
if ($EtlPath -eq "") {
$EtlPath = Join-Path $env:temp "$($SessionName).etl"
}
# Stop the ETW session.
$Command = "netsh.exe trace stopsessionname=$($SessionName)"
Write-Debug $Command
$Result = Invoke-Expression $Command
}
# Flush an ETW session.
if ($Flush) {
if ($SessionName -eq "") {
Write-Error "No SessionName argument present"
}
# Query the ETW session status (and config).
$Command = "logman.exe query $($SessionName) -ets"
Write-Debug $Command
$QueryResult = Invoke-Expression $Command
if (!$QueryResult.Contains("The command completed successfully.")) {
Write-Error "Unable to find the logging session`n$QueryResult"
}
# Flush the ETW memory buffers to disk.
$Command = "logman.exe update $($SessionName) -ets -fd"
Write-Debug $Command
Invoke-Expression $Command 2>&1 | Out-Null
# Grab the ETL output path if not already specified.
if ($EtlPath -eq "") {
$outputLocationLine = $QueryResult.Split("`r`n") | Select-String "Output Location:"
if ($outputLocationLine -eq "") {
Write-Error "Cannot extract EtlPath output location`n$($QueryResult)"
}
$EtlPath = $outputLocationLine.Line.ToString().Split(':', 2)[1].Trim()
}
}
# Convert an ETL to text (sanitizing as necessary).
if ($Convert) {
if ($EtlPath -eq "") {
Write-Error "No EtlPath argument present"
}
if (!(Test-Path $EtlPath)) {
Write-Error "$EtlPath file not found"
}
# Convert the ETL to text.
$OutputPath = Join-Path $env:temp "temp.log"
$Command = "netsh trace convert $($EtlPath) output=$($OutputPath) overwrite=yes report=no"
if ($TmfPath -ne "") {
$Command += " tmfpath=$($TmfPath)"
}
Write-Debug $Command
Invoke-Expression $Command 2>&1 | Out-Null
# Get the text content, sanitizing as necessary.
Get-Content -Path $OutputPath `
| Where-Object { !$_.Contains("(No Format Information found)")} `
| Format-IPAddresses -Sanitize $Sanitize
}