Tracking Down User Logons with PowerShell and XPath

Published:11 June 2024 - 5 min. read

When working with Windows event logs, especially the Security log, there might be instances where you need to extract specific information from events.

But you’ll find out, it’s not as easy as you’d anticipate.

Recently, I needed to query Windows events from the Security event log for user logon events (Event ID 4624 to be specific). But not all events, just events matching a specific username syntax of domain_name/username.

Join me on a journey of Windows event logs, XML and XPath as we parse Windows event logs with PowerShell. To demonstrate, let’s walk through an example.

Query the Event Log with Get-WinEvent

You first need to pull at least one Windows event. Since I’m working with user logon events, I’ll pull just one event as an example with event ID 4624.

$eventRecord = Get-WinEvent -MaxEvents 1 -FilterHashtable @{LogName='Security';ID=4624}

Done! You now have an System.Diagnostics.Eventing.Reader.EventLogRecord object.

Convert the Event Record to XML

Now, this tutorial would be pretty short if I just needed to filter on basic properties like Id or TimeCreated but unfortunately, I need to filter information from an event’s Message field. Get-WinEvent doesn’t create a friendly object for us to query. Instead, you must build your own.

To do so, you must first convert the record to XML using the ToXml() method.

The ToXml() method converts the entire event object to an XML string.

$xmlEventRecord = $eventRecord.ToXml()

Unless you’re a sadomasochist and prefer to use regex, you need to get this into a structured format to query elements inside of it. Lucky for us, you can easily cast XML strings to Xml.Document types using the [xml] type accelerator.

$xmlEventRecord = [xml]$eventRecord.ToXml()

Parse the XML to Extract Domain and Username

The XML format of the event contains various elements, and we need to navigate through these to find TargetUserName and TargetDomainName. We will use XPath queries to achieve this.

💡 Windows event logs don’t have full XPath support. They use a subset of XML 1.0 that is severely limited in the functions you can use.

On every XmlDocument object, you have a SelectSingleNode() method that allows you to pass an XPath query to to find elements in the document but you need to come up with the XPath query first.

When building XPath queries, you must first figure out the “path” to the entity you’re querying or at least the node that holds the value you’re after. In this case, the elements we’re after are in EventEventData stored as a Data node with Name attributes with values of TargetUserName and TargetDomainName that we’re after.

<Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'>
    <System>
        <Provider Name='Microsoft-Windows-Security-Auditing' Guid='{54849625-5478-4994-a5ba-3e3b0328c30d}'/>
        <EventID>4624</EventID>
        <Version>2</Version>
        <Level>0</Level>
        <Task>12544</Task>
        <Opcode>0</Opcode>
        <Keywords>0x8020000000000000</Keywords>
        <TimeCreated SystemTime='2024-06-07T17:23:16.5812423Z'/>
        <EventRecordID>260494</EventRecordID>
        <Correlation/>
        <Execution ProcessID='768' ThreadID='1708'/>
        <Channel>Security</Channel>
        <Computer>ATA-Devo-SRV-01.atadevo.local</Computer>
        <Security/>
    </System>
    <EventData>
        <Data Name='SubjectUserSid'>S-1-5-18</Data>
        <Data Name='SubjectUserName'>ATA-DEVO-SRV-01$</Data>
        <Data Name='SubjectDomainName'>ATADEVO</Data>
        <Data Name='SubjectLogonId'>0x3e7</Data>
        <Data Name='TargetUserSid'>S-1-5-21-3909178112-971454023-1868345993-500</Data>
        <Data Name='TargetUserName'>atadevoadmin</Data>
        <Data Name='TargetDomainName'>ATADEVO</Data>
        <Data Name='TargetLogonId'>0x546f80</Data>
        <Data Name='LogonType'>7</Data>
        <Data Name='LogonProcessName'>Negotiat</Data>
        <Data Name='AuthenticationPackageName'>Negotiate</Data>
        <Data Name='WorkstationName'>ATA-DEVO-SRV-01</Data>
        <Data Name='LogonGuid'> { 97292dbd-b376-1168-64b5-b750cafa6348 }</Data>
        <Data Name='TransmittedServices'>-</Data>
        <Data Name='LmPackageName'>-</Data>
        <Data Name='KeyLength'>0</Data>
        <Data Name='ProcessId'>0x300</Data>
        <Data Name='ProcessName'>C:WindowsSystem32lsass.exe</Data>
        <Data Name='IpAddress'>-</Data>
        <Data Name='IpPort'>-</Data>
        <Data Name='ImpersonationLevel'>%%1833</Data>
        <Data Name='RestrictedAdminMode'>-</Data>
        <Data Name='TargetOutboundUserName'>-</Data>
        <Data Name='TargetOutboundDomainName'>-</Data>
        <Data Name='VirtualAccount'>%%1843</Data>
        <Data Name='TargetLinkedLogonId'>0x0</Data>
        <Data Name='ElevatedToken'>%%1842</Data>
    </EventData>
