One of the most common problems in corporate environments is password expiration. With the PowerShell script I introduce in this article, your users will receive automated emails when their Active Directory account passwords are about to expire.

You know the thought process: regular password expiration is supposed to make organizations safer. However, password expiration also generates calls to the helpdesk when users forget to change passwords before the expiration occurs. One easy way to deal with users forgetting to change their passwords is notify them shortly before expiration. Today, I will show you one example of doing this via an automated email reminder that a nightly PowerShell script generates via a scheduled task.

The reminder emails are straightforward to generate once we figure out some parameters to use for finding passwords about to expire. In my example, I will check for the password expiration date of all Active Directory accounts but skip checking any accounts that have non-expiring passwords, null passwords, or disabled ones. The script will then send emails to the users seven days prior to password expiration, followed by three days prior and then finally one day prior to password expiration.

My script consists of four major parts:

Getting the password expiration date for each user, Calculating the days remaining until password expiration, Configuring the mail message to send, and Sending the email message.

The last piece is to set up the script to run regularly. We can do this by setting up a scheduled task to run the script. But I will not be covering the configuration of the scheduled task because it is easy to do and there are a ton of references to follow.

We're going to walk through each part of the script in detail, and then we'll put it all together at the end.

Getting the password expiration date for each user ^

The first step in creating the script is to query all user accounts and expose their password expiration dates:

$users = Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False -and PasswordLastSet -gt 0} ` -Properties "Name", "EmailAddress", "msDS-UserPasswordExpiryTimeComputed" | ` Select-Object -Property "Name", "EmailAddress", ` @{Name = "PasswordExpiry"; Expression = {[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed").tolongdatestring() }} 1 2 3 $users = Get-ADUser -filter { Enabled -eq $True -and PasswordNeverExpires -eq $False -and PasswordLastSet -gt 0 } ` -Properties "Name" , "EmailAddress" , "msDS-UserPasswordExpiryTimeComputed" | ` Select-Object -Property "Name" , "EmailAddress" , ` @ { Name = "PasswordExpiry" ; Expression = { [ datetime ] :: FromFileTime ( $_ . "msDS-UserPasswordExpiryTimeComputed" ) . tolongdatestring ( ) } }

Let's break this search down into smaller chunks that make it easier to understand:

Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False ‑and PasswordLastSet -gt 0} 1 Get-ADUser -filter { Enabled -eq $True -and PasswordNeverExpires -eq $False ‑ and PasswordLastSet -gt 0 }

Here we are searching for the following:

Enabled users

Users who do not have a password that never expires

Users who have a password set (PasswordLastSet -gt 0) because we want to skip users with null passwords

TIP: You could customize this search many ways. Two examples would be to target a specific organizational unit (OU) or maybe a set of accounts that match a name (such as admin accounts).

Then we ask for specific properties to return; we need the EmailAddress for later on. The value msDS-UserPasswordExpiryTimeComputed contains the user's password expiration date. However, the password expiry time is not in a human-readable form, so we have to do a conversion:

