Sending custom “password is about to expire” notifications with PowerShell

Sending out reminders to your users about changing their password before it expires could really take some load of the Helpdesk, and there are a few scripts available that does just that. What I found was that most of these scripts were assuming that everyone in the organization should get the same e-mail, which is not always true.

Therefore I wrote a new one where you could specify how the e-mail should look depending on where in your organization the user works. In our case, we needed to have different languages for different countries, the instructions were different aswell (some users needed to use a password change portal to change their passwords, others are using the classic “CTRL + ALT + DELETE”-method). We also have different contact information for the helpdesk in those countries and departments.

I will do a walk-through of this script to help you customize it to fit your organisation.

The first thing you need to do, is to decide when the users should receive a notification. This is done with these variables:

# Set when users should get a warning...

# First time
$FirstPasswordWarningDays = 14

# Second time
$SecondPasswordWarningDays = 7

# Last time
$LastPasswordWarningDays = 3

Users will then receive a warning 14 days, 7 days and finally 3 days before the password expires. Depending on how the number of days are rounded in the e-mail, it may differ a day from what you specify here.

You then need to set what smtp-server to use:

# Set SMTP-server
$SMTPServer = "MySMTP.Contoso.Com"

There is no need to change the next part of the code, it basically is just calculating when passwords will expire based on your domain policy and what you set in the variables above. It looks like this:

# Get the password expires policy
$PasswordExpiresLength = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge

# Calculating when passwords would have been set if they expire today
$CurrentPWChangeDateLimit = (Get-Date).AddDays(-$PasswordExpiresLength.Days)

# Calculating all dates
$FirstPasswordDateLimit = $CurrentPWChangeDateLimit.AddDays($FirstPasswordWarningDays)
$SecondPasswordDateLimit = $CurrentPWChangeDateLimit.AddDays($SecondPasswordWarningDays)
$LastPasswordDateLimit = $CurrentPWChangeDateLimit.AddDays($LastPasswordWarningDays)

We are now ready to load the users, you might want to adjust the filter a bit depending on how your Active Directory design is. Just edit the first part of the filter (Mail -like ‘*@*’) to do this.

