๐ŸŽ New User? Get 20% off your first purchase with code NEWUSER20 ยท โšก Instant download ยท ๐Ÿ”’ Secure checkout Register Now โ†’
Menu

Categories

Windows Event Log Analysis with PowerShell: Get-WinEvent Deep Dive (2026)

Windows Event Log Analysis with PowerShell: Get-WinEvent Deep Dive (2026)
Windows event log analysis with PowerShell guide

Get-EventLog has been deprecated since PowerShell 6 and only sees the three legacy logs (Application, System, Security). Real Windows troubleshooting in 2026 means Get-WinEvent โ€” the modern cmdlet that talks to every log channel (300+ on a typical Windows Server), supports XPath, supports server-side filtering via FilterHashtable, and does not load the entire log into memory before filtering.

This guide is the practical reference: the right way to filter, how to chase a security incident, how to find the hard-to-spot kernel events, and the patterns that matter when a single Windows Server has 50 million events in its Security log. Free PDF cheat sheet at the end.

Why Get-WinEvent (not Get-EventLog)

Three reasons. First, Get-EventLog only sees Application, Security, System and a handful of older logs โ€” modern logs (the entire Microsoft-Windows-... hierarchy) are invisible to it. Second, Get-WinEvent filters on the server side via FilterHashtable or XPath โ€” the result of Get-EventLog | Where-Object is "load all 5 million events into memory, then filter", which is unusable on a busy Windows Server. Third, Get-WinEvent is faster on every measurable axis even for the legacy logs.

If you still see Get-EventLog in old scripts, replace it. The conversion is mechanical.

List available log channels

# All registered log providers (300+ on a typical server)
Get-WinEvent -ListLog * | Where-Object RecordCount -gt 0 |
    Sort-Object RecordCount -Descending |
    Select-Object LogName, RecordCount, FileSize, LastWriteTime |
    Select-Object -First 20

# Find a specific provider
Get-WinEvent -ListProvider *defender* | Format-Table Name

Basic queries

# Last 10 events from System log
Get-WinEvent -LogName System -MaxEvents 10

# All events of one type
Get-WinEvent -LogName System -MaxEvents 100 |
    Where-Object Id -eq 6005    # event log service started

# Pretty-print a single event
$e = Get-WinEvent -LogName System -MaxEvents 1
$e | Format-List TimeCreated, Id, LevelDisplayName, ProviderName, Message

Note: Where-Object Id -eq 6005 is filter after retrieval. For anything bigger than a few hundred events, switch to FilterHashtable.

FilterHashtable โ€” the production filter

This is the most important section. FilterHashtable pushes the filter into the Windows event subsystem, so only matching events ever leave the kernel. On a 50-million-event Security log, this is the difference between 2 seconds and "give up after an hour".

# Failed logons in the last 24 hours
Get-WinEvent -FilterHashtable @{
    LogName   = 'Security'
    Id        = 4625                   # Logon failure
    StartTime = (Get-Date).AddHours(-24)
}

# Multiple IDs
Get-WinEvent -FilterHashtable @{
    LogName = 'Security'
    Id      = 4624,4625,4634           # logon, failure, logoff
    StartTime = (Get-Date).AddDays(-1)
}

# By provider
Get-WinEvent -FilterHashtable @{
    ProviderName = 'Microsoft-Windows-Defender'
    Level        = 1,2,3               # Critical, Error, Warning
    StartTime    = (Get-Date).AddDays(-7)
}

# By user (note: needs SID, not name)
Get-WinEvent -FilterHashtable @{
    LogName = 'Security'
    Id      = 4624
    Data    = (New-Object System.Security.Principal.NTAccount("DOMAIN\jsmith")).Translate([System.Security.Principal.SecurityIdentifier]).Value
}

Allowed keys in the hashtable: LogName, ProviderName, Path, Keywords, Id, Level, StartTime, EndTime, UserID, Data.

XPath filters

For filters that FilterHashtable cannot express (specific EventData fields, complex predicates), use XPath via -FilterXPath. The syntax is identical to what Event Viewer's Custom View dialog generates.

# Logon events for a specific account name
Get-WinEvent -LogName Security -FilterXPath "
    *[System[EventID=4624]]
    and *[EventData[Data[@Name='TargetUserName']='jsmith']]
"

# All audit failures from a specific IP
Get-WinEvent -LogName Security -FilterXPath "
    *[System[EventID=4625]]
    and *[EventData[Data[@Name='IpAddress']='10.20.30.40']]
"

Generate the XPath the easy way: in Event Viewer, build a Custom View, click "Edit Filter โ†’ XML โ†’ Edit query manually", and copy the inner Select Path="..." body.

Hunting security events

The IDs you actually look at most:

