Tag Archives: locked out

Finding the Source of Active Directory Account Lockouts with PowerShell

Finding the Source of Active Directory Account Lockouts with PowerShell

As organizations continue their journey towards Microsoft 365, Entra ID, and cloud-first identity management, traditional on-premises Active Directory environments are becoming less common. Many organizations today operate in hybrid environments where only a handful of services still rely on their local Active Directory.

Despite this shift, one classic problem hasn’t disappeared:

Users still get locked out.

And when they do, finding why and where the lockout originated can still be surprisingly frustrating.

The Challenge

Account lockouts are typically caused by:

  • Stale credentials stored in Windows Credential Manager
  • Scheduled tasks running with old passwords
  • Windows services using outdated service accounts
  • Mapped drives
  • Mobile devices
  • Legacy applications
  • Cached credentials on remote servers

Although Active Directory records lockout events, they’re stored in the Security log on the Domain Controller that processed the lockout. In environments with multiple Domain Controllers, tracking down the relevant event often means logging into several servers and searching manually.

That’s exactly the problem this PowerShell script aims to solve.

What the Script Does

Instead of checking each Domain Controller individually, the script:

  1. Prompts for a user’s sAMAccountName.
  2. Lets you define how far back in time to search (default is 24 hours).
  3. Automatically discovers every Domain Controller in the domain.
  4. Queries the Security log on each Domain Controller for Event ID 4740 (Account Lockout).
  5. Parses the event XML to extract the important fields.
  6. Displays all matching lockout events in a single consolidated table.
  7. Optionally exports the results to CSV for documentation or further investigation.

The result is a quick overview showing:

  • When the lockout happened
  • Which Domain Controller processed it
  • Which account was locked
  • The computer that triggered the lockout
  • The domain involved
  • Which security principal recorded the event

How It Works

The script leverages the Active Directory PowerShell module to discover the current domain and all available Domain Controllers.

For each Domain Controller, it remotely queries the Windows Security event log using:

Get-WinEvent

filtered for:

  • Security Log
  • Event ID 4740
  • Configurable time window

Rather than relying on localized event messages, the script converts each event to XML and extracts the structured EventData fields. This makes the script language-independent and significantly more reliable across different Windows installations.

Only events where the TargetUserName matches the supplied account are returned.

If a Domain Controller cannot be contacted or the Security log cannot be read, the script continues processing the remaining servers and reports the error instead of terminating.

Why This Approach?

Many existing lockout troubleshooting scripts rely on querying a single Domain Controller or using the lockoutstatus.exe utility.

While those tools certainly have their place, they often don’t immediately tell you which device actually caused the lockout.

By reading Event ID 4740 directly from every Domain Controller, this script provides the information administrators usually need first:

Which computer is repeatedly locking out my user?

In many cases, that single piece of information dramatically reduces troubleshooting time.

Example Output

The resulting output is intentionally simple:

TimeDomain ControllerLocked AccountCaller Computer
2026-07-10 08:14DC01jsmithLAPTOP-123
2026-07-10 08:14DC02jsmithLAPTOP-123

Optionally, the data can also be exported to CSV for reporting or attaching to support cases.

Final Thoughts

Even as more organizations embrace cloud identity, hybrid Active Directory environments will likely remain with us for years to come. And as long as on-premises Active Directory exists, account lockouts will continue to generate support tickets.

Having a simple script that automatically searches every Domain Controller and pinpoints the originating device can save valuable troubleshooting time and eliminate much of the guesswork.

Sometimes, the simplest tools end up being the ones you reach for the most.

#Need to run on a DC's, or a machine with PowerShell module ActiveDirectory installed.
#The concurrent user, need access to security logs on each domain conntroller from where the script is running.

Import-Module ActiveDirectory -ErrorAction Stop

$AccountName = Read-Host "Enter the AD account name / sAMAccountName"
$HoursBack = Read-Host "How many hours back would you like to search? Default is 24"

if ([string]::IsNullOrWhiteSpace($HoursBack)) {
    $HoursBack = 24
}

$StartTime = (Get-Date).AddHours(-[int]$HoursBack)

$Domain = Get-ADDomain
$DCs = Get-ADDomainController -Filter * | Sort-Object HostName

Write-Host ""
Write-Host "Domain: $($Domain.DNSRoot)"
Write-Host "Searching for lockout events for '$AccountName' since $StartTime"
Write-Host "Domain Controllers found: $($DCs.Count)"
Write-Host ""

$Results = foreach ($DC in $DCs) {
    Write-Host "Checking $($DC.HostName)..."

    try {
        $Events = Get-WinEvent -ComputerName $DC.HostName -FilterHashtable @{
            LogName   = 'Security'
            Id        = 4740
            StartTime = $StartTime
        } -ErrorAction Stop

        foreach ($Event in $Events) {
            $Xml = [xml]$Event.ToXml()

            $EventData = @{}
            foreach ($Data in $Xml.Event.EventData.Data) {
                $EventData[$Data.Name] = $Data.'#text'
            }

            if ($EventData['TargetUserName'] -ieq $AccountName) {
                [PSCustomObject]@{
                    TimeCreated        = $Event.TimeCreated
                    DomainController   = $DC.HostName
                    LockedOutAccount   = $EventData['TargetUserName']
                    CallerComputerName = $EventData['CallerComputerName']
                    TargetDomainName   = $EventData['TargetDomainName']
                    SubjectUserName    = $EventData['SubjectUserName']
                    EventId            = $Event.Id
                }
            }
        }
    }
    catch {
        if ($_.Exception.Message -notlike "*No events were found*") {
            [PSCustomObject]@{
                TimeCreated        = $null
                DomainController   = $DC.HostName
                LockedOutAccount   = $AccountName
                CallerComputerName = "Unable to read the Security log"
                TargetDomainName   = $null
                SubjectUserName    = $_.Exception.Message
                EventId            = $null
            }
        }
    }
}

Write-Host ""
Write-Host "Results:"
Write-Host ""

$Results |
    Sort-Object TimeCreated -Descending |
    Format-Table -AutoSize

$Export = Read-Host "Would you like to export the results to CSV? (Y/N)"

if ($Export -match '^[Yy]') {
    $Path = ".\AD-Lockout-$AccountName-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv"

    $Results |
        Sort-Object TimeCreated -Descending |
        Export-Csv -Path $Path -NoTypeInformation -Encoding UTF8

    Write-Host "Results exported to: $Path"
}