I have recently spent some time working with Xpath queries as part of Event Log filtering in Windows Server 2008. It’s a great feature, but one limitation I found was that it doesn’t appear possible to use the starts-with() function when querying Event Logs with either the UI or Wevtutil.exe. Here’s an example.
Let’s say I enabled LDAP logging on a DC and want to filter the Directory Service event log to find all LDAP queries coming from a particular IP address. The IP address is buried in one of the Data nodes of the Event XML, as shown in red below.
<Event xmlns=”http://schemas.microsoft.com/win/2004/08/events/event”>
<System>
<Provider Name=”Microsoft-Windows-ActiveDirectory_DomainService” Guid=”{0e8478c5-3605-4e8c-8497-1e730c959516}” EventSourceName=”NTDS General” />
<EventID Qualifiers=”16384″>1644</EventID>
<Version>0</Version>
<Level>4</Level>
<Task>15</Task>
<Opcode>0</Opcode>
<Keywords>0x8080000000000000</Keywords>
<TimeCreated SystemTime=”2009-04-29T20:39:00.886Z” />
<EventRecordID>339453</EventRecordID>
<Correlation />
<Execution ProcessID=”648″ ThreadID=”792″ />
<Channel>Directory Service</Channel>
<Computer>DC1.Myco.Com</Computer>
<Security UserID=”S-1-5-21-854245398-152049171-725345543-4606″ />
</System>
<EventData>
<Data>CN=MyCo Enterprise Issuing CA 1, CN=Public Key Services,CN=Services,CN=Configuration,DC=MyCo,DC=Com</Data>
<Data> (objectClass=cRLDistributionPoint) </Data>
<Data>1</Data>
<Data>1</Data>
<Data>192.168.40.10:4048</Data>
<Data>base</Data>
<Data>deltaRevocationList</Data>
<Data>
</Data>
</EventData>
</Event>
So if I wanted to use Xpath to filter all events in the Directory Service Event Log from that IP address my query would look something like this:
<QueryList>
<Query Id=”0″ Path=”Directory Service”>
<Select Path=”Directory Service”>*[System[(Level=4 or Level=0) and (EventID=1644)]] and *[EventData[Data[5]=’192.168.40.10:4048′]]</Select>
</Query>
</QueryList>
The query works well, but the problem is that the Data node within the XML contains the port number (4048) in addition to the IP address. I want to find all queries issued from that client, regardless of the port used. Here’s my attempt to use the starts-with() function to filter the event.
<QueryList>
<Query Id=”0″ Path=”Directory Service”>
<Select Path=”Directory Service”>*[System[(Level=4 or Level=0) and (EventID=1644)]] and *[EventData[starts-with(Data[5],’192.168.40.10′)]]</Select>
</Query>
</QueryList>
This fails with the error “The specified query is invalid“. Back to the drawing board. I posted a question to Technet Forums and got some good help from Ivan Ting at Microsoft. He provided some Javascript that used starts-with() and this worked (after some fun messing around with default namespace issues). Being something of a Javascript muppet (the antithesis of a Javascript guru), I decided to try my hand at a Powershell version. Here’s what I came up with.
#####
# Author: Tony Murray
# File name: LDAPEvents.ps1
# Date: 28th April 2009
# Purpose: Extracts LDAP Search information from Directory Service Event
# Log. Requires LDAP logging to be switched on.
###### Function to create an object for XML document navigation
# Source: Technet Scriptcenter
function get-xpn ($text)
{
$rdr = [System.IO.StreamReader] $text
$trdr = [system.io.textreader]$rdr
$xpdoc = [System.XML.XPath.XPathDocument] $trdr
$xpdoc.CreateNavigator()
}# Run Wevtutil.exe to export the Event log to file
# Could use Powershell to do this but it creates odd-looking xml!$file = “c:\util\dumplog.xml”
& $env:windir\System32\wevtutil qe `”Directory Service` /e:Events | Out-File $file# Remove the namespace from the xml file. It won’t work if it stays
$findStr = ” xmlns=`’http://schemas.microsoft.com/win/2004/08/events/event`'”
$ReplStr = “”
$newcontent = (Get-Content $file) -replace ($findStr,$ReplStr)
Set-Content $file $newcontent# Invoke the navigator
$xb = get-xpn $file# Define the Xpath query we want to use
$query = “//*[ System[(Level=4 or Level=0) and (EventID=1644)] `
and EventData[starts-with(Data[5],’192.168.40.10′)]]”# Create a CSV file with the output. Each line represents the details
# we want from a single Event.Write-Output $xb.Select($query) | %{ $_.OuterXml} | Select-Object `
@{name = “Date&Time”;Expression = {$_.Event.System.TimeCreated.SystemTime}}, `
@{name = “SearchBase”;Expression = {$_.Event.EventData.Data[0]}}, `
@{name = “Filter”;Expression = {$_.Event.EventData.Data[1]}}, `
@{name = “Visited”;Expression = {$_.Event.EventData.Data[2]}}, `
@{name = “Returned”;Expression = {$_.Event.EventData.Data[3]}}, `
#@{name = “SourceIP”;Expression = {$_.Event.EventData.Data[4]}}, `
@{name = “SearchScope”;Expression = {$_.Event.EventData.Data[5]}} `
| export-csv ds.csv -notype
# Replace the previous line with the following line to change the output format
#ConvertTo-HTML | Out-File “LDAPEvent.html”
Having to write a script is more effort than simply issuing the query from within Eventvwr, but it does have the advantage of allowing you to return only the information you are interested in – and in the format that you want. Hopefully, my experience will save you a bit of time and effort if you are trying to achieve something similar.
On this line:
@{name = “Visited”;Expression = {$_.Event.EventData.Data[2]}}, `
Is it possible to use named attributes, rather than an array index for the Data element?
On this line:
@{name = “Visited”;Expression = {$_.Event.EventData.Data[2]}}, `
Is it possible to use named attributes, rather than an array index for the Data element?