Find the computer responsible for user account lockout

Most enterprises of today have a set of security policies that govern how an Active Directory account is handled. Most environments today also have a strong password policy. Some have account lockout policies as well. In most environments I’ve worked with, they have set that the account be locked after three or five failed attempts at logon—if the user enters a wrong password three or five times, the person gets locked out of the account.

This post talks about the following:

What is account lockout

One of the most common attempts of stealing access is brute force. In this method, different combinations of characters are entered as passwords in hopes that one of the attempts would be successful. It is common for someone to try different words that they think could be the password. An account lockout limits the number of failed attempts.

Other ways accounts get locked

Most of the times, if your account is locked, the reason would be you:

The last two items in the list are infamous for frequent account lockouts. My colleagues, sometimes, create test services in their test servers with their own credentials (instead of managed service accounts), forget where they used them, and end up locking themselves out upon the mandatory password change. In an environment where they manage hundreds of servers, tens of which are test servers, this can quickly become a nightmare; it is hard to find what the source for their account lockout is.

If all of the authentication is managed using Active Directory, you are in luck.

Active Directory and account lockouts

Active Directory logs account lockouts. You can find account lockout events in the Event Viewer. To get the account lockout events, go to the Event Viewer on your Domain Controller, and expand Security. You should find Audit Failure events here, and among them would be the account lockout event you are looking for. The event ID for account lockouts is 4740.

However, in a large environment with a Domain Controller serving thousands of authentication requests every minute, searching for the event could be challenging. Not to mention the process of (sometimes logging into the server and) opening the MMC, expanding the logs, filtering the logs or searching for the events, etc.

There are some prerequisites to find account lockout events. The most important one is having verbose logging enabled. Verbose logging takes up a good amount of space. Since servers are built to serve only certain kinds of requests, you may face situations where disk space is limited—or lean, or “just enough”. However, there is a good chance that verbose logging is enabled on at least one of the Domain Controllers. A client I worked for had verbose logging enabled on their lone PDC Emulator. This script was created for that environment.

The other prerequisite is that your account should be authorised to read the event logs in the Domain Controller. If your account doesn’t have the rights, contact your Administrator to get your account added to Event Log Readers built-in group on the Domain Controller.

The script

Without further introductions or essays, here is the script (also available on GitHub):

function Get-LockOutLocation {
    [CmdletBinding()]

    param(
        [Parameter(Mandatory=$True)]
        [String]$Identity
    )

    begin {
        try{
            Import-Module ActiveDirectory -ErrorAction Stop
        }
        catch {
            Write-Output 'Unable to add Active Directory module'
            break
        }
    }

    process {
        $DcList = Get-ADDomainController -Filter *
        $PDCEmulator = ($DcList | Where-Object OperationMasterRoles -contains "PDCEmulator")

        Write-Verbose "Scanning the domain controllers in the domain for the user's last bad password attempt."
        foreach ($Dc in $DcList) {
            try {
                $UserInfo = Get-ADUser -Identity $Identity -Server $Dc.Hostname -Properties AccountLockoutTime, `
                LastBadPasswordAttempt, BadPwdCount, LockedOut -ErrorAction Stop
            }
            catch {
                Write-Warning "Unable to fetch user information with lockout data from $($Dc.Hostname)"
                continue
            }
            if ($UserInfo.LastBadPasswordAttempt) {
                $LockoutData = $UserInfo | Select-Object -Property @(
                    @{ Name = 'Name'; Expression = { $_.SamAccountName } }
                    @{ Name = 'SID'; Expression = { $_.SID.Value } }
                    @{ Name = 'LockedOut'; Expression = { $_.LockedOut } }
                    @{ Name = 'BadPwdCount'; Expression = { $_.BadPwdCount } }
                    @{ Name = 'BadPasswordTime'; Expression = { $_.BadPasswordTime } }
                    @{ Name = 'DomainController'; Expression = { $_.Hostname } }
                    @{ Name = 'AccountLockoutTime'; Expression = { $_.AccountLockoutTime } }
                    @{ Name = 'LastBadPasswordAttempt'; Expression = { ($_.LastBadPasswordAttempt).ToLocalTime() } }
                )
            }
        }
        Write-Information "$($LockoutData | Out-String)"
        try {
            $LockoutEvents = Get-WinEvent -ComputerName $PDCEmulator.HostName -FilterHashtable @{LogName='Security';Id=4740} `
            -Credential (Get-Credential -Message 'Enter AD admin credentials to query the events.') -ErrorAction Stop `
            | Sort-Object TimeCreated -Descending
        }
        catch {
            Write-Warning $_
            continue
        }

        foreach ($Event in $LockoutEvents) {
            if ($Event | Where-Object {$_.Properties[2].value -match $UserInfo.SID.Value}) {
                $Event | Select-Object -Property @(
                    @{ Label = 'User'; Expression = { $_.Properties[0].Value } }
                    @{ Label = 'DomainController'; Expression = { $_.MachineName } }
                    @{ Label = 'EventId'; Expression = { $_.Id } }
                    @{ Label = 'LockedOutTimeStamp'; Expression = { $_.TimeCreated } }
                    @{ Label = 'Message'; Expression = { $_.Message -split "`r" | Select-Object -First 1 } }
                    @{ Label = 'LockedOutLocation'; Expression = { $_.Properties[1].Value } }
                )
            }
            else {
                Write-Warning 'Could not find account lockout events in the logs.'
            }
        }
    }
}

