Public/Get-AccountLockoutReport.ps1
|
function Get-AccountLockoutReport { <# .SYNOPSIS Reports currently locked Active Directory accounts and the computers that caused the lockouts. .DESCRIPTION Queries all locked out accounts using Search-ADAccount, then parses Security Event ID 4740 on the PDC Emulator to identify which computers triggered each lockout. Outputs three report formats: - Plain text list of locked accounts - CSV with lockout source computers per user - Colour-coded HTML report sorted by username All reports are copied to a shared path and optionally emailed to a distribution group. .PARAMETER TempPath Local path for temporary file storage during report generation. Defaults to $env:TEMP. .PARAMETER SharedPath Network share path where final reports are written. Defaults to \\server\AccountLockout. .PARAMETER LookbackMilliseconds How far back to search event logs in milliseconds. Default is 4233600000 (7 weeks). .EXAMPLE Get-AccountLockoutReport Runs with default paths and 7-week lookback. .EXAMPLE Get-AccountLockoutReport -TempPath "D:\temp" -SharedPath "\\fileserver\Reports\Lockouts" -LookbackMilliseconds 86400000 Runs with custom paths and a 24-hour lookback window. .NOTES Author: K Shankar R Karanth Website: https://karanth.ovh Version: 1.0 Run as Domain Admin or equivalent with read access to the Security event log on the PDC Emulator. #> [CmdletBinding()] param ( [string]$TempPath = 'C:\ADOpsKit\Reports\Get-AccountLockoutReport', [string]$SharedPath = 'C:\ADOpsKit\Reports\Get-AccountLockoutReport', [long]$LookbackMilliseconds = 4233600000 ) # ============ FUNCTIONS ============ function Remove-ExistingFile { param([string[]]$Files) foreach ($f in $Files) { if (Test-Path $f) { Remove-Item $f -ErrorAction SilentlyContinue } } } function Write-ProgressInfo { param( [string]$Message, [string]$Color = 'Yellow' ) Write-Host $Message -ForegroundColor $Color } function Send-ReportEmail { param( [string]$From, [string]$To, [string]$Subject, [string]$Body, [string[]]$Attachments, [string]$SmtpServer ) try { Send-MailMessage -From $From -To $To -Subject $Subject ` -Body $Body -Attachments $Attachments -SmtpServer $SmtpServer Write-ProgressInfo "Email sent to $To" -Color Green } catch { Write-ProgressInfo "Failed to send email: $_" -Color Red } } # ============ EMAIL CONFIGURATION ============ # Update these before enabling email reporting # $emailFrom = 'ad.monitoring@example.com' # $emailTo = 'domainadmins@example.com' # $emailSmtp = 'smtp.example.com' # ============ FILE PATHS ============ if (-not (Test-Path $TempPath)) { New-Item -ItemType Directory -Path $TempPath -Force | Out-Null } $datePrefix = Get-Date -Format 'yyyy-MM-dd' $fileLockList = "$TempPath\${datePrefix}_List_of_locked_users.txt" $fileLockCsvTemp = "$TempPath\${datePrefix}_Computers_Causing_locked_users.csv" $fileLockCsvSort = "$TempPath\${datePrefix}_sorted.csv" $fileLockHtml = "$TempPath\${datePrefix}_Computers_Causing_Lockouts.html" $shareLocklist = "$SharedPath\${datePrefix}_List_of_locked_users.txt" $shareLockCsv = "$SharedPath\${datePrefix}_Computers_Causing_locked_users.csv" $shareLockHtml = "$SharedPath\${datePrefix}_Computers_Causing_Lockouts.html" Remove-ExistingFile -Files @($fileLockList, $fileLockCsvTemp, $fileLockCsvSort) # ============ DISCOVER PDC AND LOCKED USERS ============ $startDate = Get-Date $pdc = Get-ADDomainController -Discover -Service PrimaryDC $lockedUsers = Search-ADAccount -LockedOut | Select-Object -ExpandProperty Name $userCount = $lockedUsers.Count $lockedUsers | Out-File $fileLockList Write-ProgressInfo "Locked out accounts found: $userCount" -Color Red Write-ProgressInfo "Querying PDC $($pdc.Name) for lockout source computers..." -Color Yellow # ============ QUERY EVENT ID 4740 PER USER ============ $pass = 1 foreach ($user in $lockedUsers) { Write-ProgressInfo "Processing: $user ($pass of $userCount)" -Color Blue try { $xPath = "*[System[EventID=4740 and TimeCreated[timediff(@SystemTime) <= $LookbackMilliseconds]]" + " and EventData[Data[@Name='TargetUserName']='$user']]" Get-WinEvent -ComputerName $pdc.Name -LogName Security ` -FilterXPath $xPath -ErrorAction Stop | Select-Object TimeCreated, @{ Name = 'User Name'; Expression = { $_.Properties[0].Value } }, @{ Name = 'Source Host'; Expression = { $_.Properties[1].Value } } | Export-Csv -Path $fileLockCsvTemp -Append -NoTypeInformation -Force } catch { Write-ProgressInfo "Error processing $user : $_" -Color Red } $pass++ } $endDate = Get-Date $duration = [math]::Round((New-TimeSpan -Start $startDate -End $endDate).TotalMinutes, 2) # ============ BUILD HTML REPORT ============ $htmlHead = @' <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> '@ $htmlBody = '<h1>Computers Causing Lockouts — Sorted by User Name</h1>' # ============ SORT CSV AND WRITE REPORTS ============ try { if (Test-Path $fileLockCsvTemp) { Import-Csv -Path $fileLockCsvTemp | Sort-Object 'User Name' | Export-Csv -Path $fileLockCsvSort -NoTypeInformation Import-Csv -Path $fileLockCsvSort | ConvertTo-Html -Head $htmlHead -Body $htmlBody | Out-File $fileLockHtml -Force } else { Write-ProgressInfo "No event data found - CSV not generated." -Color Yellow } } catch { Write-ProgressInfo "Error generating HTML report: $_" -Color Red } # ============ COPY TO SHARED PATH ============ Remove-ExistingFile -Files @($shareLocklist, $shareLockCsv, $shareLockHtml) if (Test-Path $fileLockCsvTemp) { Copy-Item $fileLockCsvTemp $shareLockCsv -Force } if (Test-Path $fileLockHtml) { Copy-Item $fileLockHtml $shareLockHtml -Force } if (Test-Path $fileLockList) { Copy-Item $fileLockList $shareLocklist -Force } Write-ProgressInfo "Reports written to $SharedPath" -Color Green # ============ EMAIL SUMMARY (uncomment to enable) ============ # $emailSubject = "User Lockout Report - $userCount account(s) locked" # $emailBody = "There are $userCount account(s) locked out at this time.`n" + # "Generated by scheduled task on $env:COMPUTERNAME`n" + # "Started: $startDate - Duration: $duration minutes`n`n" + # "Attachments:`n" + # "1. List of locked accounts`n" + # "2. CSV report of lockout source computers`n" + # "3. HTML report of lockout source computers`n`n" + # "Reports also available at:`n$shareLockCsv`n$shareLockHtml`n$shareLocklist`n`n" + # "NOTE: Only accounts with Event ID 4740 recorded on the PDC Emulator within" + # " the lookback window are included." # Send-ReportEmail -From $emailFrom -To $emailTo -Subject $emailSubject ` # -Body $emailBody -Attachments @($fileLockList, $fileLockCsvTemp, $fileLockHtml) ` # -SmtpServer $emailSmtp Write-ProgressInfo "Complete. Duration: $duration minutes." -Color Green } |