How to read Windows Event Logs with PowerShell (Get-WinEvent)

Event Viewer is fine for occasional manual checks. It breaks down the moment you need to search across multiple machines, filter by time range, correlate events by ID, or automate any part of the process. Get-WinEvent is the PowerShell cmdlet that covers all of that — it queries any event log on any machine, supports structured filtering that runs server-side, and returns objects you can sort, group, and export without parsing text.

This guide covers practical filtering patterns for real troubleshooting scenarios: failed logins, service crashes, application errors, and targeted queries across multiple machines. It also covers where Get-WinEvent fits alongside the Windows Event Log Analyzer tool for faster triage of unfamiliar log entries.


Quick answer

Get the last 50 errors from the System log:

Get-WinEvent -LogName System -MaxEvents 50 | Where-Object { $_.LevelDisplayName -eq "Error" }

Get all failed login attempts (Event ID 4625) from the Security log:

Get-WinEvent -LogName Security -FilterXPath "*[System[EventID=4625]]" -MaxEvents 100

Get-WinEvent vs Get-EventLog

Windows PowerShell has two cmdlets for reading event logs. Use Get-WinEvent. The older Get-EventLog only accesses classic Windows logs (Application, System, Security) and cannot query modern ETW-based logs like Microsoft-Windows-DNS-Client/Operational or any provider-specific log. Get-WinEvent covers all of them.

Warning: Get-EventLog is deprecated. It still works on current Windows versions but has no equivalent in PowerShell 7+ on non-Windows platforms and will eventually be removed. Build new scripts with Get-WinEvent from the start.

Filtering methods

Get-WinEvent supports three filtering approaches. Understanding which to use matters for performance — especially when querying large logs or remote machines.

MethodParameterRuns server-sideBest for
XPath filter-FilterXPathYesEvent ID, time range, single conditions
Hashtable filter-FilterHashtableYesMultiple conditions, readable syntax
Where-Object(pipeline)NoPost-fetch filtering on returned objects

Server-side filtering means the query runs on the machine where the log lives — only matching events are transferred. Where-Object fetches everything first and filters locally, which is slow on large logs and generates unnecessary network traffic on remote queries.

Note: Use -FilterHashtable as your default. It is more readable than XPath and still runs server-side. Reserve -FilterXPath for conditions that hashtable filtering does not support — like filtering on specific event data fields inside the XML body.

Common Event IDs reference

These are the Event IDs that come up most often in day-to-day admin work:

Event IDLogMeaning
4624SecuritySuccessful logon
4625SecurityFailed logon attempt
4634SecurityAccount logoff
4648SecurityLogon attempt with explicit credentials
4720SecurityUser account created
4740SecurityUser account locked out
4776SecurityDomain controller credential validation
7034SystemService terminated unexpectedly
7036SystemService entered running or stopped state
1074SystemSystem was shut down or restarted
41SystemSystem rebooted without clean shutdown (crash/power loss)
1000ApplicationApplication crash
1001ApplicationWindows Error Reporting — follow-up to crash
Note: If you encounter an unfamiliar Event ID in your logs, paste the raw event XML into the Windows Event Log Analyzer tool — it uses AI to explain what the event means and suggest next steps.

Practical filtering examples

Filter by Event ID and time range

The most common query pattern: find all occurrences of a specific Event ID within a time window. Use -FilterHashtable with StartTime and EndTime for clean, readable syntax:

# Find all failed logins in the last 24 hours
# StartTime filters server-side — only matching events are returned
Get-WinEvent -FilterHashtable @{
    LogName   = 'Security'
    Id        = 4625
    StartTime = (Get-Date).AddDays(-1)
} | Select-Object TimeCreated, Message | Format-List
# Find service crashes in the last 7 days
Get-WinEvent -FilterHashtable @{
    LogName   = 'System'
    Id        = 7034
    StartTime = (Get-Date).AddDays(-7)
} | Select-Object TimeCreated, Message

Filter by severity level

Event levels map to numeric values in the filter: 1 = Critical, 2 = Error, 3 = Warning, 4 = Information, 5 = Verbose. Use the Level key in the hashtable:

# Get all Critical and Error events from the System log in the last hour
Get-WinEvent -FilterHashtable @{
    LogName   = 'System'
    Level     = 1, 2        # 1 = Critical, 2 = Error
    StartTime = (Get-Date).AddHours(-1)
} | Select-Object TimeCreated, LevelDisplayName, Message

Search event message text

When you know a keyword but not the Event ID, use Where-Object to filter on the message text. This runs client-side, so combine it with a server-side time filter to keep the result set manageable:

# Find Application log events mentioning a specific service name
# StartTime runs server-side, Where-Object runs client-side on the returned set
Get-WinEvent -FilterHashtable @{
    LogName   = 'Application'
    StartTime = (Get-Date).AddDays(-3)
} | Where-Object { $_.Message -like "*Spooler*" } |
    Select-Object TimeCreated, Id, LevelDisplayName, Message

Query a remote machine

Add -ComputerName to run the same query against any machine on the network. Credentials, firewall rules, and WinRM availability permitting:

# Query failed logins on a remote domain controller
# Requires WinRM to be enabled and appropriate permissions on the remote machine
Get-WinEvent -ComputerName SRV-DC-01 -FilterHashtable @{
    LogName   = 'Security'
    Id        = 4625
    StartTime = (Get-Date).AddHours(-4)
} | Select-Object TimeCreated, Message
Warning: Remote queries require WinRM to be enabled on the target machine (Enable-PSRemoting) and the querying account to have read permissions on the Security log — which typically means membership in the Event Log Readers group or local administrator rights on the target.

Query across multiple machines

Loop through a list of servers to aggregate results from multiple machines into a single output:

# Check for service crashes on a list of servers over the last 24 hours
$servers = 'SRV-APP-01', 'SRV-APP-02', 'SRV-WEB-01'

foreach ($server in $servers) {
    Write-Host "--- $server ---"
    Get-WinEvent -ComputerName $server -FilterHashtable @{
        LogName   = 'System'
        Id        = 7034
        StartTime = (Get-Date).AddDays(-1)
    } -ErrorAction SilentlyContinue |
        Select-Object TimeCreated, Message |
        Format-List
}
Note: -ErrorAction SilentlyContinue prevents the loop from stopping if one machine is unreachable. Remove it during development so you can see connection errors clearly.

Query provider-specific logs

Beyond Application/System/Security, Windows has hundreds of provider-specific logs under Applications and Services Logs. These are not accessible with Get-EventLog — only with Get-WinEvent:

# List all available logs — useful for finding the exact log name
Get-WinEvent -ListLog * | Where-Object { $_.RecordCount -gt 0 } |
    Select-Object LogName, RecordCount | Sort-Object LogName
# Query the DNS Client operational log — not available via Get-EventLog
Get-WinEvent -LogName 'Microsoft-Windows-DNS-Client/Operational' -MaxEvents 50 |
    Select-Object TimeCreated, Id, Message
Warning: Many provider-specific operational logs are disabled by default to avoid disk overhead. If a log shows 0 records, check whether it is enabled: Get-WinEvent -ListLog 'Microsoft-Windows-DNS-Client/Operational' | Select-Object IsEnabled. Enable with wevtutil sl "LogName" /e:true.

Exporting results

Event objects contain more fields than what Format-List shows. Export to CSV for further analysis or to share with others:

# Export failed logins to CSV — useful for sharing or analysis in Excel
Get-WinEvent -FilterHashtable @{
    LogName   = 'Security'
    Id        = 4625
    StartTime = (Get-Date).AddDays(-7)
} | Select-Object TimeCreated, Id, LevelDisplayName, Message |
    Export-Csv -Path C:\bat\failed-logins.csv -NoTypeInformation

For structured reporting, format results as a table and write to a text file:

# Write a summary of recent System errors to a text report
Get-WinEvent -FilterHashtable @{
    LogName   = 'System'
    Level     = 1, 2
    StartTime = (Get-Date).AddDays(-1)
} | Select-Object TimeCreated, Id, LevelDisplayName, Message |
    Format-Table -AutoSize |
    Out-File -FilePath C:\bat\system-errors-report.txt

Using Event Log Analyzer for unfamiliar events

When a query returns events with cryptic descriptions or XML payloads you have not seen before, the fastest path to understanding is the Windows Event Log Analyzer tool. Paste the raw event XML — which you can get with $event.ToXml() — and it explains what the event means and what to check next.

# Get raw XML for a specific event — paste into Event Log Analyzer for AI analysis
$events = Get-WinEvent -FilterHashtable @{
    LogName   = 'System'
    Id        = 41
    StartTime = (Get-Date).AddDays(-30)
} -MaxEvents 1

# Output the full XML body of the first result
$events[0].ToXml()
Result: Copy the XML output and paste it into the Event Log Analyzer. The tool explains the event in plain English and suggests specific diagnostic steps — useful for Event ID 41 (unexpected reboot) and other events where the message text alone is not actionable.

Tips and limitations

  • Security log requires elevation. Querying the Security log locally requires the session to be running as administrator. Without elevation, Get-WinEvent -LogName Security returns an access denied error. On domain controllers, the account also needs Manage auditing and security log rights.
  • Always use server-side filtering on large logs. The Security log on an active domain controller can contain millions of records. Using only Where-Object without a -FilterHashtable StartTime will attempt to load the entire log into memory — expect it to be very slow or fail entirely.
  • -MaxEvents limits results from the newest end. -MaxEvents 100 returns the 100 most recent matching events. There is no built-in equivalent for oldest-first without sorting after the fact.
  • Message text is localized. The Message property is generated at query time using the local machine’s language pack. The same event queried from an English and a German Windows machine can produce different message text. The Event ID and XML data are always consistent regardless of locale.
  • Some logs require enabling before they capture anything. Operational logs under Applications and Services Logs are often disabled by default. Query them with Get-WinEvent -ListLog to check IsEnabled before wondering why they are empty.

Official documentation

Related tools

Related guides