Skip to main content

Where did a User’s Account Get Locked Out?

Updated: May 15, 2015
When this article was originally published, two extra carriage returns were add causing the code to malfunction.  The code below is correct.  

My client for this week’s PowerShell class had a really interesting question. They needed to know where an account is being locked out at. OK, interesting. Apparently users hop around clients and forget to log off, leading to eventual lock out of their accounts. The accounts can be unlocked, but are then relocked after Active Directory replication.
This problem is solved in two parts. The first one is to modify the event auditing on the network. The second part is resolved with PowerShell.
The first part involves creating a group policy that will encompass your Domain Controllers. In this GPO, make these changes.
  • Expand Computer Configuration \ Policies \ Windows Settings \ Security Settings \ Advanced Audit Policy Configuration \ Audit Policies \ Account Management
  • Double click User Account Management
  • Check Configure the following audit events.
  • Check Success
This will allow the domain controllers to audit event 4740 which tells use that an account with locked out. Your users will see something like the image below when their account is locked out.
clip_image001
Once the above GPO has been applied, you will now begin to record lock outs. All accounts currently locked out will not have entries in the Security log until they report another lock out.
The second part is to use PowerShell to parse through all the Security logs on the domain controllers and tell you which client a user’s account was locked out on. This script is designed to be dot sourced or turned into a module. It is heavily commented so it can be used for instruction as well as an actually script in production.
Function Extract-UserName
{
param (
   $StringData
)
   <#
    Extract the username from the log file.
     The following line of code performs several tasks.  It receives
     a multi line string that is the message data from the event.  This data
     is saved in the variable $StringData.  Since this is a [STRING] object
     we have access to the methods Split, Replace, and Trim.

    - The first operation splits the string into an array of single lines.
      This is accomplished with the "Split" method.  The `n tells the
      method to split on each new line.

    - The next operation take the contents in array index [10]

    - The "Replace" method is used to replace the part of the string that we
      do not want.  In this case, we are replacing "Account Name:" with $NULL.

    - The "Trim" method removes all leading and trailing spaces and leaves
      us with just the username.
   #>
   ((($StringData.Split("`n"))[10]).Replace("Account Name:",$Null)).Trim()
}

Function Extract-ComputerName
{
param (
   $StringData
)
   # Extract the computer name from the log file.
   Write-Output ((($StringData.Split("`n"))[13]).Replace("Caller Computer Name:",$Null)).Trim()
}

<#
.SYNOPSIS
Discovers the client that a user account was locked out on.

.DESCRIPTION
Returns the client machine which has locked out a user account.
See the NOTEs section for setup information.

.PARAMETER $User
The Name of the User Account that you want to discover the client
that locked this account out.

.PARAMETER $DaysToSearch
This is the number of days to look back in the security logs.
The default setting is 14 days.

.EXAMPLE
Find-LockingClients -user jyoder

UserName                      LockingClient                TimeLocked
--------                      -------------                ----------
jyoder                        LON-CL1                      8/27/2012
7:02:06 PM
jyoder                        LON-CL1                      8/28/2012
8:53:42 PM

Returns the clients where the user account -jyoder was locked out.

.EXAMPLE
Find-LockingClients -User jyoder -DaysToSearch 7

Returns the clients where the user account -jyoder was locked out.
This will search the security logs for the past 7 days. 
The default is 14 days.


.NOTES
For the cmdlet to work, advanced auditing needs to be configured
on the domain controllers. This can be configured locally or in
Group Policy.  It must be done for all Domain Controllers.

Category: Account Management
Subcategory: User Account Management
Audit for: Success

