Quick Tip Invoke-History
The more time I spend living in the CLI the more I appreciate learning and adopting shorthand for operations. In Powershell the aliases for Where-Object and...
With more and more people working remotely there’s been a huge uptick in VPN usage. A lot of organizations have had to completely rethink some of their previous IT policies and procedures. Some things that used to be simple are now slightly more complicated.
One thing I wasn’t aware of, being so far removed from front line customer support at work, was that a lot of our user’s passwords were expiring while they were working remote. With an expired password, they couldn’t connect to the VPN, and without connecting to the VPN they couldn’t update their password. Unfortunately self-service password reset is not within our control because that’s the obvious answer. In some cases users were being told to come in to the nearest office so they could sign in to their computer on network, and then update their password. In other cases the help desk was resetting their password and dictating it to them over the phone. But, more often than not the help desk was asking for the user’s current password, and resetting it in AD to that. Obviously this is all really bad (especially that last one), but there wasn’t an available solution to stop this from happening. I read that there was a way with Powershell to essentially reset the password expiration clock on a user account to push the date out. If your password expired yesterday, and the domain policy was a 90 day password, then “resetting” it would change your expiration date to 90 days from now. This would make the user’s currently configured password valid again and prevent any form of password sharing. Then the user could manually initiate a password change once they were up and running again.
The pwdLastSet Attribute in Active Directory contains a record of the last time the account’s password was set. Here is the definition from Microsoft:
“The date and time that the password for this account was last changed. This value is stored as a large integer that represents the number of 100 nanosecond intervals since January 1, 1601 (UTC). If this value is set to 0 and the User-Account-Control attribute does not contain the UF_DONT_EXPIRE_PASSWD flag, then the user must set the password at the next logon.”
There’s also the PasswordLastSet attribute which is just the pwdLastSet attribute but converted in to a DateTime object which is a lot more readable. But, if you want to make a change directly to an account’s Password Last Set it’s done via the pwdLastSet attribute. Knowing that it’s stored as a large integer number representing “file time” is important when we start making changes to it.
Making changes to an Active Directory user account is often done with Set-ADUser and this is no different. If you look at the help info for Set-ADUser we can see that there are a lot of parameters representing attributes/properties we can change. The pwdLastSet attribute isn’t on the list however. There are plenty of forum hits and examples that reveal that the parameter we need to use is -Replace. The -Replace parameter accepts a hashtable as value so the syntax is pretty straight forward: The property name you want to update, and the value you want to replace it with.
Whether a user account’s password is expired or not, if you replace the pwdLastSet value with a 0 it effectively expires their password immediately. We’re clearing the slate here. The next step seems odd but we replace the pwdLastSet value with a -1. Since this is stored as a large integer value we’re telling it to set it to the largest number that can be stored in a large integer value. This would be some insane date out in the future except that it uses the domain password policy and caps it out at the default max password age. If that’s 90 days for example, then setting it to -1 puts the expiration date as 90 days out in the future from the execution of the command. The general consensus online is that both of these steps need to be taken: set it to 0, then -1. I haven’t done a deep dive on why, but if anyone has an explanation feel free to hit me up.
Seems simple enough then right? The script just needs to set the pwdLastSet attribute for a given user to 0 and then -1. One of the things I always ask when I’m writing Powershell for someone else’s consumption is “how” they want to be able to use this. Do they want to manually launch Powershell and execute the script by calling out its path? Do they want to be able to double-click a shortcut and have the script execute? Do they just want a function they can run as a CLI tool in Powershell?
In our case the help desk doesn’t spend a lot of time with Powershell and would prefer to just double-click a shortcut. I on the other hand prefer to run Powershell scripts from an open Powershell session, so I figured I would accommodate both.
At its simplest the script really just needs to do this:
However, I wanted the script to have some sanity checks, provide before and after info regarding the account’s password expiration, allow for alternate credential use and to run in a loop in case there were multiple accounts to target. I also wanted it to support running as the target of a shortcut, as well as an interactive script for users that would prefer to do it that way.
Script on Github
That’s a lot, I know. But let’s look at what that might look like in action. You’ve got a shortcut made for this stored somewhere accessible, and you simply double click it:
Then let’s say the account you log in to your computer with doesn’t have delegated permissions to make these changes so you need to provide credentials. This is collected securely via a Read-Host prompt:
Provide the account name that you want to target and it starts by providing the current state of the account. Seeing the ExpiryDate property represents an expired password I say “y” to the prompt to reset the clock and it pulls the account from AD again to show that now the PasswordLastSet and ExpiryDate properties have updated.
If there are more accounts to be done you can answer “y” at the end and the process starts over. If we say “n” then it will prompt that pressing “enter” will close the window, and that’s it. If running the script from the CLI directly it’s very similar except that you can provide a PSCredential object as a parameter or simply specify a username and it will securely prompt for a password. The loop is the same, just without the prompt at the end to “close this window”.
In the end the script has helped with a task that was previously being performed with poor security practices. It should be noted that both NIST and ISO have moved away from recommending password expiration as a security control, and as organizations catch up this will likely become less and less of an issue. Also, Powershell is absolutely not the right solution for this problem. The right solution would be to have a self-service password reset portal available where the users can authenticate with their expired password, and securely update their password and have it update in all required systems. In absence of that, Powershell turned out to be a pretty good solution.
The more time I spend living in the CLI the more I appreciate learning and adopting shorthand for operations. In Powershell the aliases for Where-Object and...
I got the opportunity this week to attend the 2024 Powershell Summit in Bellevue Washington. If you have an opportunity to go to this, whether you’re brand ...
SecretManagement module is a Powershell module intended to make it easier to store and retrieve secrets. The secrets are stored in SecretManagement extens...
With more and more people working remotely there’s been a huge uptick in VPN usage. A lot of organizations have had to completely rethink some of their prev...
Hi all. Just wanted to provide a brief status update. It’s been a while since my last post and while I have been busy, and making frequent use of Powershel...
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...
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...
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...
“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...
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...
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...
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...
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...
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 ...
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 ...
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...
“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...