# Load the users
$MailUsers = Get-ADUser -Filter "(Mail -like '*@*') -AND `
(PasswordLastSet -le '$FirstPasswordDateLimit' -AND PasswordLastSet -gt '$($FirstPasswordDateLimit.AddDays(-1))' -OR `
PasswordLastSet -le '$SecondPasswordDateLimit' -AND PasswordLastSet -gt '$($SecondPasswordDateLimit.AddDays(-1))' -OR `
PasswordLastSet -le '$LastPasswordDateLimit' -AND PasswordLastSet -gt '$($LastPasswordDateLimit.AddDays(-1))') -AND `
(PasswordNeverExpires -eq '$false' -AND Enabled -eq '$true')" -Properties PasswordLastSet, DisplayName, PasswordNeverExpires, mail

We now got all the users, we just need to loop through them and send out the e-mails. This is where we need to specify which users should get which e-mail.

The comments should give you the information you need to customize it for your environment. Make sure you check out lines 13 and 19 below and change “MyOU1” and “MyOU2” to match your Organizational Units in Active Directory.

# Loop through them
foreach ($MailUser in $MailUsers) {

# Count how many days are left before the password expires and round that number
$PasswordExpiresInDays = [System.Math]::Round((New-TimeSpan -Start $CurrentPWChangeDateLimit -End ($MailUser.PasswordLastSet)).TotalDays)

# Write some status...
Write-Output "$($MailUser.DisplayName) needs to change password in $PasswordExpiresInDays days."

# Build the body depending on where in the organisation the user is

# Change MyOU1 to match your the OU you want your users are in.
if ($MailUser.DistinguishedName -like "*MyOU1*") {
$Subject = "Your password is expiring in $PasswordExpiresInDays days"
$Body = "Hi $($MailUser.DisplayName),

Your password is expiring in $PasswordExpiresInDays days. Please change it now!

Don't forget to change it in your mobile devices if you are using mailsync.

Helpdesk 1"
$EmailFrom = "Helpdesk 1 <[email protected]>"
# Change MyOU2 to match your environment
elseif ($MailUser.DistinguishedName -like "*MyOU2*") {
$Subject = "Your password is expiring in $PasswordExpiresInDays days"
$Body = "Hi $($MailUser.DisplayName),

Your password is expiring in $PasswordExpiresInDays days. Please change it now!

Don't forget to change it in your mobile devices if you are using mailsync.

Helpdesk 2"
$EmailFrom = "Helpdesk 2 <[email protected]>"
# This is the default e-mail
else {
$Subject = "Your password is expiring in $PasswordExpiresInDays days"
$Body = "Hi $($MailUser.DisplayName),

Your password is expiring in $PasswordExpiresInDays days. Please change it now!

Don't forget to change it in your mobile devices if you are using mailsync.

Helpdesk 3"
$EmailFrom = "Helpdesk 3 <[email protected]>"

# Time to send the e-mail

# The line below might need changing depending on what SMTP you are using (authentication or not)
Send-MailMessage -Body $Body -From $EmailFrom -SmtpServer $SMTPServer -Subject $Subject -Encoding UTF8 -BodyAsHtml -To $MailUser.mail

# E-mail is sent!

You also need to change the body/subject/mailfrom variables to match what you want to send out. Just add more “elseif”-clauses if you want to send out more versions, or remove them if you don’t need them.

And make sure you configure the “Send-MailMessage”-cmdlet correctly to use your smtp-server if you use authentication or a different port.

All you need to do after that is to schedule the script to run at the same time every day and you are done! (And some testing of course… πŸ™‚ )

Leave a comment if you have any questions!

The complete and uncut code is available here.

12 thoughts on “Sending custom “password is about to expire” notifications with PowerShell

  1. Timmy

    First thanks for share this script.

    I have one problem.
    I define, that our users (2 OU’s) will receive a notify, 14, 7 and 2 days before pw expire…
    The problem is now, that a lot of users will receive a notification, that the password will expires in 34 days, or 61 days, and and and.

    I can not see the problem in this script.
    Maybe you have an answer for this problem?


    1. Anders Post author

      Thank you for reading!

      That seems really odd, does that affect all users or only some of them? If you run:
      Does it work? It should return the maximum allowed age of passwords in your domain.

      Have you modified anything else in the script except for the OUs and the last password warning variable?

      I’ll try to drop you an e-mail aswell if you prefer troubleshooting it that way!


    2. Timmy

      OK i found the problem…
      It is a region problem. If you have setup the server to “German (Switzerland)” the Date format is dd.MM.yyyy
      The script send it in “English (US)” format. M/d/yyyy.

      If i change the settings on server to English US and voila… now the right persons will receive a message.

  2. Timmy

    Yup… now it works fine… πŸ™‚

    Another Question.
    Maybe you know it. What for a command i need, if i want to send the Mails also to our Servicedesk?
    But only the user has not changed the password on “LastPasswordWarningDays”

    What i mean is.
    The Users will receive a mail on 14, 7 and on 2 days if he hasn’t change it. If he hasn’t change by the last remember mail, the SD will also inform…


    1. Anders Post author

      Should be easy enough, you could probably figure it out by looking at the above script, or do something like this (but add the Send-MailMessage part):

      $PasswordExpiresLength = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge

      $UsersWithExpiredPasswords = Get-ADUser -Filter "PasswordLastSet -le '$((Get-Date).AddDays(-$PasswordExpiresLength.Days))' -AND PasswordLastSet -gt '$((Get-Date).AddDays(-($PasswordExpiresLength.Days+1)))' -AND `
      (PasswordNeverExpires -eq '$false' -AND Enabled -eq '$true')" -Properties PasswordLastSet, DisplayName, PasswordNeverExpires, mail

      Write-Output "The following users passwords expired in the last day: $($UsersWithExpiredPasswords.SamAccountName -join ', ')"

    1. Anders Post author

      Thank you for reading! Not entirely sure what you mean, but it works fine if the users are synced from the local AD (this is how I’ve used it in the past) to Azure AD/Office 365.

      But if the users have a “CloudId” only, then the script needs some changes to work since it’s using the PowerShell module for AD in it’s current form.

      If you are using the smtp-service in Office 365, you need to do some changes to the Send-MailMessage-cmdlet at the end (it’s using tls and requires authentication), but it’s pretty simple to do!

      Feel free to get back to me if you have further questions!

  3. Yugesh

    Hi i am getting the following error :

    Send-MailMessage : Cannot validate argument on parameter ‘Body’. The argument is null or empty. Supply an argument that is not null or empty and then try the command again.
    At C:\Users\eisadm\Desktop\pass2.ps1:85 char:24
    + Send-MailMessage -Body $Body -From $EmailFrom -SmtpServer $SMTPServer -Subject $ …
    + ~~~~~
    + CategoryInfo : InvalidData: (:) [Send-MailMessage], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.SendMailMessage

    1. Anders Post author

      Hi! Hard to tell what the problem is without seeing the code. But check your logic for setting the $Body-variable, sounds like that is null/not set in this case. Did you perhaps change the if/else logic in the end and then ran into a case where none of them were evaluated as true?

  4. Rkelly

    Can this be used in an SSIS package? If so, would you be able to post a solution?


Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.