Use PowerShell to Find the Location of a Locked-Out User

Summary: Guest blogger and Microsoft PFE Jason Walker talks about using Windows PowerShell to find a locked-out user’s location.

Microsoft Scripting Guy, Ed Wilson, is here. Today, we have a guest blog post written by Microsoft Premier Field Engineer (PFE) Jason Walker. Jason has written a number of extremely popular Hey, Scripting Guy! blog posts and today’s article is no exception.

Finding a locked-out user’s location

Have you ever been asked to unlock a user account, and then five minutes later, asked again to unlock the same account? Or, maybe you have changed the password for a service account, and you’re not sure what server needs the new credentials. Even better, have you ever had a virus outbreak where thousands of accounts were locked out, and you had to find out what machines were producing all the failed logon attempts? I have …

There are basically two ways of troubleshooting locked-out accounts. You can chase the events that are logged when a failed logon occurs. The events that are logged vary depending on the how auditing is configured in your environment. However, an easier way is to wait until the account is locked out.

All failed logon attempts get forwarded to the PDC Emulator (PDC) in the domain. Like I said earlier, the events that get logged depend on how auditing is configured. What is consistent is the event number that gets logged when the account is locked out. In an environment with domain controllers running Windows Server 2008 or later, when an account is locked out, a 4740 event is logged in the Security log on the PDC of your domain. With the 4740 event, the source of the failed logon attempt is documented. Here is an example of this taken from my lab:

 Image of event 4740 properties

In the above example, you can see the user BrWilliams was locked out and the last failed logon attempt came from computer WIN7. So, really all we need to do is write a script that will:

  1. Find the domain controller that holds the PDC role.
  2. Query the Security logs for 4740 events.
  3. Filter those events for the user in question.

Doesn’t sound too bad.

Here is the script in action.

Image of command output

I wrote the script to contact all the domain controllers in the domain to display the LastBadPasswordAttempt timestamp, if present. If there are recent bad password attempts across all domain controllers, it could be a sign of a virus or something on a larger scale. This data isn’t truly needed to find the locked-out location. Additionally, it adds time to the script’s completion because this attribute isn’t replicated. This requires contact with every domain controller. However, I thought it could be helpful in troubleshooting. The second set of information displayed is the 4740s from the PDC for the user in question sorted by the time the event was created in descending order.

Here is an example of how we get all the domain controllers in a domain, and then query the individual domain controllers for a user’s attributes:

$DomainControllers = Get-ADDomainController -Filter *

Foreach($DC in $DomainControllers)


