Use Get-WinEvent to find account lockout events
Last week I had a user report that his account kept locking a number of time throughout the day. Usually this is because they haven’t logged out of a machine and then have changed their network password, so when that remote machine tries to authenticate… bang account lockout.
More often than not, the user has no idea what he is still logged into, so the only way is to solve this is to go through the Security event logs on each domain controller and find the account lockout event for that user. This will then tell you from what machine the account lockout took place. You can then get the user to log out and problem fixed.
Although this works, to be honest it’s manual process which really like most manual processes…it’s boring. So then I thought, why not create a PowerShell script that can easily do this for me.
*A little while later*…… and here is the blog post that contains the magical script.
Overview
Below is the full script for your enjoyment. To use it, all you need to do is replace $DCs
array with the names of each domain controller your have in your domain. You can have as many or as little as you want (even one).
Once you have done that, save it as a .ps1
file (or download it from here). From here you can simply right click on the file and select the “Run with PowerShell” option. The script will then prompt you for the username you are searching for (do not put the domain\ before the username) and then the full path of where you want to save the results.
PowerShell Execution Mode
If you download the script then you will need to change your PowerShell execution mode to Unrestricted to be able to run it (see below). However, do this at your own risk:
Set-ExecutionPolicy Unrestricted
If you copy the code below and create your own PowerShell file (.ps1
) then you will need to have your execution mode set to at lease RemoteSigned (see below). Again, this is at your own risk and in accordance to your organisation’s security policy:
Set-ExecutionPolicy RemoteSigned
Ok, now that the fluffy stuff is out the way… let’s get started. First off, the entire script. After this, I will be going through some of the more “juicy” parts of the script and explaining those in a bit more detail:
The Script
Get-WinEvent
Right, now let’s have a look at some of the more interesting parts of the script:
To search for and return the results we use the PowerShell cmdlet – Get-WinEvent
. Using the -FilterHashTable
parameter, we create a hash table of search criteria which we use to find the potential results we are looking for. In this case we are passing two criteria:
LogName
– the specific log we want to search for the results (in this case we only need to search the Security log for possible account lockouts)ID
– the specific EventID we are looking for. EventID 4740 = Account Lockout.
$Results = Get-WinEvent -FilterHashTable @{LogName="Security"; ID=4740} -ComputerName $DC | Select Message, TimeCreated
We combine the -FilterHashTable
parameter with the -ComputerName
parameter so that the cmdlet searches the domain controller’s EventLogs and not the local machines (which is by default if -ComputerName
is not specified).
Searching for the specified user
We now have a list of all account lockout events (and their details) stored within the $Results
object, which is of the System.Diagnostics.Eventing.Reader.EventLogRecord
type. We now need to look through the Message properties of each result and find only the ones that correspond to the user in question.
I am sure there must be other ways to do this, but I chose the good ‘ol classic string manipulation:
If($Item.IndexOf($sUsername) -gt 0){....}
This If
statement find the index value of the first occurrence of the username within the result’s message. Simply, if it finds it, it will be greater than 0, so continue on and attempt to collect the calling computer – which is what we are interested in. If it doesn’t find it, then this account lockout event is not for the specified user and we really don’t care about it.
Finding the calling computer
Next we need to find the elusive calling computer name. This again is done by some simple string manipulation…essentially finding the section in the event’s message that contains the computer name, and then cleaning up the result to make it look pretty and readable for the user:
$sMachineName = $Item.SubString($Item.IndexOf("Caller Computer Name"))
$sMachineName = $sMachineName.TrimStart("Caller Computer Name :")
$sMachineName = $sMachineName.TrimEnd("}")
$sMachineName = $sMachineName.Trim()
$sMachineName = $sMachineName.TrimStart("\\")
Once we’ve got all the info we need, we then store that in a new PS-Object with the properties of MachineName and TimeCreated (which is the data we need to return to the user).
The last thing we do is store each object in an array, as there may be more than one account lockout event (and potentially calling computer) for the user specified. By storing all gathered data in their own object within an array, we can easily enumerate through the array and output it to file using the Set-Content cmdlet:
$aOutput | Set-Content $sLogPath
And that is it pretty much. The script then delays for 2 seconds (just to ensure the log file has closed properly, etc) and then we launch notepad with the results.
Other things to note
There are a few other things I would like to point out that might be of interest:
1. Collecting input data from a user is really easy in PowerShell (way easier than in vbScript) – just use the Read-Host cmdlet and assign the input to a variable:
$sUsername = read-host -prompt "Enter the user you want to search account lockouts for. Note: Do not put the domain name before the user (e.g. )"
$sLogPath = read-host -prompt "Enter the full path of where you want to save the results (including file name). Example: C:\Logs\AccountLockouts.txt"
2. Displaying a message box in PowerShell for some reason isn’t that easy (why, I have no idea), however using a little vbScript we can do this quite easily anyway:
$oMsgBox = New-Object -ComObject Wscript.Shell
$oMsgBox.Popup("No username or log path entered. Script exiting.",0"Error!")
3. Why did I create a new PS-Object to store the event’s details (MachineName and TimeCreated) instead of using a standard hash table (see code below)?
#Set required info into hashtable
$hItemDetails = New-Object -TypeName psobject -Property @{
MachineName = $sMachineName
TimeCreated = $Result.TimeCreated
}
Good question! However I don’t really know the answer either. All I know is that if you use a standard hash table and then store it within an array, when you try and access the properties of the hash table for a particular array element, instead of returning the actual value assigned to the property, it returns the properties of the property (if that makes sense).
I’m not sure exactly why this happens because when I debugged my script line by line, I could see the properties and their corresponding values being correctly stored within the hash table and then the array. However when I tried to retrieve them I couldn’t. The only way to get around this was to create my own PS-Object…. it works, so there you go.
More Info
I know that I went through some of the stuff pretty quickly (especially Get-WinEvent
), so if you are looking for more information check out some of these awesome links below:
Finally, if this post helped you or you would like more info / help, then please let me know in the comments below.
Luca
HI Luca,
I am getting syntax error after i run the script. I have change the DC_name1 to DC_wdcxxx(example only) but i Am getting “The filename, directory, or volume label syntax is incorrect”. not sure what went wrong.
🙁
Hi Naavin,
Can you please post or email me the code so I can have a look?
Thanks
Luca
Hello, this is very cool but no matter if the accounts are locked out it always says no results found. I have it looping through the 5 dcs. Any tips? I am a domain admin.
Hi Roe,
What DCs are you running – Windows 2003, 2008, 2012? Also can you manually log into a Domain Controller and verify there are Account Lockout events (EventID 4740) in the Security Log?
Thanks
Luca
This script is awesome. I had to run powershell as admin but once I did gave me the exact info I needed in a clean format. Thank you Luca. By the way what helped you progress your powershell scripting skills to the point where you can put something like this together?
Thanks Kris! Glad that everything worked well.
Well I worked a lot with vbScript prior to PowerShell so I picked up a lot of standards, etc from there. I also do web development and .NET development so I guess the underlying programming understanding and fundamentals really make it easier.
If you want to improve your PowerShell, I would give these 3 suggestions:
PowerShell is an Object-Orientated Programming Language (like .NET, C, VB, etc). If you can understand the object orientated fundamentals it will really help. Things like objects, methods, properties, classes, etc. There are some really good tutorials online.
Practise. The best way to learn is to apply it to something you want to achieve. Without a target or a specific thing you want to achieve, generally you don't end up practising. Best thing is start of small and just do small things like lets say a file copy and then confirm that the file has copied successfully. If you do heaps of small projects you get a feel for the language. Also, you can always go back and use similar code from one script in another.
When developing bigger projects, I find it heaps easier to break the what you are trying to achieve in little manageable steps. Then you code each step and do some test to ensure successful and error handling if it isn't. You repeat for each individual step until you achieve the entire solution. I find that if I focus on the overall solution it becomes too daunting and really hard to achieve. Hope this helps Luca
My problem is that if security logg is empty. I mean if the filtered criteria returns an empty “answere/respons” you get a errormessage A parameter cannot be found that mach the specific criteria.
Soo naturaly I want to get around a error message by testing and if empty then skip….