It is common practice for an organization to have a password expiration policy which requires users to reset their password on a regular basis, often yearly and sometimes even more frequently. Despite evidence that this results in weaker passwords, it’s still prevalent in many organizations. However, the built-in functionality does not provide email notification for users to reset their password and the Window’s notifications might be missed by the end user if they’re even enabled to begin with. In this post, I’ll walk you through setting up a PowerShell script to notify users via email, prior to the password expiring.
Before we begin, I’d like to define the scope of work.
- We need a standard email template for notifying users when their password is expiring.
- We need a PowerShell script to identify users whose passwords are coming up for expiration.
- We want the process to be automated so that we can set it and forget it.
Creating the Password Expiration Email
We will start off by crafting the email message that we’ll use as a template when notifying each user. Below is the notification email we’ll be using.
{DISPLAYNAME},
Your password will expire in {DAYS} day(s). Please change your password before the expiration date to prevent your account from becoming locked.
To change your password, follow the steps below.
- From your company PC, press CTRL + ALT + Delete.
- Click Change a password.
- Follow the on screen instructions, then click Submit.
If you experience any issues, please contact the help desk.
Rather than sending the user a basic text-based email, I wanted something a little more elegant and to have the ability to add branding and formatting to my notification email. I’m no expert in creating HTML emails that are compatible with various email clients and web browsers, however, a quick Google search will provide exactly what we need. We’ll borrow the following template from Lee Munroe, who’s been generous enough to provide the following template for free use.
https://github.com/leemunroe/responsive-html-email-template
<!doctype html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Your password will expire on {DATE}</title> <style> @media only screen and (max-width: 620px) { table.body h1 { font-size: 28px !important; margin-bottom: 10px !important; } table.body p, table.body ul, table.body ol, table.body td, table.body span, table.body a { font-size: 16px !important; } table.body .wrapper, table.body .article { padding: 10px !important; } table.body .content { padding: 0 !important; } table.body .container { padding: 0 !important; width: 100% !important; } table.body .main { border-left-width: 0 !important; border-radius: 0 !important; border-right-width: 0 !important; } table.body .btn table { width: 100% !important; } table.body .btn a { width: 100% !important; } table.body .img-responsive { height: auto !important; max-width: 100% !important; width: auto !important; } } @media all { .ExternalClass { width: 100%; } .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div { line-height: 100%; } .apple-link a { color: inherit !important; font-family: inherit !important; font-size: inherit !important; font-weight: inherit !important; line-height: inherit !important; text-decoration: none !important; } #MessageViewBody a { color: inherit; text-decoration: none; font-size: inherit; font-family: inherit; font-weight: inherit; line-height: inherit; } .btn-primary table td:hover { background-color: #34495e !important; } .btn-primary a:hover { background-color: #34495e !important; border-color: #34495e !important; } } </style> </head> <body style="background-color: #f6f6f6; font-family: sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;"> <span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">{DISPLAYNAME}, your password will expire on {DATE}.</span> <table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #f6f6f6; width: 100%;" width="100%" bgcolor="#f6f6f6"> <tr> <td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top"> </td> <td class="container" style="font-family: sans-serif; font-size: 14px; vertical-align: top; display: block; max-width: 580px; padding: 10px; width: 580px; margin: 0 auto;" width="580" valign="top"> <div class="content" style="box-sizing: border-box; display: block; margin: 0 auto; max-width: 580px; padding: 10px;"> <!-- START HEADER --> <div class="footer" style="clear: both; margin-top: 10px; text-align: left; width: 100%;"> <table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%"> <tr> <td class="content-block" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: 24px; text-align: left;" valign="top" align="left"> <!-- Update the image src to the absolute image path, e.g. https://company.com/logo.png --> <img src="microsoft_icon.png" alt="Microsoft" width="128" height="32" border="0" style="border:0; outline:none; text-decoration:none; display:block;"> </td> </tr> </table> </div> <!-- END HEADER --> <!-- START CENTERED WHITE CONTAINER --> <table role="presentation" class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background: #ffffff; border-radius: 3px; width: 100%;" width="100%"> <!-- START MAIN CONTENT AREA --> <tr> <td class="wrapper" style="font-family: sans-serif; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 20px;" valign="top"> <table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%"> <tr> <td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top"> <p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; margin-bottom: 15px;">{DISPLAYNAME},</p> <p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; margin-bottom: 15px;">Your password will expire in {DAYS} day(s). Please change your password before the expiration date to prevent your account from becoming locked.</p> <p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; margin-bottom: 15px;">To change your password, follow the steps below.</p> <ul> <li>From your company PC, press <strong>CTRL</strong> + <strong>ALT</strong> + <strong>Delete</strong>.</li> <li>Click <strong>Change a password</strong>.</li> <li>Follow the on screen instructions, then click <strong>Submit</strong>.</li> </ul> <p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; margin-bottom: 15px;">If you experience any issues, please contact the help desk.</p> </td> </tr> </table> </td> </tr> <!-- END MAIN CONTENT AREA --> </table> <!-- END CENTERED WHITE CONTAINER --> <!-- START FOOTER --> <div class="footer" style="clear: both; margin-top: 10px; text-align: center; width: 100%;"> <table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%"> <tr> <td class="content-block" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: 12px; text-align: center;" valign="top" align="center"> <span class="apple-link" style="color: #999999; font-size: 12px; text-align: center;">Microsoft Corporation, One Microsoft Way Redmond, WA 98052-6399 USA</span> </td> </tr> <tr> <td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: 12px; text-align: center;" valign="top" align="center"> Powered by <a href="http://htmlemail.io" style="color: #999999; font-size: 12px; text-align: center; text-decoration: none;">HTMLemail</a>. </td> </tr> </table> </div> <!-- END FOOTER --> </div> </td> <td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top"> </td> </tr> </table> </body> </html>
I’ve modified the template slightly to include a company logo and inserted our own text with placeholders which we’ll replace later in our PowerShell script with user-specific details. I’m not going to dive into HTML or inline CSS but feel free to modify the text to your liking. The logo can be replaced easily, just make sure to update the dimensions on line #114.
Writing the PowerShell Script to Identify Active Directory Users with Expiring Passwords
Our first step in this process is to use PowerShell to identify when a user’s password is set to expire. We can collect this information using the Get-ADUser cmdlet and selecting the msDS-UserPasswordExpiryTimeComputed property. In addition, based on our email template, we know that we’ll need the user’s name and email address. We can retrieve both with the DisplayName and EmailAddress properties.
[note]If you’re unsure of what properties are available for an object, you can pipe it to the Get-Member cmdlet which will list the properties and methods that are available. E.g. Get-ADUser | Get-Member
Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False} –Properties "DisplayName", "EmailAddress", "msDS-UserPasswordExpiryTimeComputed"
We will want to store the user information in a variable called $Users that we’ll use later. In addition, we should define the intervals at which a user should be notified. I’ve chosen to notify the users 14 days, 7 days, 1 day, and 0 days before their password expires. I’ve put these values in an array and stored it in the variable $NotifyInterval.
$Users = Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False} –Properties "DisplayName", "EmailAddress", "msDS-UserPasswordExpiryTimeComputed" $NotifyInterval = @(14,7,1,0)
Next, we’ll create a foreach loop to iterate through each user in our users list. For each user, we will want to perform a calculation to determine the distance between today’s date and the date the user’s password expires. We can then compare those results to see if the distance between them matches any of the days in our notification interval variable, $NotifyInterval.
foreach ($User in $Users){ $Today = Get-Date $ExpiryDate = [datetime]::FromFileTime($User.'msDS-UserPasswordExpiryTimeComputed') $DaysToExpiry = ($ExpiryDate - $Today).Days # Check if the number of days until the user's password expires is in the list of days to notify. if ($DaysToExpiry -in $NotifyInterval) { # Do this if password is set to expire } }
Let’s break this down and look at which each line is doing.
$Today = Get-Date
First, we need to get the current date. We use the Get-Date cmdlet and store this in a variable, $Today.
$ExpiryDate = [datetime]::FromFileTime($User.'msDS-UserPasswordExpiryTimeComputed')
Next, we convert the user’s msDS-UserPasswordExpiryTimeComputer property from the type of Int64, to the type of DateTime, and store this in a variable called $ExpiryDate. This is necessary so that we can calculate the distance between the user’s password expiration and the current date retrieved by the Get-Date cmdlet which also has the type of DateTime.
$DaysToExpiry = ($ExpiryDate - $Today).Days
Here we subtract today’s date from the expiration date which gives us the number of days between them.
if ($DaysToExpiry -in $NotifyInterval) { # Do this if password is set to expire }
Lastly, we have an if statement to see if the value of $DaysToExpiry is in our $NotifyInterval array. If true, we’ll need to notify the user via email.
Formatting and Sending the Email Notification
Our PowerShell script is almost complete. We just have to update the place holders in our email body and send the message in our previously written if statement.
$Subject = "Password expires in $DaysToExpiry day(s)" $Body = (Get-Content -Path "C:\Send-PasswordExpiryNotification\Email_Template\email-inlined.html").Replace("{DISPLAYNAME}",$User.DisplayName).Replace("{DATE}",$ExpiryDate).Replace("{DAYS}",$DaysToExpiry) Send-MailMessage -To $User.EmailAddress -Subject $Subject -From noreply@mattlucas.net -SmtpServer localhost -BodyAsHtml $Body -UseSsl
We’ve accomplished 3 things here.
- First, we create a variable that contains the subject line we’ll use when sending our email.
- Next, we get the content of our email template and use a nested replace method to insert the user’s display name, the password expiration date, and the number of days until expiration, then store this in a variable called $Body.
- And finally, we use the Send-MailMessage cmdlet to send the email message to the end user and pass the subject and body we previously defined.
I should mention, I’m not using a username and password to authenticate with the SMTP server in this example. Instead, I’ve used Microsoft Office 365 Direct Send functionality since these emails will remain internal.
The Completed Script
Below is the completed script. You’ll notice that a few improvements have been made so that we can easily reuse this for different environments.
- The script now accepts parameters for the notification intervals, the from address, the SMTP server, and the email template.
- We moved the $Today variable, which calls the Get-Date cmdlet, outside of the foreach loop. This only needs to be run once and doesn’t need to be checked for each user.
param ( [Parameter(Mandatory)] [Int[]]$NotifyInterval, [Parameter(Mandatory)] [String]$From, [Parameter(Mandatory)] [String]$SmtpServer, [Parameter(Mandatory)] [String]$EmailTemplate ) # Get the content from our Email Template so that we can manipulate it later $EmailTemplate = Get-Content -Path $EmailTemplate # Get all AD users who are enabled and have passwords which are set to expire $Users = Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False} –Properties "DisplayName", "EmailAddress", "msDS-UserPasswordExpiryTimeComputed" $Today = Get-Date foreach ($User in $Users){ $ExpiryDate = [datetime]::FromFileTime($User.'msDS-UserPasswordExpiryTimeComputed') $DaysToExpiry = ($ExpiryDate - $Today).Days # Check if the number of days until the user's password expires is in the list of days to notify. if ($DaysToExpiry -in $NotifyInterval) { $Subject = "Password expires in $DaysToExpiry day(s)" $Body = $EmailTemplate.Replace("{DISPLAYNAME}",$User.DisplayName).Replace("{DATE}",$ExpiryDate).Replace("{DAYS}",$DaysToExpiry) Send-MailMessage -To $User.EmailAddress -Subject $Subject -From $From -SmtpServer $SmtpServer -BodyAsHtml $Body -UseSsl } }
Automate the Process with Task Scheduler
The last step in this process is to automate everything using Task Scheduler.
- Open the Task Scheduler, then under Actions, click Create Task…
- On the General tab, use the following settings.
- Name: Send Password Expiry Notification
- Under Security Options, select a user who has the proper AD permissions and choose Run whether the user is logged on or not.
- From the Triggers tab, click New.
- Begin the task: On a schedule.
- Choose Daily, then select your start date and time.
- Make sure the Enabled button is checked.
- From the Actions tab, click New.
- Action: Start a program
- Program/script: PowerShell.exe
- Add arguments (optional): -File C:\Scripts\Send-PasswordExpiryNotification.ps1 -NotifyInterval 14,7,1,0 -From noreply@mattlucas.net -SmtpServer “mattlucas-net.mail.protection.outlook.com” -EmailTemplate C:\Scripts\Send-PasswordExpiryNotification\Email_Template\email-inlined.html
- Lastly, click the OK button to create the task.
With the new task in place, the PowerShell script will run daily to check for users with expiring passwords and if applicable, will send them an email notification prior to their password expiration date.
The Final Product
We now have a final product which we can reuse in multiple environments. Use the links below to download the HTML email template and PowerShell scripts, then follow the instructions under Automate the Process with Task Scheduler to configure everything for your environment.
If you have any questions, concerns, or recommendations, please leave a comment down below.