Reputation: 143
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?
Upvotes: 6
Views: 4617
Reputation: 8354
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://learn.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://learn.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.
Upvotes: 9
Reputation: 1509
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();
Upvotes: 2