@{Name = "PasswordExpiry"; Expression = {[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed").tolongdatestring() }} 1 @ { Name = "PasswordExpiry" ; Expression = { [ datetime ] :: FromFileTime ( $_ . "msDS-UserPasswordExpiryTimeComputed" ) . tolongdatestring ( ) } }

This creates a hash table and converts the time to a human-readable format. It saves the value into the variable named PasswordExpiry. Notice at the end I use tolongdatestring. This returns the long format of the date (Sunday, March 25, 2018). I chose this because I thought it would be the most useful to display the day and date for my end users. Also, be aware that I deliberately did not display the actual time of the password expiration, only the day. Dropping the actual time makes it easier to do the comparison for password expiration.

Calculating days remaining until password expiration ^

The date calculation comes from a simple date match. I could have done subtraction of today's date versus some future dates, but I thought a date match would be simpler. To perform the date match we need to calculate what the dates are one, three, and seven days from today. I calculated the dates by adding the number of days (1, 3, 7) to today's date and saved them to three separate variables for later use in date comparisons.

$SevenDayWarnDate = (get-date).adddays(7).ToLongDateString() $ThreeDayWarnDate = (get-date).adddays(3).ToLongDateString() $OneDayWarnDate = (get-date).adddays(1).ToLongDateString() 1 2 3 $SevenDayWarnDate = ( get-date ) . adddays ( 7 ) . ToLongDateString ( ) $ThreeDayWarnDate = ( get-date ) . adddays ( 3 ) . ToLongDateString ( ) $OneDayWarnDate = ( get-date ) . adddays ( 1 ) . ToLongDateString ( )

Now that we have the dates, we can compare the password expiry date to the dates in the three variables.

if ($user.PasswordExpiry -eq $SevenDayWarnDate) 1 if ( $user . PasswordExpiry -eq $SevenDayWarnDate )

You can see above that I started an IF statement. For this script, I use an IF/ELSE statement because the password expiration date can only match one day at a time. In other words, a password that expires in three days can only match the three-day warning date. Because of this, an IF/ELSE statement makes it easy to compare the dates and keeping moving until it finds a match.

The descriptive way to read the match statement is like this:

Check to see if the expiry date is same is the same as the seven-day warn date; if it is then run a command, otherwise…

Check whether the password expiry date matches the three-day warn date, then run a command, otherwise…

Check whether the password expiry date matches the one-day warn date, then run a command, otherwise…

Skip this user and move on to the next one.

Here is the IF/ELSE statement simplified:

foreach ($user in $users) { if ($user.PasswordExpiry -eq $SevenDayWarnDate) { RUN A COMMAND } elseif ($user.PasswordExpiry -eq $ThreeDayWarnDate) { RUN A COMMAND } elseif ($user.PasswordExpiry -eq $oneDayWarnDate) { RUN A COMMAND } else {} 1 2 3 4 5 6 7 8 9 10 11 foreach ( $user in $users ) { if ( $user . PasswordExpiry -eq $SevenDayWarnDate ) { RUN A COMMAND } elseif ( $user . PasswordExpiry -eq $ThreeDayWarnDate ) { RUN A COMMAND } elseif ( $user . PasswordExpiry -eq $oneDayWarnDate ) { RUN A COMMAND } else { }

Configuring the mail message sent ^

The cmdlet to send mail via PowerShell is aptly named Send-MailMessage, and the syntax is easy to understand.

Look at the cmdlet syntax below, and notice the cmdlet is looking for string values.

SYNTAX Send-MailMessage [-To] <String[]> [-Subject] <String> [[-Body] <String>] [[‑SmtpServer] <String>] [-Attachments <String[]>] [-Bcc <String[]>] [-BodyAsHtml] [-Cc <String[]>] [-Credential <PSCredential>] [-DeliveryNotificationOption {None | OnSuccess | OnFailure | Delay | Never}] [‑Encoding <Encoding>] -From <String> [-Port <Int32>] [-Priority {Normal | Low | High}] [-UseSsl] [<CommonParameters>] 1 2 3 4 SYNTAX Send-MailMessage [ -To ] < String [ ] > [ -Subject ] < String > [ [ -Body ] < String > ] [ [ ‑ SmtpServer ] < String > ] [ -Attachments < String [ ] > ] [ -Bcc < String [ ] > ] [ -BodyAsHtml ] [ -Cc < String [ ] > ] [ -Credential < PSCredential > ] [ -DeliveryNotificationOption { None | OnSuccess | OnFailure | Delay | Never } ] [ ‑ Encoding < Encoding > ] -From < String > [ -Port < Int32 > ] [ -Priority { Normal | Low | High } ] [ -UseSsl ] [ < CommonParameters > ]

The email message I would like to send to my end users is:

I am a bot and performed this action automatically. I am here to inform you that the password for USERNAME will expire in X days on Long Date. Please contact the helpdesk if you need assistance changing your password. DO NOT REPLY TO THIS EMAIL.

The bold text represents variables. We must do some sting manipulation when trying to put variables in the middle of strings; I chose to use joins. I created some email variables to hold the string text for the email. Each string contains text up to the point where we will place a variable.

$EmailStub1 = 'I am a bot and performed this action automatically. I am here to inform you that the password for' $EmailStub2 = 'will expire in' $EmailStub3 = 'days on' $EmailStub4 = '. Please contact the helpdesk if you need assistance changing your password. DO NOT REPLY TO THIS EMAIL.'$days = 3 $EmailBody = $EmailStub1, $user.name, $EmailStub2, $days, $EmailStub3, $SevenDayWarnDate, $EmailStub4 -join ' ' 1 2 3 4 5 $EmailStub1 = 'I am a bot and performed this action automatically. I am here to inform you that the password for' $EmailStub2 = 'will expire in' $EmailStub3 = 'days on' $EmailStub4 = '. Please contact the helpdesk if you need assistance changing your password. DO NOT REPLY TO THIS EMAIL.' $days = 3 $EmailBody = $EmailStub1 , $user . name , $EmailStub2 , $days , $EmailStub3 , $SevenDayWarnDate , $EmailStub4 -join ' '

The join at the end joins the strings and variables together and separates them each with a single space. Pay close attention to the $days variable. It makes the email message customized for each date match (1, 3, and 7 days).

Sending the message ^

Here you can see my mail send syntax; it's nothing complicated. Even though it's a generic mail send message, the join statement earlier customizes the email body with the right number of days until expiration. Again, I created some variables to hold the important mail server information.

$MailSender = " Password AutoBot <EMAILADDRESS@SOMECOMPANY.com>" $Subject = 'FYI - Your account password will expire soon' $SMTPServer = 'smtp.somecompany.com' 1 2 3 $MailSender = " Password AutoBot <EMAILADDRESS@SOMECOMPANY.com>" $Subject = 'FYI - Your account password will expire soon' $SMTPServer = 'smtp.somecompany.com'

Send-MailMessage -To $user.EmailAddress -From $MailSender -SmtpServer $SMTPServer -Subject $Subject -Body $EmailBody 1 Send-MailMessage -To $user . EmailAddress -From $MailSender -SmtpServer $SMTPServer -Subject $Subject -Body $EmailBody

Now, let's put it all together into a complete script:

## Send-PasswordExpiryEmails.ps1 ## Author: Mike Kanakos - www.networkadm.in ## Created: 2018-03-23 ## ----------------------------- #Import AD Module Import-Module ActiveDirectory #Create warning dates for future password expiration $SevenDayWarnDate = (get-date).adddays(7).ToLongDateString() $ThreeDayWarnDate = (get-date).adddays(3).ToLongDateString() $OneDayWarnDate = (get-date).adddays(1).ToLongDateString() #Email Variables $MailSender = " Password AutoBot <emailaddress@somecompany.com>" $Subject = 'FYI - Your account password will expire soon' $EmailStub1 = 'I am a bot and performed this action automatically. I am here to inform you that the password for' $EmailStub2 = 'will expire in' $EmailStub3 = 'days on' $EmailStub4 = '. Please contact the helpdesk if you need assistance changing your password. DO NOT REPLY TO THIS EMAIL.' $SMTPServer = 'smtp.somecompany.com' #Find accounts that are enabled and have expiring passwords $users = Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False -and PasswordLastSet -gt 0 } ` -Properties "Name", "EmailAddress", "msDS-UserPasswordExpiryTimeComputed" | Select-Object -Property "Name", "EmailAddress", ` @{Name = "PasswordExpiry"; Expression = {[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed").tolongdatestring() }} #check password expiration date and send email on match foreach ($user in $users) { if ($user.PasswordExpiry -eq $SevenDayWarnDate) { $days = 7 $EmailBody = $EmailStub1, $user.name, $EmailStub2, $days, $EmailStub3, $SevenDayWarnDate, $EmailStub4 -join ' ' Send-MailMessage -To $user.EmailAddress -From $MailSender -SmtpServer $SMTPServer -Subject $Subject -Body $EmailBody } elseif ($user.PasswordExpiry -eq $ThreeDayWarnDate) { $days = 3 $EmailBody = $EmailStub1, $user.name, $EmailStub2, $days, $EmailStub3, $ThreeDayWarnDate, $EmailStub4 -join ' ' Send-MailMessage -To $user.EmailAddress -From $MailSender -SmtpServer $SMTPServer -Subject $Subject ` -Body $EmailBody } elseif ($user.PasswordExpiry -eq $oneDayWarnDate) { $days = 1 $EmailBody = $EmailStub1, $user.name, $EmailStub2, $days, $EmailStub3, $OneDayWarnDate, $EmailStub4 -join ' ' Send-MailMessage -To $user.EmailAddress -From $MailSender -SmtpServer $SMTPServer -Subject $Subject -Body $EmailBody } else {} } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 ## Send-PasswordExpiryEmails.ps1 ## Author: Mike Kanakos - www.networkadm.in ## Created: 2018-03-23 ## ----------------------------- #Import AD Module Import-Module ActiveDirectory #Create warning dates for future password expiration $SevenDayWarnDate = ( get-date ) . adddays ( 7 ) . ToLongDateString ( ) $ThreeDayWarnDate = ( get-date ) . adddays ( 3 ) . ToLongDateString ( ) $OneDayWarnDate = ( get-date ) . adddays ( 1 ) . ToLongDateString ( ) #Email Variables $MailSender = " Password AutoBot <emailaddress@somecompany.com>" $Subject = 'FYI - Your account password will expire soon' $EmailStub1 = 'I am a bot and performed this action automatically. I am here to inform you that the password for' $EmailStub2 = 'will expire in' $EmailStub3 = 'days on' $EmailStub4 = '. Please contact the helpdesk if you need assistance changing your password. DO NOT REPLY TO THIS EMAIL.' $SMTPServer = 'smtp.somecompany.com' #Find accounts that are enabled and have expiring passwords $users = Get-ADUser -filter { Enabled -eq $True -and PasswordNeverExpires -eq $False -and PasswordLastSet -gt 0 } ` -Properties "Name" , "EmailAddress" , "msDS-UserPasswordExpiryTimeComputed" | Select-Object -Property "Name" , "EmailAddress" , ` @ { Name = "PasswordExpiry" ; Expression = { [ datetime ] :: FromFileTime ( $_ . "msDS-UserPasswordExpiryTimeComputed" ) . tolongdatestring ( ) } } #check password expiration date and send email on match foreach ( $user in $users ) { if ( $user . PasswordExpiry -eq $SevenDayWarnDate ) { $days = 7 $EmailBody = $EmailStub1 , $user . name , $EmailStub2 , $days , $EmailStub3 , $SevenDayWarnDate , $EmailStub4 -join ' ' Send-MailMessage -To $user . EmailAddress -From $MailSender -SmtpServer $SMTPServer -Subject $Subject -Body $EmailBody } elseif ( $user . PasswordExpiry -eq $ThreeDayWarnDate ) { $days = 3 $EmailBody = $EmailStub1 , $user . name , $EmailStub2 , $days , $EmailStub3 , $ThreeDayWarnDate , $EmailStub4 -join ' ' Send-MailMessage -To $user . EmailAddress -From $MailSender -SmtpServer $SMTPServer -Subject $Subject ` -Body $EmailBody } elseif ( $user . PasswordExpiry -eq $oneDayWarnDate ) { $days = 1 $EmailBody = $EmailStub1 , $user . name , $EmailStub2 , $days , $EmailStub3 , $OneDayWarnDate , $EmailStub4 -join ' ' Send-MailMessage -To $user . EmailAddress -From $MailSender -SmtpServer $SMTPServer -Subject $Subject -Body $EmailBody } else { } }

Here's what an actual email from the script looks like after processing:

The next step would be to set up a scheduled task and run the script daily at certain time. You can copy this script and use it as is, or you can use it as a starting block and customize it to your needs. Once configured, it should greatly reduce the number of calls to the helpdesk for assistance with expired passwords.

Join the 4sysops PowerShell group!

Your question was not answered? Ask in the forum!







