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:

  1. Run LockoutStatus.exe as an administrator.
  2. Enter the affected username.
  3. 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 IDDescription
4625Failed logon attempt
4740Account locked out (on DC)
4771Kerberos pre-authentication failed (often due to bad password)
4776NTLM 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