PowerShell Scripts to Force Password Change for All Users After a Security Incident

After a confirmed or even suspected security breach it may be advised to have all users change their passwords. In this post we’ll review how to confirm if users have changed their passwords and how to force users to complete a password change in Active Directory.

You can manually check the pwdLastSet attribute on each user account to see when their password was last changed (you could also find this out by running a free scan with Specops Password Auditor and looking at password changed dates in the Password Age report). Use ADSIEdit or Active Directory Users and Computers (with advanced features enabled) to view the attribute directly:

When using PowerShell to pull the attribute, you’ll see that the attribute is actually saved in what is known as ‘filetime’ format, but you can also pull a constructed attribute called passwordLastSet which will be a DateTime PowerShell attribute (more on why that’s important in a bit).

Quick note on filetime: this is a timestamp stored as a count of the number of 100-nanosecond intervals that have elapsed since midnight on January 1, 1601 (UTC).

Also note if you see pwdLastSet attribute is 0 (i.e. ‘never’) and passwordLastSet is blank — this means the option that the user must change their password at next logon has been enabled. This is a bit of a drawback in Active Directory as we now have no way to know when the last password change was actually performed.

The passwordLastSet property as a DateTime object is important because we can now manipulate the date and compare it to other dates. So if I want to run a check to say, for example, was this user’s password changed within the past 24 hours, I can add 1 day to the passwordLastSet attribute and compare it to

$user.passwordLastSet.AddDays(1) -gt $(get-date)

If we change the threshold to 30 days for this user, the result is now true (in this example, the user’s password change was about 3 weeks ago as of this blog post):

We can run this same check against all users. We’ll also exclude disabled users and any users who are already required to change their password at next logon, and check for users whose passwords never expire and/or cannot change their own password:

$verbosepreference = "continue"  
 
## Construct a date X days ago, in this case 30 days:
$compareDateTime = $(get-date).AddDays(-30)

## Alternative Option -- compare against a specific datetime by uncommenting and modifying the date in the following line:
## $compareDateTime = get-date -date "2022-03-21 12:00"
 
write-verbose "Finding users whose passwords have not changed since $compareDateTime" 
  
Get-ADUser -filter { Enabled -eq $True } –Properties pwdLastSet,passwordLastSet,passwordNeverExpires,cannotChangePassword |   
    where { $_.passwordLastSet -lt $compareDateTime -and $_.pwdLastSet -ne 0 } |   
    Select-Object name,sAmAccountName,passwordLastSet,passwordNeverExpires,cannotChangePassword
                   

If we’d like to force all those users to change a password, we can run a script that does the same date comparison but then ticks the ‘user must change password at next logon’ option on all of them. Here we exclude any user with ‘passwordNeverExpires’ or ‘cannotChangePassword’ set to true, as you cannot require a password change on those users. We recommend a manual process to remediate these as these accounts are usually service accounts where a password change requires simultaneously updating the password in a service or application configuration.

$verbosepreference = "continue"  
 
## Construct a date X days ago, in this case 30 days:
$compareDateTime = $(get-date).AddDays(-30)

## Alternative Option -- compare against a specific datetime by uncommenting and modifying the date in the following line:
## $compareDateTime = get-date -date "2022-03-21 12:00"
 
write-verbose "Finding users whose passwords have not changed since $compareDateTime" 
  
$users = Get-ADUser -filter { Enabled -eq $True } –Properties pwdLastSet,passwordLastSet,passwordNeverExpires,cannotChangePassword |   
    where { $_.passwordLastSet -lt $compareDateTime -and $_.pwdLastSet -ne 0 -and $_.passwordNeverExpires -eq $false -and $_.cannotChangePassword -eq $false }

foreach ($user in $users) {
    $outObject = new-object -typename psobject
    $outobject | Add-member -MemberType NoteProperty -Name distinguishedName -Value $user.distinguishedname
    $outobject | Add-Member -MemberType NoteProperty -Name OldPasswordLastSet -value $user.passwordlastset
    set-aduser $user -ChangePasswordAtLogon:$true
    $outObject
} 

(Last updated on March 30, 2022)

Tags: , ,

darren siegel

Written by

Darren Siegel

Darren Siegel is a cyber security expert at Specops Software. He works as a lead IT engineer, helping organizations solve complex challenges within IT security. Darren has more than 15 years’ experience within Active Directory, IT security, servers, storage, virtualization, cloud, and identity and access management.

Back to Blog

Related Articles

  • How to enforce password history in Active Directory

    The “Enforce password history” setting in Active Directory is used to determine the number of unique passwords a user must use before they can use an old password again. This is an important setting because password reuse is a common issue – the more often the same (or similar) password is used, the greater chance…

    Read More
  • Password expiration policy best practice

    Instead of arbitrarily expiring passwords every 90 or so days, why not configure the maximum password age based on the complexity level of a password?

    Read More
  • Helpdesk password reset best practices

    If your organization is currently using a self-service password reset solution, it is critical that the helpdesk staff who manage the system, and assist users, consistently follow best practices. This post will provide tips for reducing password-related calls to the helpdesk, and outline some security measures for safeguarding user accounts. Educate and direct to self-service…

    Read More