Get-ADUser -Identity brwilliams -Server $DC.Hostname `

-Properties AccountLockoutTime,LastBadPasswordAttempt,BadPwdCount,LockedOut


Notice I used the Properties parameter and specified only the properties I care about. I see admins use –Properties * and it makes me cringe. When you use the wildcard, all the user’s 140+ attributes are sent across the wire. This adds unnecessary time to the script. In a small environment with 3 domain controllers this might not matter that much, but in a larger domain with 15 domain controllers I guarantee you will see a performance degradation.

Now let’s see how to get the 4740s off the PDC Emulator.

By using the Get-WinEvent cmdlet, I easily create a filter that will quickly bring back all the 4740 events.

Image of command output

Now, let’s look closely at one event by piping it to a Format-List.

Image of command output

The Message note property has everything we need to script finding the lock-out location, but the property is a string and will take some coding to get what we need. The hidden gem here is the property name Properties. Let’s take a look.

Image of command output

Here we have the user name, computer name, and SID of the user. Each value being a separate entry in an array. Very cool, and very easy to work with.

Once we have all the 4740s, we filter for the user being locked out, and then display the second entry in the properties array. This ends up being the computer where the failed user logon attempt came from.

A few things to take note of: 

  1. After you have the locked-out location, there is still some troubleshooting to do. You still have to figure out what what machine is creating the failed logon attempts. Is it a service account whose password was changed and needs to be updated on a service or scheduled task? I think the most common scenario is a user has logged on to a machine, never logged out, and has since changed their password.
  2. In a production environment, the security logs on the PDC Emulator get rolled every 24-48 hours. The sooner you can start troubleshooting the better.
  3. This script is dependent on the PDC running Windows Server 2008 or later. Get-WinEvent is not compatible with Windows Server 2003 and a domain controller running this operating system version logs a 644 event, not a 4740 when a user account is locked out.
  4. The ActiveDirectory module is used in the script, which requires the Active Directory Web Services to be running on a domain controller.

I hope this blog post helps you better troubleshoot locked-out accounts and that you picked up some other tips I mentioned.

Happy troubleshooting!

You can download the script from the script repository.

Thank you, Jason, for a very useful article.

Join me tomorrow when we will have a guest blog written by Steffan Stranger.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Comments (10)

  1. Jason W says:

    @JohnB The script is written as a function so you will need to dot source it first. Ed explains how this is done in the following blog.

  2. Anonymous says:

    I know this is old but I am running into the same issue as Jan GL….

    I have tried the PS script associated with this but when I execute the command I just get a clean prompt. I don’t get an error or any output.

    What am I doing incorrectly?

    Thank you!

  3. Anonymous says:

    Awesome post Jason!

  4. Neil says:

    I had created a similar script that is triggered on our domain controllers whenever an 4740 event is logged but I found it easier to work with the events after they were converted to XML, that way I could pull specific fields out by name:

    #Get security user lockout events.  30 seconds should be sufficient

    #as this script will be triggered very quickly after the event is logged.

    #EventID 4740 is user lockout.

    $eventlist = get-winevent -filterhashtable @{logname="security";starttime=$((get-date).addseconds(-30));id="4740"}

    $emailbody = $null

    #Check to see if the event search returned anything, if not at least send the name of the domain controller.


     $emailbody = @"

     $env:computername reported user lockout, but no events found.

     Search security log for event 4740.



    #Start a walk through the events to find the important information.

    $eventlist | foreach-object {

     [string]$timechanged = $_.timecreated

     [string]$dcname = $_.machinename

     #Convert to XML to make it easy to pull out individual fields.

     [xml]$eventrecordxml = $_.toxml()

     $TargetUserName = $EventRecordXML.SelectSingleNode("//*[@Name='TargetUserName']")."#text"

     $TargetSid = $EventRecordXML.SelectSingleNode("//*[@Name='TargetSid']")."#text"

     $TargetComputer = $EventRecordXML.SelectSingleNode("//*[@Name='TargetDomainName']")."#text"

    #Compile the alert text from each event

     $emailbody += @"

    User: $TargetUserName

    Computer: $TargetComputer

    Time Locked: $timechanged

    Domain Controller : $dcname

     UserSID: $targetsid

    "@ + "`r`n`r`n"



  5. JohnB says:

    I ran this script on our PDC, which meets all the pre-requesites and, there are 4740 events; and I get nothing.  Nothing is displayed on the screen.  BTW, what your script provides for information, which I didn't get, is also provided by Microsoft's Account Lockout Status utility.

  6. Jan G. L. says:

    I don’t have any 4740 events on my 2008 R2-server.
    I have 4771, but they don’t reveal caller computer name.
    What to do?

  7. Just curios…why not run the following. what are the pros and cons of doing it this way?

    get-addomaincontroller -filter * | Select Name | % {invoke-command -computername $ -scriptblock {get-eventlog security | where-object {$_.EventID -eq 4740 -and $_.Message -like ‘*username*’}}

    note: username would be changed to the user account you are in search of.

  8. art says:

    Anyone else getting "Directory object not found" when running Get-ADDomainController -filter *

    Maybe a recent windows update broke something? This script was working perfectly until I tried running it a week or two ago.

Skip to main content