Last active
August 13, 2024 10:33
-
-
Save mattifestation/edbac1614694886c8ef4583149f53658 to your computer and use it in GitHub Desktop.
Retrieves TraceLogging metadata from a file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#requires -version 5 | |
<# | |
The things you find on Google searching for specific GUIDs... | |
Known Keyword friendly names: | |
"UTC:::CATEGORYDEFINITION.MS.CRITICALDATA":"140737488355328" | |
"UTC:::CATEGORYDEFINITION.MS.MEASURES":"70368744177664" | |
"UTC:::CATEGORYDEFINITION.MS.TELEMETRY":"35184372088832" | |
"UTC:::CATEGORYDEFINITION.MSWLAN.CRITICALDATA":"2147483648" | |
"UTC:::CATEGORYDEFINITION.MSWLAN.MEASURES":"1073741824" | |
"UTC:::CATEGORYDEFINITION.MSWLAN.TELEMETRY":"536870912" | |
"UTC:::CATEGORYDEFINITION.WINEVENT.TELEMETRY":"562949953421312" | |
Known Provider Group Definitions: | |
"UTC:::GROUPDEFINITION.MICROSOFT-APPLICATIONINSIGHTS":"0d943590-b235-5bdb-f854-89520f32fc0b" | |
"UTC:::GROUPDEFINITION.MICROSOFT-APPLICATIONINSIGHTS-DEV":"ba84f32b-8af2-5006-f147-5030cdd7f22d" | |
"UTC:::GROUPDEFINITION.AI":"0d943590-b235-5bdb-f854-89520f32fc0b" | |
"UTC:::GROUPDEFINITION.AI-DEV":"ba84f32b-8af2-5006-f147-5030cdd7f22d" | |
"UTC:::GROUPDEFINITION.ARIA":"780dddc8-18a1-5781-895a-a690464fa89c" | |
"UTC:::GROUPDEFINITION.DEVCNTR":"1d34c0ff-54c5-516f-9ca2-0e20966588a5" | |
"UTC:::GROUPDEFINITION.ENG":"207cf9d5-b3e5-5f45-9a58-c1308f9abdda" | |
"UTC:::GROUPDEFINITION.MICROSOFTTELEMETRY":"4f50731a-89cf-4782-b3e0-dce8c90476ba" | |
"UTC:::GROUPDEFINITION.MICROSOFTWLANTELEMETRY":"976a8310-986e-4640-8bfb-7736ee6d9b65" | |
"UTC:::GROUPDEFINITION.MSPG":"5ECB0BAC-B930-47F5-A8A4-E8253529EDB7" | |
"UTC:::GROUPDEFINITION.OFFICE":"8DBEEE55-EAB8-41BE-988E-B1FAE0397155" | |
"UTC:::GROUPDEFINITION.SEVILLE":"541dae91-cc3c-5807-b064-c2561c16d7e8" | |
"UTC:::GROUPDEFINITION.SKYPE":"9dfc8457-4d69-44c7-8fcd-192290702a89" | |
"UTC:::GROUPDEFINITION.WINDOWSCORETELEMETRY":"c7de053a-0c2e-4a44-91a2-5222ec2ecdf1" | |
"UTC:::GROUPDEFINITION.XBOX.XSAPI":"53b78fc6-e359-453e-89fe-a5f4e5ff4af3" | |
#> | |
# Parsing these out will be useful for grouping by keyword type. e.g. anything not related to telemetry might be more interesting. | |
# Is CRITICALDATA indicative of sensitive data? | |
$KeywordMapping = @{ | |
[UInt64] 140737488355328 = 'MS.CRITICALDATA' | |
[UInt64] 70368744177664 = 'MS.MEASURES' | |
[UInt64] 35184372088832 = 'MS.TELEMETRY' | |
[UInt64] 562949953421312 = 'WINEVENT.TELEMETRY' | |
} | |
enum TlgIn { | |
NULL = 0 | |
UNICODESTRING = 1 | |
ANSISTRING = 2 | |
INT8 = 3 | |
UINT8 = 4 | |
INT16 = 5 | |
UINT16 = 6 | |
INT32 = 7 | |
UINT32 = 8 | |
INT64 = 9 | |
UINT64 = 10 | |
FLOAT = 11 | |
DOUBLE = 12 | |
BOOL32 = 13 | |
BINARY = 14 | |
GUID = 15 | |
POINTER_UNSUPPORTED = 16 | |
FILETIME = 17 | |
SYSTEMTIME = 18 | |
SID = 19 | |
HEXINT32 = 20 | |
HEXINT64 = 21 | |
COUNTEDSTRING = 22 | |
COUNTEDANSISTRING = 23 | |
STRUCT = 24 | |
# The following enum values will not be defined since they collide with other types: | |
# INTPTR, UINTPTR, POINTER | |
# These should have been defined as unique values in TraceLoggingProvider.h | |
} | |
enum TlgOut { | |
NULL = 0 | |
NOPRINT = 1 | |
STRING = 2 | |
BOOLEAN = 3 | |
HEX = 4 | |
PID = 5 | |
TID = 6 | |
PORT = 7 | |
IPV4 = 8 | |
IPV6 = 9 | |
SOCKETADDRESS = 10 | |
XML = 11 | |
JSON = 12 | |
WIN32ERROR = 13 | |
NTSTATUS = 14 | |
HRESULT = 15 | |
FILETIME = 16 | |
SIGNED = 17 | |
UNSIGNED = 18 | |
UTF8 = 35 | |
PKCS7_WITH_TYPE_INFO = 36 | |
CODE_POINTER = 37 | |
} | |
filter Get-TraceLoggingMetadata { | |
<# | |
.SYNOPSIS | |
Retrieves TraceLogging metadata from a file. | |
.DESCRIPTION | |
Get-TraceLoggingMetadata retrieves TraceLogging metadata from a file. TraceLogging is what enables ETW tracing to occur without needing to register/install a manifest. Rather, event metadata is embedded within trace data which Windows 10-based tools are able to parse. Wanting to know what providers and events were possible for a given binary, Get-TraceLoggingMetadata was developed to extract this information. The majority of Get-TraceLoggingMetadata was developed with the assistance of TraceLoggingProvider.h from the Windows SDK. | |
The intended purpose of Get-TraceLoggingMetadata is for research purposes where you would like to discover whether or not interesting providers/events can be traced for one or more binaries. Once an interesting provider is identified, a trace can be captured with a tool like Windows Problem Recorder (wpr.exe). Once a trace is collected, it can be formatted and parsed properly with tracerpt.exe. | |
Author: Matthew Graeber (@mattifestation) | |
License: BSD 3-Clause | |
.PARAMETER Path | |
Specifies the path to a file that may contain trace logging metadata. | |
.EXAMPLE | |
Get-TraceLoggingMetadata -Path C:\Windows\System32\LocationFramework.dll | |
Retrieves trace logging metadata from a specific file. | |
.EXAMPLE | |
Get-ChildItem -Path 'C:\Program Files\*' -Include '*.dll', '*.exe' -Recurse | Get-TraceLoggingMetadata | |
Retrieves trace logging metadata for any EXE or DLL within "C:\Program Files\". | |
.EXAMPLE | |
(Get-Process -Id $PID).Modules.FileName | Get-ChildItem | Get-TraceLoggingMetadata | |
Retrieves trace logging metadata for any loaded module within the current PowerShell process. | |
.INPUTS | |
System.IO.FileInfo | |
Accepts one or more files returned from Get-ChildItem. | |
.OUTPUTS | |
TraceLogging.Schema | |
If a file contains trace logging metadata, an object will be output consisting of provider and event information. Get-TraceLoggingMetadata will only output an object if the file actually contains trace logging metadata. | |
#> | |
param ( | |
[Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0)] | |
[Alias('FullName')] | |
[String] | |
$Path | |
) | |
# Resolve the full path in case a relative path was supplied. | |
$FullPath = Resolve-Path -Path $Path | |
# This string encoding will ensure a 1-to-1 byte<->char mapping. | |
$StringEncoder = [Text.Encoding]::GetEncoding(28591) | |
$FileBytes = [IO.File]::ReadAllBytes($FullPath) | |
$MemoryStream = New-Object -TypeName IO.MemoryStream -ArgumentList @(,$FileBytes) | |
$StreamReader = New-Object IO.StreamReader($MemoryStream, $StringEncoder) | |
$BinaryString = $StreamReader.ReadToEnd() | |
# Search for the trace logging signature value offset - _TlgSigVal | |
$TlgSigValIndex = $BinaryString.IndexOf('ETW0') | |
if ($TlgSigValIndex -ne -1) { | |
# "ETW0" was found. Ensure that it is a part of a _TraceLoggingMetadata_t structure. | |
$ProviderList = New-Object 'System.Collections.Generic.List[System.Management.Automation.PSObject]' | |
$EventList = New-Object 'System.Collections.Generic.List[System.Management.Automation.PSObject]' | |
# Jump to the offset of "ETW0" | |
$StreamReader.BaseStream.Position = $TlgSigValIndex | |
$BinaryReader = New-Object -TypeName IO.BinaryReader -ArgumentList $MemoryStream, $StringEncoder | |
$TlgSigVal = [Text.Encoding]::ASCII.GetString($BinaryReader.ReadBytes(4)) | |
$Size = $BinaryReader.ReadUInt16() # sizeof(_TraceLoggingMetadata_t) - Expected to equal 16 | |
$Version = $BinaryReader.ReadByte() | |
$Flags = $BinaryReader.ReadByte() | |
$Magic = $BinaryReader.ReadUInt64() # Expected to equal 0xBB8A052B88040E86 (13513619316402294406) - _TlgMagicVal | |
if (($Size -eq 16) -and ($Magic -eq ([UInt64] 13513619316402294406))) { | |
$null = [PSCustomObject] @{ | |
PSTypeName = 'TraceLogging.Metadata' | |
Signature = $TlgSigVal | |
Size = $Size | |
Version = $Version | |
Flags = $Flags # 1 - Indicates 64-bit pointer size. 0 - Indicates 32-bit pointer size | |
Magic = $Magic | |
} | |
# In practice, needing to know pointer size is not relevant. | |
<# | |
$PointerSize = 4 | |
if ($Flags -eq 1) { $PointerSize = 8 } | |
#> | |
# The next byte is a metadata blob type | |
$BlobType = $BinaryReader.ReadByte() | |
while ($BlobType -ne 1) { # _TlgBlobEnd: signals _TraceLoggingMetadataEnd | |
switch ($BlobType) { | |
0 { # _TlgBlobNone | |
# This is not documented anywhere but I see it pop up in rare instances. | |
# e.g. C:\Windows\System32\LocationFramework.dll has 8 of these after the metadata header prior to landing at the first event metadata blob. | |
} | |
2 { # _TlgBlobProvider | |
<# | |
This is documented nowhere but this provider type is present in C:\Windows\System32\ortcengine.dll *shrug* | |
I've only seen this so far for the "Microsoft.CRTProvider" provider (https://www.reddit.com/r/cpp/comments/4hoyzr/msvc_mutex_is_slower_than_you_might_expect/) | |
Another reference: https://habr.com/post/281374/ | |
The following structure can be inferred based on hex editor/IDA analysis: | |
UINT8 Type; // = _TlgBlobProvider | |
UINT16 RemainingSize; // = sizeof(RemainingSize + ProviderName) | |
char ProviderName[sizeof("providerName")]; // UTF-8 nul-terminated provider name | |
GUID ProviderGroupId; | |
#> | |
$RemainingSize = $BinaryReader.ReadUInt16() | |
$ProviderGroupGUID = $null | |
$ProviderName = $null | |
if ($RemainingSize) { | |
$StrBuilder = New-Object Text.StringBuilder | |
# Build up the provider name string until a null is reached. | |
do { | |
$CharVal = $BinaryReader.ReadChar() | |
$null = $StrBuilder.Append($CharVal) | |
} while ($CharVal -ne "`0") | |
$ProviderName = $StrBuilder.ToString().TrimEnd("`0") | |
# Report if this is anything other than 19. | |
# I would not expect there to be any other data other than a single provider group GUID | |
$RemainingChunkSize = $BinaryReader.ReadUInt16() | |
if ($RemainingChunkSize -ne 19) { | |
Write-Warning 'Unexpected provider metadata chunk size!' | |
} | |
if ($RemainingChunkSize) { | |
# Refers to ETW_PROVIDER_TRAIT_TYPE in evntcons.h | |
$ProviderAdditionalInfoTypeVal = $BinaryReader.ReadByte() | |
switch ($ProviderAdditionalInfoTypeVal) { | |
1 { # EtwProviderTraitTypeGroup | |
$ProviderGroupGUID = ([Guid][Byte[]] $BinaryReader.ReadBytes(16)).Guid | |
} | |
2 { # EtwProviderTraitDecodeGuid | |
# I don't expect to see this so alert if it is encountered | |
Write-Warning 'EtwProviderTraitDecodeGuid value encountered. Inspect this manually and develop a parser.' | |
} | |
default { | |
Write-Warning 'Unknown provider chunk type value encountered. Inspect this manually and develop a parser.' | |
} | |
} | |
} | |
} | |
$ProviderMetadata = [PSCustomObject] @{ | |
PSTypeName = 'TraceLogging.ProviderMetadata' | |
ProviderGUID = $null | |
ProviderName = $ProviderName | |
ProviderGroupGUID = $ProviderGroupGUID | |
} | |
$ProviderList.Add($ProviderMetadata) | |
} | |
3 { # _TlgBlobEvent3 | |
$EventID = $BinaryReader.BaseStream.Position - $TlgSigValIndex | |
$Channel = $BinaryReader.ReadByte() # This _should_ always be 11 (TraceLogging - Event contains provider traits and TraceLogging event metadata.) | |
$Level = $BinaryReader.ReadByte() | |
$Opcode = $BinaryReader.ReadByte() | |
$KeywordVal = $BinaryReader.ReadUInt64() | |
$KeywordFriendlyName = $KeywordMapping[$KeywordVal] | |
$Keyword = "0x$($KeywordVal.ToString('X16'))" | |
$RemainingSize = $BinaryReader.ReadUInt16() | |
$ExtensionList = $null | |
$EventName = $null | |
$Fields = $null | |
# Non-null remaining size implies that event/field metadata exists. | |
if ($RemainingSize) { | |
# Calculate the end stream position and validate along the way that it hasn't been exceeded. | |
# This is required due to how structure are stored in tightly-packed streams that don't | |
# contain fields about metadata length. | |
$EndPosition = $BinaryReader.BaseStream.Position + $RemainingSize - 2 # 2 - sizeof(RemainingSize) | |
<# What follows is an EventMetadata structure: | |
struct EventMetadata // Variable-length pseudo-structure, byte-aligned, tightly-packed. | |
{ | |
UINT16 Size; // = sizeof(EventMetadata) | |
UINT8 Extension[]; // 1 or more bytes. Read until you hit a byte with high bit unset. | |
char Name[]; // UTF-8 nul-terminated event name | |
FieldMetadata Fields[]; // 0 or more field definitions. | |
}; | |
#> | |
if ($BinaryReader.BaseStream.Position -ne $EndPosition) { | |
# Read extension array | |
$ExtensionList = New-Object 'System.Collections.Generic.List[Byte]' | |
do { | |
$ExtensionVal = $BinaryReader.ReadByte() | |
# To-do: inspect to see if this is ever non-zero. It is currently unclear what these are used for. | |
$ExtensionList.Add($ExtensionVal) | |
} while ($ExtensionVal -band 0x80) | |
if ($BinaryReader.BaseStream.Position -ne $EndPosition) { | |
$StrBuilder = New-Object Text.StringBuilder | |
# Build up the event name string until a null is reached. | |
do { | |
$CharVal = $BinaryReader.ReadChar() | |
$null = $StrBuilder.Append($CharVal) | |
} while ($CharVal -ne "`0") | |
$EventName = $StrBuilder.ToString().TrimEnd("`0") | |
$Fields = New-Object 'System.Collections.Generic.List[System.Management.Automation.PSObject]' | |
while ($BinaryReader.BaseStream.Position -lt $EndPosition) { | |
# Parse out all fields in the event. Fields are optional. | |
<# | |
struct FieldMetadata // Variable-length pseudo-structure, byte-aligned, tightly-packed. | |
{ | |
char Name[]; // UTF-8 nul-terminated field name | |
UINT8 InType; // Values from the TlgIn enumeration. | |
UINT8 OutType; // TlgOut enumeration. Only present if (InType & 128) == 128. | |
UINT8 Extension[]; // Only present if OutType is present and (OutType & 128) == 128. Read until you hit a byte with high bit unset. | |
UINT16 ValueCount; // Only present if (InType & CountMask) == Ccount. | |
UINT16 TypeInfoSize; // Only present if (InType & CountMask) == Custom. | |
char TypeInfo[TypeInfoSize]; // Only present if (InType & CountMask) == Custom. | |
}; | |
#> | |
# Build up the field name string until a null is reached. | |
$StrBuilder = New-Object Text.StringBuilder | |
do { | |
$CharVal = $BinaryReader.ReadChar() | |
$null = $StrBuilder.Append($CharVal) | |
} while ($CharVal -ne "`0") | |
$FieldName = $StrBuilder.ToString().TrimEnd("`0") | |
$InTypeVal = $BinaryReader.ReadByte() | |
$InTypeMask = 31 | |
<# | |
if ($InTypeVal -band 64) { | |
Write-Warning "VCount Tag Flag Encountered! Current position: 0x$($BinaryReader.BaseStream.Position.ToString('X8'))" | |
} | |
#> | |
$OutType = $null | |
$OutTypeVal = $null | |
$FieldExtensionList = $null | |
if ($InTypeVal -band 128) { | |
# This means that the OutType field is populated. | |
$OutTypeVal = $BinaryReader.ReadByte() | |
$OutTypeMask = 127 | |
if ($OutTypeVal -band 128) { | |
$FieldExtensionList = New-Object 'System.Collections.Generic.List[Byte]' | |
do { | |
$ExtensionVal = $BinaryReader.ReadByte() | |
# To-do: inspect to see if this is ever non-zero. It is currently unclear what these are used for. | |
$FieldExtensionList.Add($ExtensionVal) | |
} while ($ExtensionVal -band 0x80) | |
} | |
$MaskedOutTypeVal = $OutTypeVal -band $OutTypeMask | |
if ([Enum]::IsDefined([TlgOut], $MaskedOutTypeVal)) { | |
$OutType = ([TlgOut] ($MaskedOutTypeVal)).ToString() | |
} else { | |
Write-Verbose "Unsupported field out type: 0x$($MaskedOutTypeVal.ToString('X2')); File path: $FullPath" | |
$OutType = $MaskedOutTypeVal.ToString() | |
} | |
} | |
[UInt16] $ValueCount = 0 | |
if ($InTypeVal -band 32) { | |
$ValueCount = $BinaryReader.ReadUInt16() | |
} | |
[UInt16] $TypeInfoSize = 0 | |
$TypeInfo = $null | |
if (($InTypeVal -band (32 -bor 64)) -eq (32 -bor 64)) { | |
$TypeInfoSize = $BinaryReader.ReadUInt16() | |
[String] $TypeInfo = $BinaryReader.ReadChars($TypeInfoSize) -join '' | |
} | |
$InType = ([TlgIn] ($InTypeVal -band $InTypeMask)).ToString() | |
$FieldMetadata = [PSCustomObject] @{ | |
PSTypeName = 'TraceLogging.FieldMetadata' | |
FieldName = $FieldName | |
InType = $InType | |
OutType = $OutType | |
Extension = $FieldExtensionList | |
ValueCount = $ValueCount | |
TypeInfo = $TypeInfo | |
} | |
$Fields.Add($FieldMetadata) | |
} | |
} | |
} | |
} | |
$EventMetadata = [PSCustomObject] @{ | |
PSTypeName = 'TraceLogging.EventMetadata' | |
EventId = $EventID | |
Channel = $Channel | |
Level = $Level | |
Opcode = $Opcode | |
Keyword = $Keyword | |
KeywordName = $KeywordFriendlyName | |
Extension = $ExtensionList | |
EventName = $EventName | |
FieldInfo = $Fields | |
} | |
$EventList.Add($EventMetadata) | |
} | |
4 { # _TlgBlobProvider3 | |
$ProviderGUID = ([Guid][Byte[]] $BinaryReader.ReadBytes(16)).Guid | |
$RemainingSize = $BinaryReader.ReadUInt16() | |
$ProviderName = $null | |
$ProviderGroupGUID = $null | |
<# | |
struct ProviderMetadata // Variable-length pseudo-structure, byte-aligned, tightly-packed. | |
{ | |
UINT16 Size; // = sizeof(ProviderMetadata) | |
char Name[]; // UTF-8 nul-terminated provider name | |
ProviderMetadataChunk AdditionalProviderInfo[]; // 0 or more chunks of data. | |
}; | |
// ProviderMetadataChunk: | |
struct ProviderMetadataChunk // Variable-length pseudo-structure, byte-aligned, tightly-packed. | |
{ | |
UINT16 Size; // = sizeof(ProviderMetadataChunk) | |
UINT8 Type; // Value from the ETW_PROVIDER_TRAIT_TYPE enumeration. | |
AnyType Data; | |
}; | |
#> | |
if ($RemainingSize) { | |
$EndPosition = $BinaryReader.BaseStream.Position + $RemainingSize - 2 # 2 - sizeof(RemainingSize) | |
# Implies that a provider name follows | |
$StrBuilder = New-Object Text.StringBuilder | |
# Build up the provider name string until a null is reached. | |
do { | |
$CharVal = $BinaryReader.ReadChar() | |
$null = $StrBuilder.Append($CharVal) | |
} while ($CharVal -ne "`0") | |
$ProviderName = $StrBuilder.ToString().TrimEnd("`0") | |
# It is not guaranteed that chunk data will follow. | |
if ($BinaryReader.BaseStream.Position -ne $EndPosition) { | |
# Report if this is anything other than 19. | |
# I would not expect there to be any other data other than a single provider group GUID | |
$RemainingChunkSize = $BinaryReader.ReadUInt16() | |
if ($RemainingChunkSize -ne 19) { | |
Write-Warning 'Unexpected provider metadata chunk size!' | |
} | |
if ($RemainingChunkSize) { | |
# Refers to ETW_PROVIDER_TRAIT_TYPE in evntcons.h | |
$ProviderAdditionalInfoTypeVal = $BinaryReader.ReadByte() | |
switch ($ProviderAdditionalInfoTypeVal) { | |
1 { # EtwProviderTraitTypeGroup | |
$ProviderGroupGUID = ([Guid][Byte[]] $BinaryReader.ReadBytes(16)).Guid | |
} | |
2 { # EtwProviderTraitDecodeGuid | |
# I don't expect to see this so alert if it is encountered | |
Write-Warning 'EtwProviderTraitDecodeGuid value encountered. Inspect this manually and develop a parser.' | |
} | |
default { | |
Write-Warning 'Unknown provider chunk type value encountered. Inspect this manually and develop a parser.' | |
} | |
} | |
} | |
} | |
} | |
$ProviderMetadata = [PSCustomObject] @{ | |
PSTypeName = 'TraceLogging.ProviderMetadata' | |
ProviderGUID = $ProviderGUID | |
ProviderName = $ProviderName | |
ProviderGroupGUID = $ProviderGroupGUID | |
} | |
$ProviderList.Add($ProviderMetadata) | |
} | |
5 { # _TlgBlobEvent2 | |
<# | |
This structure type is not documented but I will do my best to infer/reverse data types. | |
Event blob structure based on reversing | |
1) byte - Level | |
2) byte - Opcode | |
3) ushort - Task | |
4) ulonglong - keyword | |
Assumed values/statis values supplied: | |
* Channel: 0xB | |
* Id: metadata offset calc | |
* Version: 0 | |
Same event and field info as _TlgBlobEvent3 | |
#> | |
$EventID = $BinaryReader.BaseStream.Position - $TlgSigValIndex | |
$Level = $BinaryReader.ReadByte() | |
$Opcode = $BinaryReader.ReadByte() | |
$null = $BinaryReader.ReadUInt16() # Task value. This is not relevant to trace logging | |
$KeywordVal = $BinaryReader.ReadUInt64() | |
$KeywordFriendlyName = $KeywordMapping[$KeywordVal] | |
$Keyword = "0x$($KeywordVal.ToString('X16'))" | |
[Byte] $Channel = 0xB | |
$RemainingSize = $BinaryReader.ReadUInt16() | |
$ExtensionList = $null | |
$EventName = $null | |
$Fields = $null | |
# Non-null remaining size implies that event/field metadata exists. | |
if ($RemainingSize) { | |
# Calculate the end stream position and validate along the way that it hasn't been exceeded. | |
# This is required due to how structure are stored in tightly-packed streams that don't | |
# contain fields about metadata length. | |
$EndPosition = $BinaryReader.BaseStream.Position + $RemainingSize - 2 # 2 - sizeof(RemainingSize) | |
<# What follows is an EventMetadata structure: | |
struct EventMetadata // Variable-length pseudo-structure, byte-aligned, tightly-packed. | |
{ | |
UINT16 Size; // = sizeof(EventMetadata) | |
UINT8 Extension[]; // 1 or more bytes. Read until you hit a byte with high bit unset. | |
char Name[]; // UTF-8 nul-terminated event name | |
FieldMetadata Fields[]; // 0 or more field definitions. | |
}; | |
#> | |
if ($BinaryReader.BaseStream.Position -ne $EndPosition) { | |
# Read extension array | |
$ExtensionList = New-Object 'System.Collections.Generic.List[Byte]' | |
do { | |
$ExtensionVal = $BinaryReader.ReadByte() | |
# To-do: inspect to see if this is ever non-zero. It is currently unclear what these are used for. | |
$ExtensionList.Add($ExtensionVal) | |
} while ($ExtensionVal -band 0x80) | |
if ($BinaryReader.BaseStream.Position -ne $EndPosition) { | |
$StrBuilder = New-Object Text.StringBuilder | |
# Build up the event name string until a null is reached. | |
do { | |
$CharVal = $BinaryReader.ReadChar() | |
$null = $StrBuilder.Append($CharVal) | |
} while ($CharVal -ne "`0") | |
$EventName = $StrBuilder.ToString().TrimEnd("`0") | |
$Fields = New-Object 'System.Collections.Generic.List[System.Management.Automation.PSObject]' | |
while ($BinaryReader.BaseStream.Position -lt $EndPosition) { | |
# Parse out all fields in the event. Fields are optional. | |
<# | |
struct FieldMetadata // Variable-length pseudo-structure, byte-aligned, tightly-packed. | |
{ | |
char Name[]; // UTF-8 nul-terminated field name | |
UINT8 InType; // Values from the TlgIn enumeration. | |
UINT8 OutType; // TlgOut enumeration. Only present if (InType & 128) == 128. | |
UINT8 Extension[]; // Only present if OutType is present and (OutType & 128) == 128. Read until you hit a byte with high bit unset. | |
UINT16 ValueCount; // Only present if (InType & CountMask) == Ccount. | |
UINT16 TypeInfoSize; // Only present if (InType & CountMask) == Custom. | |
char TypeInfo[TypeInfoSize]; // Only present if (InType & CountMask) == Custom. | |
}; | |
#> | |
# Build up the field name string until a null is reached. | |
$StrBuilder = New-Object Text.StringBuilder | |
do { | |
$CharVal = $BinaryReader.ReadChar() | |
$null = $StrBuilder.Append($CharVal) | |
} while ($CharVal -ne "`0") | |
$FieldName = $StrBuilder.ToString().TrimEnd("`0") | |
$InTypeVal = $BinaryReader.ReadByte() | |
$InTypeMask = 31 | |
<# | |
if ($InTypeVal -band 64) { | |
Write-Warning "VCount Tag Flag Encountered! Current position: 0x$($BinaryReader.BaseStream.Position.ToString('X8'))" | |
} | |
#> | |
$OutType = $null | |
$OutTypeVal = $null | |
$FieldExtensionList = $null | |
if ($InTypeVal -band 128) { | |
# This means that the OutType field is populated. | |
$OutTypeVal = $BinaryReader.ReadByte() | |
$OutTypeMask = 127 | |
if ($OutTypeVal -band 128) { | |
$FieldExtensionList = New-Object 'System.Collections.Generic.List[Byte]' | |
do { | |
$ExtensionVal = $BinaryReader.ReadByte() | |
# To-do: inspect to see if this is ever non-zero. It is currently unclear what these are used for. | |
$FieldExtensionList.Add($ExtensionVal) | |
} while ($ExtensionVal -band 0x80) | |
} | |
$MaskedOutTypeVal = $OutTypeVal -band $OutTypeMask | |
if ([Enum]::IsDefined([TlgOut], $MaskedOutTypeVal)) { | |
$OutType = ([TlgOut] ($MaskedOutTypeVal)).ToString() | |
} else { | |
Write-Verbose "Unsupported field out type: 0x$($MaskedOutTypeVal.ToString('X2')); File path: $FullPath" | |
$OutType = $MaskedOutTypeVal.ToString() | |
} | |
} | |
[UInt16] $ValueCount = 0 | |
if ($InTypeVal -band 32) { | |
$ValueCount = $BinaryReader.ReadUInt16() | |
} | |
[UInt16] $TypeInfoSize = 0 | |
$TypeInfo = $null | |
if (($InTypeVal -band (32 -bor 64)) -eq (32 -bor 64)) { | |
$TypeInfoSize = $BinaryReader.ReadUInt16() | |
[String] $TypeInfo = $BinaryReader.ReadChars($TypeInfoSize) -join '' | |
} | |
$InType = ([TlgIn] ($InTypeVal -band $InTypeMask)).ToString() | |
$FieldMetadata = [PSCustomObject] @{ | |
PSTypeName = 'TraceLogging.FieldMetadata' | |
FieldName = $FieldName | |
InType = $InType | |
OutType = $OutType | |
Extension = $FieldExtensionList | |
ValueCount = $ValueCount | |
TypeInfo = $TypeInfo | |
} | |
$Fields.Add($FieldMetadata) | |
} | |
} | |
} | |
} | |
$EventMetadata = [PSCustomObject] @{ | |
PSTypeName = 'TraceLogging.EventMetadata' | |
EventId = $EventID | |
Channel = $Channel | |
Level = $Level | |
Opcode = $Opcode | |
Keyword = $Keyword | |
KeywordName = $KeywordFriendlyName | |
Extension = $ExtensionList | |
EventName = $EventName | |
FieldInfo = $Fields | |
} | |
$EventList.Add($EventMetadata) | |
} | |
6 { # _TlgBlobEvent4 Same as _TlgBlobEvent4 but EventID is always 0. Thanks Alex Ionescu for the explanation! | |
$Channel = $BinaryReader.ReadByte() # This _should_ always be 11 (TraceLogging - Event contains provider traits and TraceLogging event metadata.) | |
$Level = $BinaryReader.ReadByte() | |
$Opcode = $BinaryReader.ReadByte() | |
$KeywordVal = $BinaryReader.ReadUInt64() | |
$KeywordFriendlyName = $KeywordMapping[$KeywordVal] | |
$Keyword = "0x$($KeywordVal.ToString('X16'))" | |
$RemainingSize = $BinaryReader.ReadUInt16() | |
$ExtensionList = $null | |
$EventName = $null | |
$Fields = $null | |
# Non-null remaining size implies that event/field metadata exists. | |
if ($RemainingSize) { | |
# Calculate the end stream position and validate along the way that it hasn't been exceeded. | |
# This is required due to how structure are stored in tightly-packed streams that don't | |
# contain fields about metadata length. | |
$EndPosition = $BinaryReader.BaseStream.Position + $RemainingSize - 2 # 2 - sizeof(RemainingSize) | |
<# What follows is an EventMetadata structure: | |
struct EventMetadata // Variable-length pseudo-structure, byte-aligned, tightly-packed. | |
{ | |
UINT16 Size; // = sizeof(EventMetadata) | |
UINT8 Extension[]; // 1 or more bytes. Read until you hit a byte with high bit unset. | |
char Name[]; // UTF-8 nul-terminated event name | |
FieldMetadata Fields[]; // 0 or more field definitions. | |
}; | |
#> | |
if ($BinaryReader.BaseStream.Position -ne $EndPosition) { | |
# Read extension array | |
$ExtensionList = New-Object 'System.Collections.Generic.List[Byte]' | |
do { | |
$ExtensionVal = $BinaryReader.ReadByte() | |
# To-do: inspect to see if this is ever non-zero. It is currently unclear what these are used for. | |
$ExtensionList.Add($ExtensionVal) | |
} while ($ExtensionVal -band 0x80) | |
if ($BinaryReader.BaseStream.Position -ne $EndPosition) { | |
$StrBuilder = New-Object Text.StringBuilder | |
# Build up the event name string until a null is reached. | |
do { | |
$CharVal = $BinaryReader.ReadChar() | |
$null = $StrBuilder.Append($CharVal) | |
} while ($CharVal -ne "`0") | |
$EventName = $StrBuilder.ToString().TrimEnd("`0") | |
$Fields = New-Object 'System.Collections.Generic.List[System.Management.Automation.PSObject]' | |
while ($BinaryReader.BaseStream.Position -lt $EndPosition) { | |
# Parse out all fields in the event. Fields are optional. | |
<# | |
struct FieldMetadata // Variable-length pseudo-structure, byte-aligned, tightly-packed. | |
{ | |
char Name[]; // UTF-8 nul-terminated field name | |
UINT8 InType; // Values from the TlgIn enumeration. | |
UINT8 OutType; // TlgOut enumeration. Only present if (InType & 128) == 128. | |
UINT8 Extension[]; // Only present if OutType is present and (OutType & 128) == 128. Read until you hit a byte with high bit unset. | |
UINT16 ValueCount; // Only present if (InType & CountMask) == Ccount. | |
UINT16 TypeInfoSize; // Only present if (InType & CountMask) == Custom. | |
char TypeInfo[TypeInfoSize]; // Only present if (InType & CountMask) == Custom. | |
}; | |
#> | |
# Build up the field name string until a null is reached. | |
$StrBuilder = New-Object Text.StringBuilder | |
do { | |
$CharVal = $BinaryReader.ReadChar() | |
$null = $StrBuilder.Append($CharVal) | |
} while ($CharVal -ne "`0") | |
$FieldName = $StrBuilder.ToString().TrimEnd("`0") | |
$InTypeVal = $BinaryReader.ReadByte() | |
$InTypeMask = 31 | |
<# | |
if ($InTypeVal -band 64) { | |
Write-Warning "VCount Tag Flag Encountered! Current position: 0x$($BinaryReader.BaseStream.Position.ToString('X8'))" | |
} | |
#> | |
$OutType = $null | |
$OutTypeVal = $null | |
$FieldExtensionList = $null | |
if ($InTypeVal -band 128) { | |
# This means that the OutType field is populated. | |
$OutTypeVal = $BinaryReader.ReadByte() | |
$OutTypeMask = 127 | |
if ($OutTypeVal -band 128) { | |
$FieldExtensionList = New-Object 'System.Collections.Generic.List[Byte]' | |
do { | |
$ExtensionVal = $BinaryReader.ReadByte() | |
# To-do: inspect to see if this is ever non-zero. It is currently unclear what these are used for. | |
$FieldExtensionList.Add($ExtensionVal) | |
} while ($ExtensionVal -band 0x80) | |
} | |
$MaskedOutTypeVal = $OutTypeVal -band $OutTypeMask | |
if ([Enum]::IsDefined([TlgOut], $MaskedOutTypeVal)) { | |
$OutType = ([TlgOut] ($MaskedOutTypeVal)).ToString() | |
} else { | |
Write-Verbose "Unsupported field out type: 0x$($MaskedOutTypeVal.ToString('X2')); File path: $FullPath" | |
$OutType = $MaskedOutTypeVal.ToString() | |
} | |
} | |
[UInt16] $ValueCount = 0 | |
if ($InTypeVal -band 32) { | |
$ValueCount = $BinaryReader.ReadUInt16() | |
} | |
[UInt16] $TypeInfoSize = 0 | |
$TypeInfo = $null | |
if (($InTypeVal -band (32 -bor 64)) -eq (32 -bor 64)) { | |
$TypeInfoSize = $BinaryReader.ReadUInt16() | |
[String] $TypeInfo = $BinaryReader.ReadChars($TypeInfoSize) -join '' | |
} | |
$InType = ([TlgIn] ($InTypeVal -band $InTypeMask)).ToString() | |
$FieldMetadata = [PSCustomObject] @{ | |
PSTypeName = 'TraceLogging.FieldMetadata' | |
FieldName = $FieldName | |
InType = $InType | |
OutType = $OutType | |
Extension = $FieldExtensionList | |
ValueCount = $ValueCount | |
TypeInfo = $TypeInfo | |
} | |
$Fields.Add($FieldMetadata) | |
} | |
} | |
} | |
} | |
$EventMetadata = [PSCustomObject] @{ | |
PSTypeName = 'TraceLogging.EventMetadata' | |
EventId = 0 | |
Channel = $Channel | |
Level = $Level | |
Opcode = $Opcode | |
Keyword = $Keyword | |
KeywordName = $KeywordFriendlyName | |
Extension = $ExtensionList | |
EventName = $EventName | |
FieldInfo = $Fields | |
} | |
$EventList.Add($EventMetadata) | |
} | |
default { | |
# To-do: logic goes here to account for unparsed structures. | |
# e.g. I will need to get to parsing provider metadata next. | |
Write-Error "Unparsed blob type enountered! Blob type val: 0x$($BlobType.ToString('X2')); File path: $FullPath; Current position: 0x$($BinaryReader.BaseStream.Position.ToString('X8'))" | |
} | |
} | |
$BlobType = $BinaryReader.ReadByte() | |
} | |
} | |
[PSCustomObject] @{ | |
PSTypeName = 'TraceLogging.Schema' | |
FilePath = $FullPath.Path | |
Providers = $ProviderList | |
Events = $EventList | |
} | |
$BinaryReader.Close() | |
} | |
$StreamReader.Close() | |
} | |
Export-ModuleMember -Function 'Get-TraceLoggingMetadata' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment