🎁 New User? Get 20% off your first purchase with code NEWUSER20 Register Now →
Menu

Categories

Windows Event Log Forensic Queries: A 2026 PowerShell Reference

Windows Event Log Forensic Queries: A 2026 PowerShell Reference
Windows event log forensic queries - Dargslan 2026

Get-WinEvent is the right tool because it streams from the channel, supports XPath filtering at the source (server-side, not in PowerShell), and works against remote hosts. The recipes below cover the most common questions an incident responder asks of Windows in 2026.

Why XPath, not Where-Object

Filtering with Where-Object after the fact pulls every event off the channel and across the network. FilterXPath filters at the source. On a busy DC, the difference is seconds vs minutes:

Get-WinEvent -LogName Security -FilterXPath "*[System[EventID=4624]]"
# vs (do not do this):
Get-WinEvent -LogName Security | Where Id -eq 4624

Logon and logoff events

Get-WinEvent -LogName Security -FilterXPath @"
*[System[(EventID=4624 or EventID=4625) and TimeCreated[timediff(@SystemTime) <= 86400000]]]
"@ |
    Select TimeCreated, Id,
        @{N='User';E={$_.Properties[5].Value + '\' + $_.Properties[6].Value}},
        @{N='LogonType';E={$_.Properties[8].Value}},
        @{N='SourceIp';E={$_.Properties[18].Value}}

LogonType cheat-sheet: 2 = interactive, 3 = network (SMB), 7 = unlock, 10 = RDP, 11 = cached domain.

Account lockout source

4740 on the DC names the source workstation:

Get-WinEvent -ComputerName DC01 -LogName Security `
    -FilterXPath "*[System[EventID=4740]]" |
    Select TimeCreated, @{N='User';E={$_.Properties[0].Value}},
        @{N='Source';E={$_.Properties[1].Value}}

RDP session history

Get-WinEvent -LogName 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational' `
    -FilterXPath "*[System[(EventID=21 or EventID=23 or EventID=25)]]" |
    Select TimeCreated, Id, @{N='User';E={$_.UserId}}

21 = login successful, 23 = logoff, 25 = reconnect.

Service install and start

Get-WinEvent -LogName System -FilterXPath "*[System[EventID=7045]]" |
    Select TimeCreated,
        @{N='Service';E={$_.Properties[0].Value}},
        @{N='Path';E={$_.Properties[1].Value}}

7045 (service install) is one of the most reliable post-exploitation indicators on a Windows host.

Scheduled task creation

Get-WinEvent -LogName 'Microsoft-Windows-TaskScheduler/Operational' `
    -FilterXPath "*[System[EventID=106]]" |
    Select TimeCreated, @{N='Task';E={$_.Properties[0].Value}},
        @{N='User';E={$_.Properties[1].Value}}

PowerShell command history

Get-WinEvent -LogName 'Microsoft-Windows-PowerShell/Operational' `
    -FilterXPath "*[System[EventID=4104]]" -MaxEvents 500 |
    Select TimeCreated, @{N='Script';E={($_.Message -split "`n")[2]}}

USB device insertion

Get-WinEvent -LogName 'Microsoft-Windows-DriverFrameworks-UserMode/Operational' `
    -FilterXPath "*[System[EventID=2003 or EventID=2004 or EventID=2010]]"

Defender detections

Get-WinEvent -LogName 'Microsoft-Windows-Windows Defender/Operational' `
    -FilterXPath "*[System[(EventID=1116 or EventID=1117)]]" |
    Select TimeCreated, Id,
        @{N='Threat';E={$_.Properties[7].Value}},
        @{N='Path';E={$_.Properties[21].Value}}

IR-ready checklist

  1. Snapshot of the relevant channel exported with wevtutil epl.
  2. Logon timeline (4624/4625) for the last 14 days.
  3. 4740 lockout source for the affected user.
  4. 7045 service install in the last 14 days.
  5. 4104 ScriptBlock log scan for IEX, Invoke-Expression, FromBase64String.

Hunting queries: pivot from a single event to the whole story

An event log query is only useful if it leads somewhere. The hunting mindset is to start from a high-signal event (logon, process create, scheduled task created) and pivot through related events on time, machine and user until you have a complete story.

Logon hunting starting from event 4624

# Successful interactive logons in the last 24 hours
$events = Get-WinEvent -FilterHashtable @{
  LogName='Security'; Id=4624; StartTime=(Get-Date).AddDays(-1)
}

$events | ForEach-Object {
  $xml = [xml]$_.ToXml()
  $d = $xml.Event.EventData.Data
  [pscustomobject]@{
    TimeCreated   = $_.TimeCreated
    LogonType     = ($d | Where-Object Name -eq 'LogonType').'#text'
    User          = ($d | Where-Object Name -eq 'TargetUserName').'#text'
    Workstation   = ($d | Where-Object Name -eq 'WorkstationName').'#text'
    SrcIp         = ($d | Where-Object Name -eq 'IpAddress').'#text'
    LogonProcess  = ($d | Where-Object Name -eq 'LogonProcessName').'#text'
  }
} |
  Where-Object LogonType -in 2,3,10 |   # interactive, network, remote-interactive
  Format-Table -AutoSize

Logon type 10 from an external IP outside business hours is the canonical "investigate now" signal.

Process-create chain (4688) with parent attribution

Event 4688 records every process creation and, with command-line auditing enabled, the full command line. Combine with parent-process tracking and you have a tree.

$proc = Get-WinEvent -FilterHashtable @{
  LogName='Security'; Id=4688; StartTime=(Get-Date).AddHours(-2)
}

$proc | ForEach-Object {
  $xml = [xml]$_.ToXml()
  $d = $xml.Event.EventData.Data
  [pscustomobject]@{
    Time   = $_.TimeCreated
    User   = ($d | Where-Object Name -eq 'SubjectUserName').'#text'
    Image  = ($d | Where-Object Name -eq 'NewProcessName').'#text'
    Cmd    = ($d | Where-Object Name -eq 'CommandLine').'#text'
    Parent = ($d | Where-Object Name -eq 'ParentProcessName').'#text'
  }
} |
  Where-Object { $_.Image -match 'powershell|cmd|wscript|cscript|mshta|regsvr32' } |
  Format-Table -AutoSize

Scheduled task creation (event 4698)

A new scheduled task is the most common attacker persistence mechanism. Anything created outside change windows by a non-admin account deserves review.

Get-WinEvent -FilterHashtable @{ LogName='Security'; Id=4698 } |
  Select TimeCreated,
         @{N='User';E={ ([xml]$_.ToXml()).Event.EventData.Data |
            Where-Object Name -eq 'SubjectUserName' | Select -Expand '#text' }},
         @{N='TaskName';E={ ([xml]$_.ToXml()).Event.EventData.Data |
            Where-Object Name -eq 'TaskName' | Select -Expand '#text' }} |
  Format-Table -AutoSize

Common pitfalls

  • Querying without a time window. A bare Get-WinEvent -LogName Security on a busy DC pulls millions of events. Always filter at the source with FilterHashtable.
  • Using Get-EventLog instead of Get-WinEvent. The legacy cmdlet does not handle classic and crimson logs uniformly and ignores the message catalogue. Stick with Get-WinEvent.
  • Trusting message text. The localised message can change between OS versions. Filter on event ID and pull data from the XML payload, never from the message string.
  • Not enabling command-line in 4688. Without it, every interesting question becomes "we cannot tell". Enable via Group Policy: Audit Process Creation + Include command line.
  • Forgetting Sysmon. Native logging is good; Sysmon is great. For any environment that takes detection seriously, Sysmon (with a curated config like olafhartong's) multiplies the visibility for free.

Triage playbook: from "we got an alert" to "we have a story"

An EDR alert or SIEM hit lands in the queue. The playbook below converts that single signal into a defensible incident timeline within an hour, using only built-in event log queries.

  1. Minute 0–5 — capture context. From the alert, extract: hostname, user, timestamp (with timezone), and any named indicator (process, file, hash). Open a fresh investigation note with these as the header.
  2. Minute 5–15 — logon source. On the affected host, query event 4624 around the timestamp ±15 minutes. Identify the logon type (interactive, network, service), the workstation name, and the source IP. Cross-reference against expected user behaviour.
  3. Minute 15–25 — process tree. Pull 4688 events for the user in the same window. Reconstruct the parent-child chain. Note any process that runs from a writeable user-profile path (%LocalAppData%, %Temp%, %Downloads%) — this is the canonical "where attackers live".
  4. Minute 25–40 — persistence sweep. Check 4698 (scheduled task created), 4697 (service installed), 4670 (permissions changed) within ±2 hours. Anything created or modified by the user under investigation is a persistence candidate.
  5. Minute 40–50 — lateral movement. Pull 4624 logon-type 3 (network) for the user across other hosts. PowerShell Remoting events from Microsoft-Windows-WinRM/Operational reveal interactive remote sessions even when the source claims none.
  6. Minute 50–60 — write the story. Order the events on a timeline. Mark each with what you know, what you assume, and what you cannot tell. The output is a single document the next analyst can pick up without rediscovering the chain.

Two pre-written queries make the playbook fast: a "logon and process tree for one user" function that takes a username and time window, and a "persistence touched in window" function that returns 4698/4697/4670 in the same shape. Put both in a shared module and every analyst's first hour gets shorter.

For high-volume environments, ship the queries to your SIEM and parameterise — but keep the local PowerShell version in the runbook. SIEM outages happen during incidents more often than statistics would suggest, and the local query never depends on a working network connection.

Building event-log forensics into daily operations

Event log queries are most valuable during an active incident, but they atrophy if they only run during incidents. The teams that respond well to incidents at 2 a.m. have practiced the queries enough during business hours that the syntax is reflex. Three operational habits build that reflex without adding meaningful workload.

The first habit is a shared query library. Every analyst's hard-won query — the one that finally surfaced the lateral movement, the one that filtered out the noisy service account — belongs in a shared module that the next analyst can import and run. Name them descriptively: Get-LateralLogon, Get-SuspiciousProcessTree, Find-PersistenceTouched. The library doubles as documentation: a new team member who reads it learns the team's investigative patterns in an hour.

The second is weekly threat-hunting hours. Block two hours every week for proactive hunting against fresh data. Pick a different technique each week — credential access, persistence, defence evasion — and run the matching queries against a sample of the fleet. Most weeks turn up nothing; the value is the practice, the familiarity with normal-vs-anomalous in your environment, and the occasional finding that would otherwise have waited for a real incident.

The third is retention discipline. The most painful incident-response moment is needing event 4624 from 90 days ago and discovering the log rolled over after 14. Set explicit retention targets — 30 days hot in the SIEM, 12 months cold in object storage — and verify monthly that both targets are met. The verification is a single query; the cost of skipping it is measured in cases that cannot be solved.

Couple the operational habits with a single metric: mean time to first credible lead from incident open to first concrete indicator. Track it across cases, share the trend monthly, and celebrate decreases. The metric forces the practice — teams that get faster invest in the queries, the library, and the retention; teams that do not, do not. Both outcomes are visible, and the choice between them becomes a conscious one rather than the result of drift.

Forensic event-log analysis is not magic. It is a small, learnable set of queries practiced often enough to be useful when it matters. The investment is hours, the return is incidents resolved in hours instead of days.

FAQ

Where do these events live on a DC?

Security log on the DC for 4624, 4625, 4740. Workstation/server System log for 7045.

What about multi-host queries?

Get-WinEvent -ComputerName host1, host2 ... works. WEF + a SIEM is the right answer at scale.

Can I export to CSV easily?

Yes — pipe to Export-Csv or Out-GridView for ad-hoc filtering.

How far back can I query?

As far as the log retention allows. Default Security log on a Windows server is 128 MB which holds a few days on a busy box. Raise to 1 GB and ship to a SIEM.

Can I run these queries against multiple machines at once?

Yes — wrap with Invoke-Command -ComputerName $servers -ScriptBlock { ... }. For more than 50 hosts, ship to a central log store and query there instead.

What about Defender events?

The Microsoft-Windows-Windows Defender/Operational log carries detection events. Combine with 4688 and 4624 for the full picture of what was attempted and what blocked it.

How do I export results for the IR ticket?

Export-Csv -NoTypeInformation for tabular output, or ConvertTo-Json -Depth 5 | Out-File for richer payloads. Attach both to the ticket so the next analyst has the raw and the summary.

Related Dargslan resources

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.