Recently, one of our customers asked us to help port their PowerShell script to our script engine (built on FastScript/Pascal Script). While helping them, we paid close attention to how they filtered events in PowerShell—and noticed a common performance pitfall.
Their original script filtered the System log by event type using a pipeline:
$elSysErr = Get-EventLog -LogName System | Where-Object EntryType -Eq 'Error'
Although the PowerShell pipeline is a powerful feature, using it here is unnecessary and inefficient. This command first loads all events from the System log into memory and only then filters them by type. A more efficient approach is to filter during retrieval:
$elSysErr = Get-EventLog -LogName System -EntryType 'Error'
This runs faster—but there’s a bigger issue.
You shouldn’t use Get-EventLog at all:
- Its filtering parameters are limited (no reliable way to filter by Event ID).
- It works only with classic/legacy logs.
- It relies on deprecated Windows APIs.
Microsoft now recommends using Get-WinEvent, which is documented here:
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.diagnostics/get-winevent
Rewriting the Command with Get-WinEvent
At first glance, you might simply replace Get-EventLog with Get-WinEvent:
$elSysErr = Get-WinEvent System | Where-Object LevelDisplayName -eq 'Error'
Or slightly better:
$elSysErr = Get-WinEvent System | Where-Object Level -eq 2
But this still requires reading the entire log first—so you gain no performance benefits.
The real power of Get-WinEvent is that it allows event filtering before reading the log. You can filter in two ways:
- Using a hashtable
- Using an XML (XPath) query
Using a Hashtable (the simplest approach)
$elSysErr = Get-WinEvent -FilterHashTable @{LogName='System'; Level=2}
Using an XPath query
$elSysErr = Get-WinEvent -LogName 'System' -FilterXPath '*[System[(Level=2)]]'
Performance Comparison
I ran all variations against my local System log (≈50,000 events), using Measure-Command.
Pipeline filtering (Get-EventLog + Where-Object)
Measure-Command { Get-EventLog -LogName System | Where-Object EntryType -Eq 'Error' }
≈ 8 seconds
Filter parameter (Get-EventLog -EntryType)
Measure-Command { Get-EventLog -LogName System -EntryType 'Error' }
≈ 6 seconds (about 25% faster)
Pipeline filtering with Get-WinEvent
Measure-Command { Get-WinEvent System | Where-Object Level -eq 2 }
≈ 13 seconds (even slower!)
FilterHashTable (the fastest)
Measure-Command { Get-WinEvent -FilterHashTable @{LogName='System'; Level=2} }
≈ 566 ms
XPath filter
Measure-Command { Get-WinEvent -LogName 'System' -FilterXPath '*[System[(Level=2)]]' }
≈ 600 ms
Conclusion:
For performance, always use Get-WinEvent with -FilterHashTable, -FilterXML, or -FilterXPath.
Handling the “No events found” error
One important detail rarely mentioned in online resources:
If no events match the filter, Get-WinEvent throws a non-terminating error.
This is not a big issue in interactive sessions, but in scripts it produces noisy output. To avoid this, simply ignore the error:
$elSysErr = Get-WinEvent -FilterHashTable @{LogName='System'; Level=2} -ErrorAction SilentlyContinue
if ($elSysErr -eq $null) {
Write-Output "no events match this filter"
} else {
# work with the result set
}
How This Looks in Event Log Explorer Script Engine
Here’s how we port the same query into our scripting engine:
theApp.OpenLogQuery('', 'System', '*[System[(Level = 2)]]', True);
To measure performance:
var
StartTime, FinishTime: DWORD;
begin
StartTime := GetTickCount();
theApp.OpenLogQuery('', 'System', '*[System[(Level = 2)]]', True);
FinishTime := GetTickCount();
DebugOut('Executed in ', FinishTime - StartTime, ' ms');
end.
Result: ≈ 609 ms
Not bad—especially considering it not only retrieves the events, but also displays them.
Closing Thoughts
That’s all for now. As the developers of Event Log Explorer, we strongly recommend using our software for tasks involving event log analysis. However, we fully recognize that PowerShell offers an exceptionally powerful scripting environment for managing Windows systems. When you choose to work with PowerShell, it’s important to do so efficiently—and using the right event filtering techniques can dramatically improve performance.
