Locked Accounts in Active Directory
Finding the root cause of locked accounts in Active Directory (AD) is a common but sometimes tricky issue for Windows administrators. Below is a detailed, step-by-step troubleshooting guide that will help you systematically identify why accounts are locking out and where the lockouts are coming from.
1. Confirm the symptoms :
- Which users are being locked out?
- When did it start happening?
- Is it happening repeadly or intermittently?
- Is it always from the same machine, subnet, or location?
You can use Active Directory Users and Computers (ADUC) or PowerShell to check the user’s lockout status.
Search-ADAccount -LockedOut | Select-Object Name, SamAccountName, LastLogonDate
2. Use Microsoft’s “Account Lockout and Management Tools”
Microsoft provides a toolkit that includes LockoutStatus.exe (LockoutStatus tool).
https://www.microsoft.com/en-us/download/details.aspx?id=15201
How to use:
- Run
LockoutStatus.exeas an administrator. - Enter the affected username.
- It will show all domain controllers (DCs) and indicate which one recorded the last bad password or lockout event.
The “Originating DC” is your best clue — this is the DC where the bad password attempt originated.
3. Check Event Viewer Logs
Now, go to the originating domain controller identified in Step 2.
Look under:
Event Viewer → Windows Logs → Security
Filter for these Event IDs:
| Event ID | Description |
|---|---|
| 4625 | Failed logon attempt |
| 4740 | Account locked out (on DC) |
| 4771 | Kerberos pre-authentication failed (often due to bad password) |
| 4776 | NTLM Authentication failure |
Event ID 4740
- Source: Microsoft-Windows-Security-Auditing
- Details include:
- TargetUserName → affected account
- CallerComputerName → where the bad logon originated (This CallerComputerName is often the root cause)
4. Identify the Source
Once you have the Caller Computer Name, check that system for possible causes:
- Mapped drives or network shares using old credentials.
- Scheduled tasks running under the user’s account.
- Windows services configured with the user’s credentials.
- Persistent sessions (e.g., RDP, Outlook, Teams) on other devices.
- Mobile devices (ActiveSync / Outlook app) with cached passwords.
- Applications using stored credentials (e.g., browsers, SQL Management Studio).
- Group Policy scripts or logon scripts.
Check Scheduled Tasks on the suspected server/workstation:
Get-ScheduledTask | Where-Object {$_.Principal.UserId -like "username"}
Check stored credentials on the suspected server/workstation:
cmdkey /list
<#
.SYNOPSIS
Collects and summarizes all account lockout-related events for a specified user across all domain controllers.
.DESCRIPTION
The script queries all DCs for Security Event IDs 4740 (Account Locked Out),
4625 (Failed Logon), and 4771 (Kerberos Pre-Auth Failures) related to a given user.
Results are summarized by timestamp, DC name, event type, and source computer.
.PARAMETER UserName
The SamAccountName of the user to investigate.
.EXAMPLE
.\Get-ADLockoutSummary.ps1 -UserName jsmith
#>
param(
[Parameter(Mandatory = $true)]
[string]$UserName
)
# Ensure the ActiveDirectory module is loaded
Import-Module ActiveDirectory -ErrorAction Stop
Write-Host "Collecting domain controllers..." -ForegroundColor Cyan
$DCs = Get-ADDomainController -Filter *
$Results = @()
Write-Host "Searching for lockout events for user '$UserName'..." -ForegroundColor Cyan
foreach ($DC in $DCs) {
Write-Host "Querying DC: $($DC.HostName)" -ForegroundColor Yellow
try {
$events = Get-WinEvent -ComputerName $DC.HostName -FilterHashtable @{
LogName = 'Security'
ID = 4740, 4625, 4771
StartTime = (Get-Date).AddDays(-7) # Adjust lookback window here
} -ErrorAction Stop | Where-Object { $_.Message -match $UserName }
foreach ($event in $events) {
$Message = $event.Message
$CallerComputer = if ($Message -match 'Caller Computer Name:\s+([^\r\n]+)') { $matches[1].Trim() } else { 'N/A' }
$IpAddress = if ($Message -match 'Source Network Address:\s+([^\r\n]+)') { $matches[1].Trim() } else { 'N/A' }
$Results += [PSCustomObject]@{
TimeCreated = $event.TimeCreated
DC = $DC.HostName
EventID = $event.Id
EventType = switch ($event.Id) {
4740 { "Account Locked Out" }
4625 { "Failed Logon" }
4771 { "Kerberos Pre-Auth Failed" }
default { "Other" }
}
CallerComputer = $CallerComputer
SourceIP = $IpAddress
}
}
}
catch {
Write-Warning "Could not query $($DC.HostName): $_"
}
}
if ($Results.Count -eq 0) {
Write-Host "No lockout-related events found for $UserName in the last 7 days." -ForegroundColor Red
return
}
Write-Host "Summarizing results..." -ForegroundColor Cyan
$Results |
Sort-Object TimeCreated |
Select-Object TimeCreated, DC, EventType, CallerComputer, SourceIP |
Format-Table -AutoSize
# Optional: Export to CSV for audit
$CsvPath = "C:\Temp\LockoutReport-$UserName-$(Get-Date -Format 'yyyyMMdd_HHmm').csv"
$Results | Export-Csv -Path $CsvPath -NoTypeInformation -Encoding UTF8
Write-Host "`nDetailed report saved to: $CsvPath" -ForegroundColor Green
<#
.SYNOPSIS
Collects, correlates, and summarizes all account lockout-related events across domain controllers.
.DESCRIPTION
Queries all DCs for lockout-related Security Event IDs (4740, 4625, 4771) for a specified user,
then orders results by timestamp to show the replication chain — identifying the *originating DC*
and probable source computer or IP address.
.EXAMPLE
.\Get-ADLockoutChain.ps1 -UserName jsmith -Days 5
#>
param(
[Parameter(Mandatory = $true)]
[string]$UserName,
[int]$Days = 7
)
# Ensure ActiveDirectory module is available
Import-Module ActiveDirectory -ErrorAction Stop
Write-Host "Collecting domain controllers..." -ForegroundColor Cyan
$DCs = Get-ADDomainController -Filter *
$Results = @()
Write-Host "Searching all DCs for user '$UserName' (last $Days days)..." -ForegroundColor Cyan
foreach ($DC in $DCs) {
Write-Host "Querying DC: $($DC.HostName)" -ForegroundColor Yellow
try {
$events = Get-WinEvent -ComputerName $DC.HostName -FilterHashtable @{
LogName = 'Security'
ID = 4740, 4625, 4771
StartTime = (Get-Date).AddDays(-$Days)
} -ErrorAction Stop | Where-Object { $_.Message -match $UserName }
foreach ($event in $events) {
$Message = $event.Message
$CallerComputer = if ($Message -match 'Caller Computer Name:\s+([^\r\n]+)') { $matches[1].Trim() } else { 'N/A' }
$IpAddress = if ($Message -match 'Source Network Address:\s+([^\r\n]+)') { $matches[1].Trim() } else { 'N/A' }
$Results += [PSCustomObject]@{
TimeCreated = $event.TimeCreated
DC = $DC.HostName
EventID = $event.Id
EventType = switch ($event.Id) {
4740 { "Account Locked Out" }
4625 { "Failed Logon" }
4771 { "Kerberos Pre-Auth Failed" }
default { "Other" }
}
CallerComputer = $CallerComputer
SourceIP = $IpAddress
}
}
}
catch {
Write-Warning "Could not query $($DC.HostName): $_"
}
}
if ($Results.Count -eq 0) {
Write-Host "No lockout-related events found for $UserName in the last $Days days." -ForegroundColor Red
return
}
Write-Host "`nCorrelating lockout chain..." -ForegroundColor Cyan
$SortedResults = $Results | Sort-Object TimeCreated
$FirstEvent = $SortedResults | Where-Object { $_.EventID -eq 4740 } | Select-Object -First 1
if ($null -ne $FirstEvent) {
Write-Host "`n🧩 Lockout Chain Summary for user: $UserName" -ForegroundColor Green
Write-Host "------------------------------------------------------"
Write-Host " Originating DC : $($FirstEvent.DC)"
Write-Host " Lockout Time : $($FirstEvent.TimeCreated)"
Write-Host " Source Machine : $($FirstEvent.CallerComputer)"
Write-Host " Source IP : $($FirstEvent.SourceIP)"
Write-Host "------------------------------------------------------"
} else {
Write-Host "No Event ID 4740 found — unable to determine origin DC." -ForegroundColor Yellow
}
Write-Host "`n🔎 Full Lockout Timeline (replication chain):" -ForegroundColor Cyan
$SortedResults |
Sort-Object TimeCreated |
Select-Object TimeCreated, DC, EventType, CallerComputer, SourceIP |
Format-Table -AutoSize
# Optional: Export detailed results to CSV
$CsvPath = "C:\Temp\LockoutChain_$UserName_$(Get-Date -Format 'yyyyMMdd_HHmm').csv"
$SortedResults | Export-Csv -Path $CsvPath -NoTypeInformation -Encoding UTF8
Write-Host "`nDetailed timeline exported to: $CsvPath" -ForegroundColor Green
<#
.SYNOPSIS
Correlates account lockouts across DCs, resolves source IP/host,
checks the suspect machine for scheduled tasks or services using the account,
and classifies each failed logon by logon type.
.EXAMPLE
.\Get-ADLockoutRootCause.ps1 -UserName jsmith -Days 7
#>
param(
[Parameter(Mandatory = $true)]
[string]$UserName,
[int]$Days = 7
)
Import-Module ActiveDirectory -ErrorAction Stop
function Get-LogonTypeName {
param([int]$Type)
switch ($Type) {
2 { "Interactive (console/local logon)" }
3 { "Network (SMB/mapped drive)" }
4 { "Batch (scheduled task)" }
5 { "Service (service account)" }
7 { "Unlock (workstation unlock)" }
8 { "NetworkCleartext (IIS, Basic Auth)" }
9 { "NewCredentials (runas /netonly)" }
10 { "RemoteInteractive (RDP/TS)" }
11 { "CachedInteractive (offline logon)" }
default { "Other / Unknown" }
}
}
Write-Host "Collecting domain controllers..." -ForegroundColor Cyan
$DCs = Get-ADDomainController -Filter *
$Results = @()
Write-Host "Searching all DCs for user '$UserName' (last $Days days)..." -ForegroundColor Cyan
foreach ($DC in $DCs) {
Write-Host "Querying DC: $($DC.HostName)" -ForegroundColor Yellow
try {
$events = Get-WinEvent -ComputerName $DC.HostName -FilterHashtable @{
LogName = 'Security'
ID = 4740,4625,4771
StartTime = (Get-Date).AddDays(-$Days)
} -ErrorAction Stop | Where-Object { $_.Message -match $UserName }
foreach ($event in $events) {
$msg = $event.Message
$CallerComputer = if ($msg -match 'Caller Computer Name:\s+([^\r\n]+)') { $matches[1].Trim() } else { 'N/A' }
$IpAddress = if ($msg -match 'Source Network Address:\s+([^\r\n]+)') { $matches[1].Trim() } else { 'N/A' }
$LogonTypeNum = if ($msg -match 'Logon Type:\s+(\d+)') { [int]$matches[1] } else { $null }
$LogonTypeName = if ($LogonTypeNum) { Get-LogonTypeName $LogonTypeNum } else { 'N/A' }
$Results += [PSCustomObject]@{
TimeCreated = $event.TimeCreated
DC = $DC.HostName
EventID = $event.Id
EventType = switch ($event.Id) {
4740 { "Account Locked Out" }
4625 { "Failed Logon" }
4771 { "Kerberos Pre-Auth Failed" }
default { "Other" }
}
CallerComputer = $CallerComputer
SourceIP = $IpAddress
LogonTypeNum = $LogonTypeNum
LogonType = $LogonTypeName
}
}
} catch {
Write-Warning "Could not query $($DC.HostName): $_"
}
}
if (-not $Results) {
Write-Host "No lockout-related events found for $UserName." -ForegroundColor Red
return
}
# --- Correlate and summarize ---
$Sorted = $Results | Sort-Object TimeCreated
$First = $Sorted | Where-Object { $_.EventID -eq 4740 } | Select-Object -First 1
if ($First) {
Write-Host "`n🧩 Lockout Summary" -ForegroundColor Green
Write-Host "------------------------------------------------------"
Write-Host "User : $UserName"
Write-Host "Originating DC : $($First.DC)"
Write-Host "Lockout Time : $($First.TimeCreated)"
Write-Host "Source Machine : $($First.CallerComputer)"
Write-Host "Source IP : $($First.SourceIP)"
Write-Host "------------------------------------------------------"
}
# --- Resolve IP to hostname ---
$SourceHost = $null
if ($First.SourceIP -and $First.SourceIP -notmatch "0\.0\.0\.0|::1|::") {
try {
$dns = [System.Net.Dns]::GetHostEntry($First.SourceIP)
$SourceHost = $dns.HostName
Write-Host "`nResolved $($First.SourceIP) → $SourceHost" -ForegroundColor Cyan
} catch {
Write-Warning "Unable to resolve hostname for IP $($First.SourceIP)"
}
}
# --- Check the suspected machine ---
if ($SourceHost) {
Write-Host "`nChecking $SourceHost for tasks/services using $UserName..." -ForegroundColor Cyan
try {
$Tasks = Invoke-Command -ComputerName $SourceHost -ScriptBlock {
Get-ScheduledTask | Where-Object { $_.Principal.UserId -match $using:UserName } |
Select TaskName, State, @{n="Type";e={"ScheduledTask"}}
} -ErrorAction Stop
$Services = Invoke-Command -ComputerName $SourceHost -ScriptBlock {
Get-CimInstance Win32_Service | Where-Object { $_.StartName -match $using:UserName } |
Select Name, State, @{n="Type";e={"Service"}}
} -ErrorAction Stop
$Usage = $Tasks + $Services
if ($Usage) { $Usage | Format-Table -AutoSize }
else { Write-Host "No scheduled tasks or services using $UserName found on $SourceHost." -ForegroundColor Yellow }
} catch {
Write-Warning "Could not query $SourceHost – check WinRM and permissions."
}
}
# --- Display timeline with logon type ---
Write-Host "`n🔎 Full Lockout Timeline (with Logon Type)" -ForegroundColor Cyan
$Sorted | Select TimeCreated, DC, EventType, CallerComputer, SourceIP, LogonType | Format-Table -AutoSize
# --- Export ---
$Csv = "C:\Temp\LockoutRootCause_$UserName_$(Get-Date -Format 'yyyyMMdd_HHmm').csv"
$Sorted | Export-Csv $Csv -NoTypeInformation -Encoding UTF8
Write-Host "`nReport exported to: $Csv" -ForegroundColor Green
