HackBlue

Tools · Windows security and Event Logs · 12 / 27

Get-WinEvent tutorial: fast filtering with FilterHashtable and XPath

Query Windows event logs efficiently from PowerShell, extract fields from event XML, query remote machines, and avoid the slow Where-Object trap.

11 min read · practice only with authorization

Filter at the source

Get-WinEvent -LogName Security | Where-Object Id -eq 4625 reads every event and then filters, which can take minutes. -FilterHashtable passes the filter to the event log service so only matching events are returned. Keys include LogName, Id (one or an array), ProviderName, Level, StartTime, EndTime, and Data for matching event data values.

-FilterXPath and -FilterXml allow more complex conditions, such as matching a specific field value, and are what Event Viewer’s custom views use under the hood.

Snippet
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=(Get-Date).AddDays(-1)} -MaxEvents 50
Get-WinEvent -LogName Security -FilterXPath "*[System[EventID=4624] and EventData[Data[@Name='LogonType']='10']]" -MaxEvents 20

Getting fields out

Each event has a Properties array with values in a fixed order for that event ID, and a ToXml() method that returns named fields. Named fields from the XML are more readable and survive across versions; positional Properties are faster when you know the layout.

A small helper that converts an event into a PowerShell object with named properties makes every later query cleaner.

Snippet
function ConvertFrom-WinEvent { process {
  $x=[xml]$_.ToXml(); $o=[ordered]@{Time=$_.TimeCreated; Id=$_.Id; Computer=$_.MachineName}
  foreach ($d in $x.Event.EventData.Data) { $o[$d.Name]=$d.'#text' }
  [pscustomobject]$o } }
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624} -MaxEvents 5 | ConvertFrom-WinEvent | Format-List

Lab: measure the difference and go remote

Time the Where-Object version and the FilterHashtable version of the same query on a busy log with Measure-Command. Then query a second lab machine with -ComputerName, which needs the Remote Event Log Management firewall rule enabled, or wrap the query in Invoke-Command over WinRM.

Export results with Export-Csv for sharing, and use Get-WinEvent -Path to read a saved .evtx file collected from another machine.

Snippet
Measure-Command { Get-WinEvent -LogName Security | Where-Object Id -eq 4625 }
Measure-Command { Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625} -ErrorAction SilentlyContinue }
Invoke-Command -ComputerName LAB-SRV01 { Get-WinEvent -FilterHashtable @{LogName='System'; Id=7045} -MaxEvents 10 }
Get-WinEvent -Path .\collected\Security.evtx -FilterXPath '*[System[EventID=1102]]'

Practical tips

Use -ErrorAction SilentlyContinue when a filter may match nothing, otherwise PowerShell throws “No events were found.” Run as administrator to read the Security log. Prefer -MaxEvents while you are developing a query.

Get-WinEvent -ListLog * shows every log and its record count, and -ListProvider shows which provider writes to which log.

Diagram

Two ways to find 4625 events

Same answer, very different cost.

Pipe then filter

Command. Get-WinEvent -LogName Security | Where-Object Id -eq 4625

Work. Reads and parses every event.

Time. Slow on large logs.

Filter at source

Command. Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625}

Work. The log service returns matches only.

Time. Fast.

Check yourself

1. Why is FilterHashtable faster than Where-Object?

2. How do you read a saved .evtx file?

3. Which method returns named event fields such as TargetUserName?