PowerShell – Active Directory Account Lockout Reporter

The following script needs to be run on a DC with Domain Admin priveleges . This will send a detailed report of repeated account lockouts

<#
.SYNOPSIS
    Generates a report of currently locked user accounts in Active Directory, along with the computers that caused the lockouts.
.DESCRIPTION
    This script queries locked out users, parses relevant security events, and outputs clean HTML/CSV reports.
.PARAMETER TempPath
    Path for local temp storage.
.PARAMETER SharedPath
    Path for shared report storage.
.PARAMETER LookbackMilliseconds
    Time window (in ms) for event lookup.
#>
param (
    [string]$TempPath = "C:\temp",
    [string]$SharedPath = "\\Sharedpath\AccountLockout",
    [int]$LookbackMilliseconds = 4233600000 # 7 weeks
)

# --- Function Definitions ---

function Remove-ExistingFile {
    param ([string[]]$Files)
    foreach ($f in $Files) {
        if (Test-Path $f) { 
            Remove-Item $f -ErrorAction SilentlyContinue 
        }
    }
}

function Write-ProgressInfo {
    param($msg, $color="Yellow")
    Write-Host $msg -ForegroundColor $color
}

function Send-ReportEmail {
    param($from, $to, $subject, $body, $attachments, $smtp)
    Try {
        Send-MailMessage -From $from -To $to -Subject $subject -Body $body -Attachments $attachments -SmtpServer $smtp
        Write-ProgressInfo "Email sent to $to" Green
    } Catch {
        Write-ProgressInfo "Failed to send email: $_" Red
    }
}

# --- Variables and File Paths ---

$StartDate = Get-Date

$filename1 = "$TempPath\List_of_locked_users.txt"
$filename2 = "$TempPath\Computers_Causing_locked_users.csv"
$filename3 = "$SharedPath\Computers_Causing_locked_users.csv"
$filename4 = "$TempPath\sorted.csv"
$filename5 = "$TempPath\Computers_Causing_Lockouts.html"
$filename6 = "$SharedPath\Computers_Causing_Lockouts.html"
$filename7 = "$SharedPath\List_of_locked_users.txt"

Remove-ExistingFile -Files @($filename1, $filename2, $filename4)

# --- Discover PDC and Locked Users ---
$PDC = Get-ADDomainController -Discover -Service PrimaryDC
$LockedUsers = Search-ADAccount -LockedOut | Select-Object -ExpandProperty Name
$LockedUsers | Out-File $filename1
$UserCount = $LockedUsers.Count

Write-ProgressInfo "Current List of locked out users:" Red
$LockedUsers
Write-ProgressInfo "There are $UserCount accounts locked out." Red
Write-ProgressInfo "...Now checking which computers caused the lockouts....." Red

# --- Query Event Log per User & Export ---
$pass = 1
foreach ($User in $LockedUsers) {
    Write-ProgressInfo "Checking account: $User ($pass of $UserCount)" Blue
    Try {
        Get-WinEvent -ComputerName $PDC.Name -Logname Security `
            -FilterXPath "*[System[EventID=4740 and TimeCreated[timediff(@SystemTime) <= $LookbackMilliseconds]] and EventData[Data[@Name='TargetUserName']='$User']]" `
            -ErrorAction Stop |
            Select-Object TimeCreated,@{Name='User Name';Expression={ $_.Properties[0].Value }},@{Name='Source Host';Expression={ $_.Properties[1].Value }} |
            Export-Csv -Path $filename2 -Append -NoTypeInformation -Force
    } Catch {
        Write-ProgressInfo "Error processing user $User: $_" Red
    }
    $pass++
}

$EndDate = Get-Date
$duration = New-TimeSpan -Start $StartDate -End $EndDate
$duration2 = [math]::Round($duration.TotalMinutes,2)

# --- Clean HTML Output (CSS) ---

$htmlformat = @'
<title>Computers Causing Lockouts</title>
<style>
body { background: #f3f4f6; color: #22223b; font-family: "Segoe UI", Arial, sans-serif; font-size: 16px; margin: 20px;}
h1 { color: #2a394f; border-bottom: 2px solid #c9d6e3; padding-bottom: 8px; }
table { border-collapse: collapse; width: 100%; background: #fff; margin-top: 20px;}
th, td { border: 1px solid #e1e5ee; padding: 10px; text-align: left;}
th { background: #eaf0fa;}
tr:nth-child(even) td { background: #f8f8fc;}
</style>
'@
$bodyformat = '<h1>Computers Causing Lockouts: Sorted by User Name</h1>'

# --- Sort and Export Reports ---
Try {
    if (Test-Path $filename2) {
        Import-Csv -Path $filename2 | Sort-Object "User Name" | Export-Csv -Path $filename4 -NoTypeInformation
        Import-Csv -Path $filename4 | ConvertTo-Html -Head $htmlformat -Body $bodyformat | Out-File $filename5 -Force
    } else {
        Write-ProgressInfo "No event data exported, $filename2 missing." Yellow
    }
} Catch {
    Write-ProgressInfo "Error generating HTML report: $_" Red
}

# --- Clean-up Old Reports on File Share ---
Remove-ExistingFile -Files @($filename3, $filename6, $filename7)

# --- Copy Results to Shared Path ---
Copy-Item $filename2 $filename3 -Force
Copy-Item $filename5 $filename6 -Force
Copy-Item $filename1 $filename7 -Force

# --- Compose and Send Email Summary ---
$From = "shankar.karanth@karanth.ovh"
$To = "DomainAdmins@karanth.ovh"
$Sub = "User lockout report"
$Body = @"
There are $UserCount accounts locked out at this time.
This report was generated via a scheduled task on $env:COMPUTERNAME
It started at $StartDate and took $duration2 minutes to run.

Please refer to the following attachments:
1. A list of locked out accounts
2. CSV and HTML reports showing which computers were recorded as causing account lockouts.

Lockout Reports can be accessed from:
$filename3
$filename6
$filename7

NOTE:
The Computers_Causing_locked_users report only shows users who were locked out if they were noted in the Event Viewer during the configured time frame --AND-- only lists computers if they are noted in the Event Viewer of the PDC.
The CSV and HTML reports are therefore, not necessarily all inclusive.
"@
$SMTP = "mail@karanth.ovh"

Send-ReportEmail -from $From -to $To -subject $Sub -body $Body -attachments @($filename1, $filename2, $filename5) -smtp $SMTP

Write-ProgressInfo "Report generation complete. Duration: $duration2 minutes." Green
K Shankar R Karanth
K Shankar R Karanth
Articles: 4