</Event>

If you don’t work with XPath too often, you’re first inclination would probably be to do something like this:

$xmlEventRecord.SelectSingleNode("//Data[@Name='TargetUserName']")

The XPath query is sound. It’s looking for all Data nodes with an attribute of Name and a value of TargetUserName. But, it won’t work. Why? This XML use a namespace.

xmlns='http://schemas.microsoft.com/win/2004/08/events/event

XML namespaces are used for providing uniquely named elements and attributes in an XML document. An XML instance may contain element or attribute names from more than one XML vocabulary. If each vocabulary is given a namespace, the ambiguity between identically named elements or attributes can be resolved.

To query an XML document that uses a namespace, you must first define an XmlNamespaceManager and then add that namespace to it.

$eventNamespace = New-Object System.Xml.XmlNamespaceManager($xmlEventRecord.NameTable)
$eventNamespace.AddNamespace("evt", "http://schemas.microsoft.com/win/2004/08/events/event")

Once you have the namespace defined, you must then use that namespace in the SelectSingleNode() query.

$xmlEventRecord.SelectSingleNode("//evt:Data[@Name='TargetUserName']", $eventNamespace)

At this point, you should be able to extract the XML node.

Finding the Text Value

The SelectSingleNode() method will not return a simple string that you’ll need. Instead, it returns an System.Xml.XmlElement object which you’ll need to extract the node value from. To do that, you can use your familiar PowerShell dot notation to reference the InnerText property.

$xmlEventRecord.SelectSingleNode("//evt:Data[@Name='TargetUserName']", $eventNamespace).InnerText

Once you know how to find the text value, you’re back into familiar PowerShell territory. At this point, you just need to extract both of the strings (TargetUserName and TargetDomainName) in this case and concatenate them together.

$targetUserName = $xmlEventRecord.SelectSingleNode("//evt:Data[@Name='TargetUserName']", $eventNamespace).InnerText
$targetDomainName = $xmlEventRecord.SelectSingleNode("//evt:Data[@Name='TargetDomainName']", $eventNamespace).InnerText
$domainUser = "$targetDomainName$targetUserName"

Finally, combine the extracted TargetUserName and TargetDomainName to form the DomainNameUsername format.

And you’re done!

Conclusion and Script

If you’re building any PowerShell scripts querying Windows event logs, chances are you’re going to have to especially if you want to query the Message field.

Here’s the complete script if you want to copy and paste.

# Step 1: Query the Security log for Event ID 4624
$eventRecord = Get-WinEvent -MaxEvents 1 -FilterHashtable @{LogName='Security';ID=4624}

# Step 2: Convert the event record to XML
$xmlEventRecord = [xml]$eventRecord.ToXml()

# Step 3: Define the namespace manager
$eventNamespace = New-Object System.Xml.XmlNamespaceManager($xmlEventRecord.NameTable)
$eventNamespace.AddNamespace("evt", "<http://schemas.microsoft.com/win/2004/08/events/event>")

# Step 4: Extract TargetUserName and TargetDomainName using XPath
$targetUserName = $xmlEventRecord.SelectSingleNode("//evt:Data[@Name='TargetUserName']", $eventNamespace).InnerText
$targetDomainName = $xmlEventRecord.SelectSingleNode("//evt:Data[@Name='TargetDomainName']", $eventNamespace).InnerText

# Step 5: Combine DomainName and UserName
$domainUser = "$targetDomainName\$targetUserName"
$domainUser

Hate ads? Want to support the writer? Get many of our tutorials packaged as an ATA Guidebook.

Explore ATA Guidebooks

Looks like you're offline!