#>
Function Find-LockingClients
{
Param(
    [cmdletbinding()]
    [Parameter(Mandatory=$True)][String]$User = $(Read-Host "Provide a user name"),
    [int]$DaysToSearch = 14


)
   # Get a list of all of the Domain Controllers
   $DCs = Get-ADComputer -Filter * `
    -SearchBase "OU=Domain Controllers,$((Get-ADDomain).distinguishedName)"



   # Set up the number of days to search in the past.
   $StartTime = (Get-Date).AddDays(-($DaysToSearch))

   # Create the hash for the event logs.
   $LogHash = @{LogName = 'Security';StartTime = $StartTime; ID=4740}


   # Store each relevant event in the variable $Events.
    ForEach($Item in $DCs)
     {

      # Error handling just in case on the Domain Controllers
      # is offline.
      Try
       {
          Write-Host "Gathering events from"$Item.Name-ForegroundColor Green `
           -BackgroundColor DarkMagenta
          $Events = Get-WinEvent -FilterHashtable $LogHash `
          -ComputerName $Item.Name -ErrorAction Stop |
          Sort-Object -Property TimeCreated
       }
      Catch
       {

           Write-host "Domain Controller"$Item.Name" if offline" `
           -ForegroundColor Red `
           -BackgroundColor DarkRed
       }
      Finally
       {
            Write-Host $Item.Name"completed" -ForegroundColor Green `
            -BackgroundColor DarkMagenta
       }
     }



   # Set up the array to hold multiple sets of data from the event logs.
   $EventArray = @()

   ForEach ($Event in $Events)
    {
       # Send the event message data to the functions to extract
       # Username and Computer Name.
       $UserName = Extract-UserName $Event.Message
       $ComputerName = Extract-ComputerName $Event.Message

     
       # Write the data to the object.
       $Obj = New-Object -TypeName PSObject
       $Obj |Add-Member -MemberType NoteProperty -Name "UserName" -Value $UserName
       $Obj |Add-Member -MemberType NoteProperty -Name "LockingClient" -Value $ComputerName
       $Obj |Add-Member -MemberType NoteProperty -Name "TimeLocked" -Value $Event.TimeCreated
     
       # Add the individual object to the array of objects.
       $Obj | Where-Object {$_.Username -eq $User}

    }

   # Filter for the user specified in the parameter $User and
   # Send the output into the Pipeline.
  #$EventArray |Where-Object {$_.UserName -eq $User}


}










Comments

Unknown said…
Can you use get-qadcomputer instead of get-adcomputer?
Owen,

I do not utilize the Quest cmdlets. If the object produced has a property called NAME that holds the computer name, then I do not see why not. Also, GET-ADDomainControler could work to extract the names of the domain controllers.

Jason
Anonymous said…
Filtering the specific eventid in eventviewer would have been much easier, no? :)

Thanks for the group policy thing. I was looking all over the place for this.
It would be easier, if you only had one Domain Controller. What if you had 10, 20, or even 100 Domain Controllers? This method touches all of them by executing it only once.
Unknown said…
What would be returned if you were to be locked by say a Mac, or iPhone or other tablet?
Dennis,

I do not have access to Apple Tech on my networks. If they are active directory integrated, go ahead and lock an account on one and see what is in the event log. I'm interested in hearing what you find.

Jason
Unknown said…
After a quick test, I tried to access wireless via a Mac and it showed up blank. I will test further tomorrow when in the office from a login window (via centrify) and an ipad. Thanks
Unknown said…
How does this script decided whether a DC is online or offline? I keep getting messages that the DC is offline but I know for a fact it is online.

Dennis,

The call to the remote computer is in the Get-WinEvent cmdlet. Doing a quick test on my computer to a non-existent server, and RPC error was returned.

Try this:
Enable Windows Remoting on your servers.

Change -------

$Events = Get-WinEvent -FilterHashtable $LogHash `
-ComputerName $Item.Name-ErrorAction Stop |
Sort-Object -Property TimeCreated

To -----------

$Events = Invoke-Command -ScriptBlock {
Param ($LogHash)
Get-WinEvent -FilterHashtable $LogHash `|
Sort-Object -Property TimeCreated } -ComputerName $Item.Name -EA Stop


I do not have a setup to test this. This should remove the RPC technology and utilize Windows PowerShell Remoting for the connection to the remote system. Search my blog of the post on enabling PowerShell Remoting with Group Policy.
Unknown said…
Thanks for the post.