How the script works

I would admit that I’ve broken some rules of programming. But in the world of IT Pros and SysAdmins, this could be forgiven. Besides, this is another of the scripts of my early days as a PowerShell guy.

This function has one parameter: The SAM account name of the user whose account frequently locks. We first load the Active Directory module into the session with a break statement in the catch block, so that the function doesn’t proceed if there is an error loading the Active Directory module.

Next, we query all the Domain Controllers in the environment. From this list, we pick the PDC Emulator by filtering on the Operation Master Roles. The OperationMasterRoles property is an array, and so, we use the contains operator on it. (A Domain Controller can play multiple roles, hence, the array.)

Next, the last bad password attempt is queried for. The idea is to show the administrator which domain controller the last bad password attempt happened at. Different environments give different levels of access to different teams. This data would be helpful in some environments.

However, note that this data is being output in the form of Information. And it would be helpful only if you call the function (or cmdlet) with -InformationAction Continue. This is done in order to not pollute the Success stream.

The next block is reading lockout events. We use the Get-WinEvent cmdlet to fetch these events from the PDC Emulator. We use a filter hash table—we specify the log name and the event ID. This statement also sends out a request for credentials, in case you have a different admin account that has the rights to read the event logs. We sort the output entries in a reverse chronological order.

Next, we build the lockout events object by picking one event after another from the output of the previous statement. The SID of the user’s account is the third property in the output. Therefore, we use $_.Properties[2]. We match its value with the user’s SID gathered from AD—use this condition as a filter to pick only the relevant events from all the 4740s. This filtered object is then sent to Select-Object to pick only those properties that we need; we name these properties to make the output more meaningful to the context using Calculated Properties.

Simply running the cmdlet would output the $Event object, which contains all the necessary information.

The output tells you which computer is responsible for the account lockout. It could either help your colleague jog his memory on what (s)he did that caused the lockout, or (s)he should be able to sign in (timing this is critical) and change the credentials in the service or within Credential Manager.

Wrapping up

Frequent account lockouts can be a menace to administrators. In an environment that has hundreds of potential nodes where this can happen, finding out the node responsible for the account lockout needs some administrative intervention. While tools like System Center Operations Manager can help you with this sort of information, not every environment that has AD, has SCOM. Not to mention the complexity of implementation of SCOM—it is insane to set it up to simply monitor account lockouts (of course, nobody does it).

This simple PowerShell function would help give you necessary information about the account lockouts by taking in the user’s username (or technically, SAM Account Name) as the input. The script does not have any server names hard-coded—as long as the computer you are running this cmdlet on reports to AD and has the AD PowerShell module installed, you are good to go, of course, provided you meet the prerequisites on the Domain Controller.

Want to learn PowerShell?

The award-winning book, PowerShell Core for Linux Administrators Cookbook, which I co-authored, uses the recipe-based learning approach to give you a deep understanding of PowerShell. And the best part is, the concepts discussed work across platforms!

The best new PowerShell books

powered by TinyLetter