When I first started getting in to Powershell I was working in an IT Security position and was sifting through a lot of “noise” in the SIEM alerts. The main offender was account lockouts. Typically, if I looked up the user in Active Directory I’d find out that they had recently changed their password, and so it wasn’t anomalous behavior for them to have locked their account. But, getting this information from the AD/UC snapin was very slow, and some of the information was more easily gleaned through Powershell. One of the Sys Admins had given me a script they wrote that kind of did what I wanted, but I decided to write my own.

Running the following command got me to a good starting place:

Get-ADuser -Identity JohnSmith -Properties * | Select-Object *

This gave me all of the possible properties a user object from AD might contain and what their values were. A little bit of searching and I had my list of properties I wanted to query for a specific user.

Get-ADuser -Identity JohnSmith -Properties Displayname,Passwordlastset,Badpasswordtime,msDS-userpasswordexpirytimecomputed,lockedout,accountlockouttime,LastBadPasswordAttempt

That’s kind of long to look at and as it turns out, the value stored within “msDS-userpasswordexpirytimecomputed” isn’t very friendly so you need to convert it to something more human readable:

Get-ADuser -Identity JohnSmith -Properties Displayname,Passwordlastset,Badpasswordtime,msDS-userpasswordexpirytimecomputed,lockedout,accountlockouttime,LastBadPasswordAttempt | Select-Object @{Name="ExpiryDate";Expression={[datetime]::fromfiletime($_."msds-userpasswordexpirytimecomputed")}}

Great, not it’s even longer. Obviously there’s no way I’m going to remember all of that every time I want to get this information, so this is a perfect opportunity for a Powershell function. When I have a lot of object properties to deal with I like to set up hashtables so I can splat them at the cmdlet.

$GetADUserArgs = [ordered]@{
    Identity = $User
    Server = $Server
    Properties = @(

I’ve got 3 parameters for the Get-ADuser cmdlet I want to provide values for and one of them (Properties) I’ve got 7 values to provide. Creating this ahead of time in the above format makes it much easier to read and better for the next person who comes along to edit it. I also need to convert that one time property to a human readable version in a Select-Object statement so I might as well set up a similar block for that.

$SelObjArgs = [ordered]@{
    Property = @("Displayname",
                @{Name="LockoutTime";Expression={ $_.accountlockouttime }},
                @{Name="LastFailedAuth";Expression={ $_.lastbadpasswordattempt}}

The last two are just to rename the properties “AccountLockoutTime” and “LastBadPasswordAttempt” so that my results are a little shorter and easier to look at. With those two variables defined my cmdlet execution get’s to look pretty concise.

Get-ADUser @GetADUserArgs | Select-Object  @SelObjArgs

Now we just need to build the function around this and we’re almost there

Function Get-ADPasswordInfo {
    [Parameter(Position = 0, ValueFromPipeline,Mandatory=$true)]
    [string]$Server = '' #put your AD domain here


Begin {
    # stuff to set up before processing the request

    # check to make sure the AD module is loaded

    if (!(Get-module -name ActiveDirectory)){
        Import-Module -Name ActiveDirectory
    # define our 'select-object' properties to make the command easier to read down below

    $SelObjArgs = [ordered]@{
        Property = @("Displayname",
                    @{Name="LockoutTime";Expression={ $_.accountlockouttime }},
                    @{Name="LastFailedAuth";Expression={ $_.lastbadpasswordattempt}}
    # an array to store our results in

    $results = @()
} # end Begin block

Process {
    # our command parameters, defined ahead of time for easier reading down below. This needs to be in the 'process' block so that the $user variable can be defined/updated from pipelineinput

    $GetADUserArgs = [ordered]@{
        Identity = $User
        Server = $server
        Properties = @('Displayname','Passwordlastset','Badpasswordtime','msDS-userpasswordexpirytimecomputed','lockedout','accountlockouttime','LastBadPasswordAttempt')
    # do the query and then append the info to the results array

        $adinfo = Get-ADUser @GetADUserArgs -ErrorAction SilentlyContinue | Select-Object  @SelObjArgs
    }Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]{
        Write-Host "$User not found in domain: $Server" -ForegroundColor Yellow
} # end Process block

End {
    $Results | Format-Table
} # end of End block

} # end of function

I omitted the Get-Help info at the top, but I always recommend writing this so other people can understand how to use your code. Execution of this function might look something like this.

Get-ADPasswordInfo -User cbodett
Displayname            Passwordlastset      ExpiryDate           Lockedout LockoutTime LastFailedAuth
-----------            ---------------      ----------           --------- ----------- --------------
Bodett, Courtney        2/13/2020 3:44:03 PM 4/13/2020 4:44:03 PM     False             3/16/2020 7:16:02 AM

Since it’s an advanced function it supports pipeline input, and the Begin and Process blocks are built around this idea too. This means you can pipe a bunch of usernames to the function, it will process them all, and then spit out a big table with all of your results. I would often just do this:

Search-ADaccount -Lockedout | Select-Object -Expand samaccountname | Get-ADPasswordInfo

This would get me the pertinent password information about every account that was currently locked out. A quick glance at when their password expired and when they last changed it would usually let me know whether or not this required much further investigation.



14 minute read

Getting GPS Coordinates From A Windows Machine Since 2020 a lot of organizations have ended up with a more distributed workforce than they previously had. T...

Quick Tip on ParameterSetNames

3 minute read

I was writing a new function today. Oddly enough I was actually re-writing a function today and hadn’t realized it. Let me explain. Story Time About a hal...

ProtectStrings. A Module Story

21 minute read

I’ve had an itch lately to do something with AES encryption in Powershell. I’ve tossed around the idea of building a password manager in Powershell, but I g...

Powershell all of the things. And more logging

9 minute read

“If all you have is a hammer, everything looks like a nail” - Abraham Maslow. I use a variation of this quote a lot, and I typically use it in jest, but it’s...

Back to top ↑



6 minute read

Introduction I’ve had some exposure to Microsoft Defender here and there, but I was in a class with Microsoft recently where they were going over some more f...

Logging in Powershell scripts; Continued

23 minute read

In my previous post I explained a bit about some of my justifications for logging in Powershell. My interest in logging has continued since then and I spent...

Logging in Powershell scripts

7 minute read

Everyone has a different use for Powershell. Some people use it for daily administrative tasks at work. Some people are hard at work developing Powershell m...

Parsing log files with Powershell

8 minute read

Early on when I first started using Powershell I was dealing with some firewall logs from a perimeter firewall. They were exported from a SIEM in CSV format...

Get-Connections; netstat for Powershell

5 minute read

One of the tools I feel like I’ve been using for years is Netstat. It exists in both Linux and Windows (with some differences) and has similar syntax. It’s ...

Secure Credentials in Powershell

6 minute read

A coworker from a neighboring department had an interesting request one day. They wanted a scheduled task to run on a server. Through whatever mechanism the ...


4 minute read

When I first started getting in to Powershell I was working in an IT Security position and was sifting through a lot of “noise” in the SIEM alerts. The main...

Jekyll & Minimal Mistakes; Done

less than 1 minute read

“Hello World” and all that. What started as a small conversation turned in to an Idea that I couldn’t shake: I wanted a blog. But I didn’t want a WordPress...

Back to top ↑