Parses security event 4625 over a lookback window and groups the failures twice: by target account and by source address or workstation. One account failing everywhere is a stale saved credential; one source failing against many accounts is something else entirely, and the two views are what tell them apart.
powershell
# Audit Failed Logons
# Single use case: summarize failed logon attempts (event 4625) of the last N hours
#-----------------------------------------------------------------
function Write-Log {
param($Message)
Write-Host "<WRITE-LOG = `"*$Message*`">"
}
# --- Parameters (replace via ServerEngine API parameters if needed) ---
$LookbackHours = 24
$WarnThreshold = 20 # warn when one account/source exceeds this many failures
$since = (Get-Date).AddHours(-[int]$LookbackHours)
$events = Get-WinEvent -FilterHashtable @{ LogName = "Security"; Id = 4625; StartTime = $since } -ErrorAction SilentlyContinue
if (-not $events) {
Write-Log "No failed logons (4625) in the last $LookbackHours hours."
return
}
Write-Log "Failed logons in the last ${LookbackHours}h: $(@($events).Count)"
$parsed = foreach ($e in $events) {
$xml = [xml]$e.ToXml()
$data = @{}
foreach ($d in $xml.Event.EventData.Data) { $data[$d.Name] = $d.'#text' }
[pscustomobject]@{
Account = "$($data['TargetDomainName'])\$($data['TargetUserName'])"
Source = if ($data['IpAddress'] -and $data['IpAddress'] -ne "-") { $data['IpAddress'] } else { $data['WorkstationName'] }
}
}
Write-Log "================ By Account (top 10) ================"
$parsed | Group-Object Account | Sort-Object Count -Descending | Select-Object -First 10 | ForEach-Object {
$flag = if ($_.Count -ge [int]$WarnThreshold) { "WARNING: " } else { "" }
Write-Log "$flag x$($_.Count.ToString().PadLeft(5)) $($_.Name)"
}
Write-Log "================ By Source (top 10) ================"
$parsed | Group-Object Source | Sort-Object Count -Descending | Select-Object -First 10 | ForEach-Object {
$flag = if ($_.Count -ge [int]$WarnThreshold) { "WARNING: " } else { "" }
Write-Log "$flag x$($_.Count.ToString().PadLeft(5)) $($_.Name)"
}
Write-Log "Failed logon audit complete. Sources above $WarnThreshold attempts may indicate brute force."Run it across your fleet
This script runs as-is on a single host. Paste it into ServerEngine to schedule it, run it on a whole server group in parallel, and keep the credentials out of the file — see the scripts documentation and the credential store.
More in Security & Auditing
Enable the Windows Firewall with PowerShell
2026-08-17Check Windows Firewall Status with PowerShell
2026-08-17Disable SMBv1 with PowerShell
2026-08-17Enforce TLS 1.2 in the Registry with PowerShell
2026-08-17Enable RDP Network Level Authentication with PowerShell
2026-08-17Audit the Local Administrators Group with PowerShell
2026-08-17Ready when you are.
Try ServerEngine free for 7 days.