IDMeaning
4624Successful logon
4625Failed logon
4634 / 4647Logoff
4672Special privileges assigned (admin login)
4720 / 4722 / 4725 / 4726Account create / enable / disable / delete
4732 / 4733Member added / removed from local group
4738User account changed
4768 / 4769 / 4771Kerberos TGT / service ticket / pre-auth fail
4776NTLM authentication attempt
1102Audit log cleared (suspicious!)
# Top 10 source IPs of failed logons in the last 24h
Get-WinEvent -FilterHashtable @{ LogName='Security'; Id=4625; StartTime=(Get-Date).AddHours(-24) } |
    ForEach-Object {
        $ip = ($_.Properties[19]).Value
        if ($ip) { [PSCustomObject]@{ IP = $ip } }
    } |
    Group-Object IP | Sort-Object Count -Descending |
    Select-Object -First 10 Name, Count

Remote queries

# Single remote computer
Get-WinEvent -ComputerName web01 -LogName System -MaxEvents 20

# Multiple computers in parallel (PS 7+)
'web01','web02','web03' | ForEach-Object -Parallel {
    Get-WinEvent -ComputerName $_ -FilterHashtable @{
        LogName = 'Security'; Id = 4625
        StartTime = (Get-Date).AddHours(-1)
    } | Select-Object @{n='Server';e={$using:_}}, TimeCreated, Id, Message
} -ThrottleLimit 10

Remote queries require permission on the target's Event Log Readers group (or admin) and TCP/135 + dynamic RPC ports open inbound. WinRM-based remoting is more firewall-friendly: Invoke-Command -ComputerName web01 -ScriptBlock { Get-WinEvent ... }.

Export and rotation

# Export to .evtx (binary, can be re-opened in Event Viewer)
wevtutil epl Security C:\Backup\Security-2026-04-17.evtx

# Export to CSV
Get-WinEvent -FilterHashtable @{ LogName='Security'; Id=4624; StartTime=(Get-Date).AddDays(-7) } |
    Select-Object TimeCreated, Id, @{n='User';e={$_.Properties[5].Value}}, Message |
    Export-Csv .\logons-7d.csv -NoTypeInformation

# Clear a log (requires admin; audit logged as 1102)
wevtutil cl Security

Performance tips

  • Always use FilterHashtable or -FilterXPath. Never Get-WinEvent ... | Where-Object on busy logs.
  • Use -MaxEvents for ad-hoc spelunking โ€” saves RAM.
  • For incident response, copy the .evtx file off the host with wevtutil epl and analyse offline.
  • Querying remote Security logs is slower than local โ€” use -Path against an exported file when possible.
  • Stream large result sets through the pipeline; don't $x = Get-WinEvent ... when there are millions of events.

Cheat sheet

Every filter, every important security event ID on a single PDF: Get-WinEvent Cheat Sheet.

FAQ

Why does my filter return nothing for a recent event?

The most common cause: the event source is logging to a different channel than you assume. Check with Get-WinEvent -ListLog * | Where-Object RecordCount -gt 0 and look for the right LogName. Microsoft channels are deeply nested.

How do I read the EventData fields?

$event.Properties is an array; the position depends on the event ID. For 4624, position 5 is the target user, position 19 is the IP address. The Microsoft docs for each event ID list the field order.

Can I subscribe to events in real time?

Yes โ€” use Register-WinEvent for cmdlet-based subscriptions, or wevtutil sub for system-level event forwarding to a collector.

What permissions do I need to read the Security log?

Local administrator OR membership in the built-in Event Log Readers group, plus SeAuditPrivilege for some advanced reads.

Why are some event channels missing entries?

The channel may be disabled. Check with Get-WinEvent -ListLog <channel> | Format-List and look at IsEnabled. Enable with wevtutil sl <channel> /e:true.

How do I correlate events across multiple servers?

Either pull them all to one place with -ComputerName and merge by TimeCreated, or use Windows Event Forwarding (WEF) with a collector. WEF is the production answer for fleets.

Get-WinEvent is slow against the Security log โ€” what now?

Almost always means you are filtering with Where-Object instead of FilterHashtable. Move every condition you can into the hashtable and the query usually becomes 100x+ faster.

Related reading

Share this article:
Dargslan Editorial Team (Dargslan)
About the Author

Dargslan Editorial Team (Dargslan)

Collective of Software Developers, System Administrators, DevOps Engineers, and IT Authors

Dargslan is an independent technology publishing collective formed by experienced software developers, system administrators, and IT specialists.

The Dargslan editorial team works collaboratively to create practical, hands-on technology books focused on real-world use cases. Each publication is developed, reviewed, and...

Programming Languages Linux Administration Web Development Cybersecurity Networking

Stay Updated

Subscribe to our newsletter for the latest tutorials, tips, and exclusive offers.