How to get logged on user history with PowerShell

Jordan Hammond fun headshot
Jordan Hammond|April 22, 2020
image (50)
image (50)

If you have read some of my blogs, you know how much I love PowerShell. If this is your first blog, HI! I am Jordan, and I love PowerShell! My love for it is why we are here today, PDQ has a new feature out that will allow you to create a custom scanner in Inventory using PowerShell. What does this mean for you? It means you can grab virtually any data that you could ever want. As always, with PowerShell, it is not about “can I”? But “how do I”? With that in mind, let’s break down a script we have in our GitHub for checking logged on user history. Watch the accompanying video to this blog post here.

Prep Work!

First, let’s get the caveats out of the way. This script uses the event log to track this, so if you have not enabled Audit Logon Events from Group Policy, you will need to. Without it, it will look at the events still, but chances are the data you want most has been overwritten already. DAMN YOU CIRCULAR LOGGING!!!!

Getting Logged on User History

I will break down some critical points in this, but Nate has done an excellent job with his comments. I will have the full script at the end, and it should answer any lingering questions. The base of this script is the Get-EventLog command. Logon eventID’s are 4624.

Get-EventLog -LogName "Security" -InstanceId 4624 -ErrorAction "SilentlyContinue"

This is going to grab a LOT of events, so step 2 of this is fine-tuning your results. In a foreach loop, you will need to grab the specific data you need: the account name that logged in and the logon type. Logon type is so you can remove any logon that is not a local or remote logon.

$EventMessage = $_ $AccountName = $EventMessage.ReplacementStrings[5] $LogonType = $EventMessage.ReplacementStrings[8]

Now that we have the data, and it is in a format we can use, it is time to remove what we don’t need. There is a bit of logic in there. First, we exclude all events that are not logon type 2 or 10 (Local and Remote) as well as all logons done with Windows Service Accounts. Then, we remove all duplicate entries. I do not care how often Bo Jangles logged in, just if he has or not. Finally, we put our results into a custom object where we can control our results format.

Username = $AccountName LogonType = $LogonTypeName LastLogon = $EventMessage.TimeGenerated.ToString("yyyy-MM-dd HH:mm:ss")

Putting it all Together

Now let’s see what the script looks like when we put it all together. Below is the complete script that we have been talking about. We highly recommend that if you are going to use it you get it from GIT and follow the steps in there so your scanner will remain up to date if changes are made. Iterate with Foreach-Object

# This script requires that Audit Logon events are enabled in Group Policy and those events are kept for the amount of history preferred [CmdletBinding()] param ( [Switch]$Lowercase ) $UserArray = New-Object System.Collections.ArrayList # Query all logon events with id 4624 Get-EventLog -LogName "Security" -InstanceId 4624 -ErrorAction "SilentlyContinue" | ForEach-Object { $EventMessage = $_ $AccountName = $EventMessage.ReplacementStrings[5] $LogonType = $EventMessage.ReplacementStrings[8] if ( $Lowercase ) { # Make all usernames lowercase so they group properly in Inventory $AccountName = $AccountName.ToLower() } # Look for events that contain local or remote logon events, while ignoring Windows service accounts if ( ( $LogonType -in "2", "10" ) -and ( $AccountName -notmatch "^(DWM|UMFD)-\d" ) ) { # Skip duplicate names if ( $UserArray -notcontains $AccountName ) { $null = $UserArray.Add($AccountName) # Translate the Logon Type if ( $LogonType -eq "2" ) { $LogonTypeName = "Local" } elseif ( $LogonType -eq "10" ) { $LogonTypeName = "Remote" } # Build an object containing the Username, Logon Type, and Last Logon time [PSCustomObject]@{ Username = $AccountName LogonType = $LogonTypeName LastLogon = $EventMessage.TimeGenerated.ToString("yyyy-MM-dd HH:mm:ss") } } } }

This script is a great one to dive in and learn if you are getting into PowerShell. You don’t want to run any old script you got off the internet without knowing what is in it, so I highly recommend breaking this one down to understand what is going into it. If you are a PowerShell vet, take some time to appreciate how awesome Nate is, if you are new to PowerShell, take some time breaking this down and learning what he did and why. You will learn a lot about best practices if you do.

Jordan Hammond fun headshot
Jordan Hammond

Jordan had spent his life wondering why tasks he didn’t like to do had no options to complete themselves. Eventually he had to make that happen on his own. It turned out that he enjoyed making tasks complete themselves, and PDQ thought that is something he should talk about on the internet.

Related articles