My C# application subscribes to Windows Event Log messages:
var subscriptionQuery = new EventLogQuery(Settings.Default.LogPath, PathType.LogName, Settings.Default.LogQuery);
_watcher = new EventLogWatcher(subscriptionQuery);
_watcher.EventRecordWritten += EventLogEventRead;
When message occurs EventLogEventRead
handler reseives a System.Diagnostics.Eventing.Reader.EventLogRecord
object, containing event data. This information includes a collection of EventProperty
objects.
The problem is: EventProperty
only defines a value, not the name of the property. However, when I open the same event in Windows Event Log Viewer, it shows properties with names.
Now the question is: How do I get event propery names?
I finally figured it out! 🎉
You are intended to use EventLogPropertySelector
to specify XPath expressions to select the properties you want. Then, instead of calling EventRecord.Properties
, call EventLogRecord.GetPropertyValues(EventLogPropertySelector)
. The returned values are correlated by index with the XPath expressions that you specified when you created the EventLogPropertySelector
.
For a C++ example which shows you what the XPath expressions should look like, see https://docs.microsoft.com/windows/desktop/WES/rendering-events:
... LPWSTR ppValues[] = {L"Event/System/Provider/@Name", L"Event/System/Channel"}; DWORD count = sizeof(ppValues)/sizeof(LPWSTR); // Identify the components of the event that you want to render. In this case, // render the provider's name and channel from the system section of the event. // To get user data from the event, you can specify an expression such as // L"Event/EventData/Data[@Name=\"<data name goes here>\"]". hContext = EvtCreateRenderContext(count, (LPCWSTR*)ppValues, EvtRenderContextValues); ...
Here’s an example in C# which demonstrates pulling only the needed values for each event type:
var query = new EventLogQuery(
"Security",
PathType.LogName,
"*[System[EventID=4624 or EventID=4634]]");
using (var loginEventPropertySelector = new EventLogPropertySelector(new[]
{
// (The XPath expression evaluates to null if no Data element exists with the specified name.)
"Event/EventData/Data[@Name='TargetUserSid']",
"Event/EventData/Data[@Name='TargetLogonId']",
"Event/EventData/Data[@Name='LogonType']",
"Event/EventData/Data[@Name='ElevatedToken']",
"Event/EventData/Data[@Name='WorkstationName']",
"Event/EventData/Data[@Name='ProcessName']",
"Event/EventData/Data[@Name='IpAddress']",
"Event/EventData/Data[@Name='IpPort']"
}))
using (var logoffEventPropertySelector = new EventLogPropertySelector(new[]
{
"Event/EventData/Data[@Name='TargetUserSid']",
"Event/EventData/Data[@Name='TargetLogonId']"
}))
using (var reader = new EventLogReader(query))
{
// In C# 8: while (reader.ReadEvent() is { } ev)
while (reader.ReadEvent() is var ev && ev != null)
{
using (ev)
{
switch (ev.Id)
{
case 4624:
var loginPropertyValues = ((EventLogRecord)ev).GetPropertyValues(loginEventPropertySelector);
var targetUserSid = (SecurityIdentifier)loginPropertyValues[0];
// ...
break;
case 4634:
var logoffPropertyValues = ((EventLogRecord)ev).GetPropertyValues(logoffEventPropertySelector);
var targetUserSid = (SecurityIdentifier)logoffPropertyValues[0];
// ...
break;
}
}
}
}
The thing that made this nearly undiscoverable (besides the lack of .NET documentation) was that EventLogReader.ReadEvent
returns EventRecord
which you then have to cast to EventLogRecord
. This is what clued me in when looking at the .NET Framework source and seeing that EventRecord.Properties
ultimately p/invokes EvtRender
.
https://docs.microsoft.com/windows/desktop/api/winevt/nf-winevt-evtrender#remarks:
There is a one-to-one relationship between the array of XPath expressions that you specified when you called the EvtCreateRenderContext function and the array the values returned in the buffer.
There are two ways you could go about this (that I know of), but both involve parsing xml.
EventRecord
has the function ToXml
which includes an UserData
section, containing all the values from that block.
The other option is a bit more complicated:
Create an ProviderMetadata
, find the EventMetadata
that describes the current EventRecord
, and parse the Template
member.
This Template
member explains how to interpret the values (even including types), but it is in XML format.
var meta = new ProviderMetadata(record.ProviderName).Events.Where(evt => evt.Id == eventRecord.Id).FirstOrDefault();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With