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.
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.
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
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
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
Thanks for the group policy thing. I was looking all over the place for this.
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
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.
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.
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
The Value parameter is required for a member of type "NoteProperty". Specify the Value parameter when adding members of
this type.
Value:
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
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
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