What parameters do I need to supply?
I have tried:
Full name
"Full Name"
SamID
Domain\SamID
nothing
In all cases, I get the error:
[PS] E:\>Extract-UserName
You cannot call a method on a null-valued expression.
At E:\SCripts\PS\Functions\Extract-UserName.ps1:25 char:37
+ Write-Output ((($StringData.Split <<<< ("`n"))[10]).Replace("Account
+ CategoryInfo : InvalidOperation: (Split:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull


What am I missing?

thanks in advance.
Lars,

Extract-UserName is a supporting function. Take a look further down the code. You will see a cmdlet called Find-LockingClients. it also has a help file.

Jason
Ken Elmer said…
I have tweaked the powershell script to use Invoke-Command to run the Get-WinEvent part on each domain controller simultaneously and also run as a straight script instead of dot sourced. Much quicker with 8 DCs.
Bill Curtis said…
I keep getting this message when I run it:
The Value parameter is required for a member of type "NoteProperty". Specify the Value parameter when adding members of
this type.
Value:
Bill,

Thank you for sending the error. The source of the problem came from two extra carriage returns injected when I copied the code over. I've fixed the formatting in the blog to prevent that from occurring again. Please copy the above code and try again.

Jason
Anonymous said…
Awesome functions! Thanks for this!
Unknown said…
I know this is an old post, hoping someone still receives comments. I ran as admin, set execution policy, copy the script into power shell and it simply stares back at me with a blinking cursor after the last line. Sorry, newbie here, what am I doing wrong?
Anthony,

This creates a PowerShell Script Cmdlet in memory. just type Find-LockingClients and press Enter.

Remember, nothing will get reported if you just turned on the auditing. If auditing was not enabled until today, you will not receive information on events from yesterday.

Jason
Bill Curtis said…
I was working on another script utilizing the event log and I found similarities to this one. I thought I would share this since I don't work with the split command very well. It will also reduce the need for the supporting functions.

There is a property named ReplacementStrings that will contain the username, computername, Domain name, and some related SIDS. Using this, you can get the names without needing to parse the message.

$a = get-eventlog -logname security -instanceid 4740 -newest 1
$a.replacementstrings[0] will be the username.
$a.replacementstrings[1] will be the computer that locked it out.
$a.replacementstrings[5] will be the domain the account was in that locked it out.

Thought you would find this interesting.

-Bill

Popular posts from this blog

How to list all the AD LDS instances on a server

AD LDS allows you to provide directory services to applications that are free of the confines of Active Directory.  To list all the AD LDS instances on a server, follow this procedure: Log into the server in question Open a command prompt. Type dsdbutil and press Enter Type List Instances and press Enter . You will receive a list of the instance name, both the LDAP and SSL port numbers, the location of the database, and its status.

How to run GPResult on a remote client with PowerShell

In the past, to run the GPResult command, you would need to either physically visit this client, have the user do it, or use and RDP connection.  In all cases, this will disrupt the user.  First, you need PowerShell remoting enabled on the target machine.  You can do this via Group Policy . Open PowerShell and type this command. Invoke-Command –ScriptBlock {GPResult /r} –ComputerName <ComputerName> Replace <ComputerName> with the name of the target.  Remember, the target needs to be online and accessible to you.

Error icon when creating a GPO Preference drive map

You may not have an error at all.  Take a look at the drive mapping below. The red triangle is what threw us off.  It is not an error.  It is simply a color representation of the Replace option of the Action field in the properties of the drive mappings. Create action This give you a green triangle. The Create action creates a new mapped drive for users. Replace Action The Replace action gives you a red triangle.  This action will delete and recreate mapped drives for users. The net result of the Replace action is to overwrite all existing settings associated with the mapped drive. If the drive mapping does not exist, then the Replace action creates a new drive mapping. Update Action The Update action will have a yellow triangle. Update will modify settings of an existing mapped drive for users. This action differs from Replace in that it only updates settings defined within the preference item. All other settings remain as configured on the ma...