Tracing the Source of Account Lockouts

As an Active Directory administrator, you have no doubt experienced re-occurring account lockouts. Back in the day, you would need the investigative powers of a Mr Sherlock Holmes to get to the bottom of these little mysteries! Then, the Account Lockout Tools made the process somewhat easier. Now, though, we have the magnificence of PowerShell...

From Windows Server 2008 onwards, an account lockout event will register on the PDCe as event ID 4740 - 'a user account was locked out' - with the lockout source included. All we then need to do is collect these events!

Here's the sample code:

#Obtain user

$User = Read-Host -Prompt "Please enter a user name"

#Specify PDCe

$PDC = Get-ADDomainController -Discover -Service PrimaryDC

#Collect lockout events for user from last hour

Get-WinEvent -ComputerName $PDC `

-Logname Security `

-FilterXPath "*[System[EventID=4740 and TimeCreated[timediff(@SystemTime) <= 3600000]] and EventData[Data[@Name='TargetUserName']='$User']]" |

Select-Object TimeCreated,@{Name='User Name';Expression={$_.Properties[0].Value}},@{Name='Source Host';Expression={$_.Properties[1].Value}}


The 'Obtain user' section uses Read-Host to prompt the operator for a user account that is locked out. The supplied name is then stored in $User for later use.

The 'Specify PDCe' section uses the Get-ADDomainController cmdlet, with its -Discover parameter, to locate the domain's PDCe. The domain controller found by the dclocator process is then stored in $PDC.

The final section moves up a gear or two. The Get-WinEvent cmdlet connects to the PDCe and looks at the Security log. A filter is then applied, using the XPath language. This language lets you do some really cool stuff with XML documents (from Windows Server 2008 onwards, events are stored as XML).

Let's break the expression down. We're first going to join and match two conditions from the 'System' node within each XML entry:

  • the first condition is easy, 'EventID=4740' - this matches any 4740 events
  • the second makes sure we collect events from the last hour - 'TimeCreated[timediff(@SystemTime) <= 3600000]

Next, we join the first two conditions to a third matched condition from the 'EventData node':

  • EventData[Data[@Name='TargetUserName']='$User'] - we look 'TargetUserName' and make sure it matches our supplied user name, stored in $User

Once we've matched all three conditions from the XPath expression, we pass any resultant, filtered objects on to the Select-Object cmdlet. Here, the time the event was created is displayed along with two custom headers. Let's look at one of them in more detail:

@{Name='User Name';Expression={$_.Properties[0].Value}}

Here, we create a header called 'User Name' and populate it with the first element from an array of the event's properties. The next Select-Object expression does something very similar, creating a header called 'Source Host' and populating it with the second element from the 'Properties' array.

Here's some sample output:



Again, PowerShell makes life's little administrative tasks much, much easier!

Finally, with the source host identified, experience tells me to look for stale RDP sessions, mapped drives, schedules tasks, etc., etc...