Microsoft Secure Tech Accelerator
Apr 03 2024, 07:00 AM - 11:00 AM (PDT)
Microsoft Tech Community
How to Setup a Password Expiration Notification Email Solution
Published Sep 20 2018 03:34 AM 281K Views

First published on TechNet on May 04, 2015

"Hello World!"

Hi there! Mike Kullish, here. I'm a PFE based out of Minneapolis, MN with a focus on AD, Hyper-V and DFS but I try to help customers with anything on the Windows Desktop and/or Server platforms. I have been with Microsoft for nearly three years and this is my first blog post.

Have you ever had a need to configure notifications for user's password expirations but found that existing solutions didn't quite fit the bill? We all know you can use built-in solutions with Windows and Active Directory/Group Policy but this requires users to interactively log-on to a network-based computer. What about those BYOD or mobile users or users of web apps/email? More often than not, these users will have to call the helpdesk because they had no idea their domain passwords were going to expire. Statistics show that some of the most common calls to the helpdesk are password-related and implementing a process like the one covered here could really make a dent in your helpdesk call volume and costs.

Recently, a customer asked for some help implementing a solution for this issue based on a script they'd found on the Microsoft TechNet Script Center.  The script queries the pwdLastSet attribute of user accounts in AD and the MaxPwdAge property within the domain, then does some time computations and sends an email to those users who are near a password expiration 'event.'

 

I thought it would make a helpful blog post to cover some of the details and considerations when implementing a solution like this. The particular script my customer found was the work of Microsoft MVP Robert Pearman and he deserves the Kudos for initially putting it together, as well as several refinements to it (including support for Fine Grained Password Policies).

DISCLAIMER:

  • PFEs don't normally provide code beyond sample or "proof of concept" code
  • The code we discuss here is an additional layer beyond code from a PFE; it is code from a Microsoft MVP resource
  • As with ANY code, you should always test/validate its behavior in an isolated lab
  • Note the script author has validated the code via his own testing on Windows Server 2008 R2.  Did I mention you should test/validate the code?
  • If/when you're ready to deploy it to production, you should employ a solid change control process and a controlled release. This code can possibly generate emails to 100,000s of users .

You can download the script from the following link . ( https://gallery.technet.microsoft.com/Password-Expiry-Email-177c3e27 )

Click on the blue box and save the file to a workstation or member server. Obviously, a DC would work but likely isn't the best choice. The workstation or member server needs the RSAT tools for Active Directory installed. If you already have an "admin server" system where you have existing scripts, tools, Scheduled Tasks, etc., that would be a logical place for this.

Once you have downloaded the script:

  1. Place the file in a directory on your admin server. (For this example, I will use C:\scripts)
  2. Edit the following portions of the script as applicable using Notepad or PowerShell ISE
    • $smtpServer="mail.domain.com"
      • This will be the name of your SMTP server. The admin server machine will need to be able to send using SMTP – you will likely need to work with your email team to get that process working
    • $expireindays = 21
      • This is the number of days prior to password expiration that you want to notify users. The actual number of days remaining before expiration will be displayed in the email notification.
    • $from = "Company Administrator <support@mycompany.com>"
      • This field can be modified to be sent from a valid email account within your environment. Consideration should be given to this address in order to prevent the perception of a phishing email as well as how replies will be handled .
    • $logging = "Enabled" # Set to Disabled to Disable Logging
      • Logging is recommended to ensure that you can trace any errors that might occur
    • $logFile = "C:\scripts\PasswordExpiration\pwdexp.csv"
      • This field should be changed to a desired location on the local system or network share as desired.
    • $testing = "Enabled"
      • Set to Disabled to email users (configuring this to Enabled, runs a check against all accounts and sends emails ONLY the account specified in the $testRecipient field below.)
        • Configuring this to disabled actually sends emails to the users that will have their passwords expire in the configured amount of time .
        • Understand this - you risk sending out a mass-email to 10s, 100s or 10,000s of users.
        • This is automation – with great power, comes great responsibility
    • $testRecipient = "someone@company.com"
      • Specifies the test user account that will receive the test email. CAUTION : This account will receive 1 email for every user the script identifies.
  3. Save the script once you are done editing it.
  4. Now you can test the script by running it in a lab.
    1. You may need to modify the execution policy for PowerShell scripts on your admin server machine.
  5. You should get an email that looks something like this:

    From: someone@company.com [mailto:someone@company.com]
    Sent: Thursday, March 23, 2015 12:52 PM
    To: Someone@company.com
    Subject: Your Windows password will expire in 4 days.
    Importance: High

    Dear someone,

    Your corporate network password will expire in 4 days.


    To change your password on a PC press CTRL-ALT-Delete and chose "Change Password."

  6. It is important to ensure that you change the section of the script under $body .  The message should be modified to ensure that user's don't accidentally delete the email because they suspect it is spam or a phishing email. Good inter-team collaboration and communication about this "password expiration notification process" cannot be emphasized enough.
    1. Work with your helpdesk and security teams to ensure everyone signs off on this effort and approves the specific text and additional information for the email, including how to manage a 'reply' to that email address
  7. When it all is working as desired/expected, you can disable testing:
    1. $testing = "Disabled"

Now, at some pre-determined time, you or one of your staff can execute the script to generate the 'password expiry notification email' to the affected users.

For those who don't want to manually run the script, it's a simple process to create a Scheduled Task to run the script automatically.

There are numerous other ways to address this need; I have talked to many people who have developed their own processes, scripts and/or code for this. This particular process was pretty easy to implement and I was able to work with my customer to get the whole thing working in a short amount of time.

Thanks to Hilde and all the other PFE bloggers here for helping me "dip a toe" in the blog-pond (or pool?) and a special thanks to Microsoft MVP Robert Pearman who provided some insight and details around his script.

See you all next time!

Mike "CANNONBALL!" Kullish

33 Comments
Copper Contributor

Hello Michael, 

Thanks for your blog and the time you took to create it.

 

Do you guys at Microsoft have a way that we can use the new Multi-Factor Authentication that Office365 requires us to use now?

 

I'm sure that there are a lot of folks out there that would benefit from this update.

Best

Paul

Copper Contributor

Hey Michael

 

I've been trying to get hold of this script but unable to do so with the link. Can anyone share this script with me?

 

Regards

 

Reinhard

Microsoft

I have found an updated copy of the script that can be found here: Original Robert Pearman v1.4 Password Change Notification via https://web.archive.org/web/2016111622...

 

It was posted by Robert Pearman the original script author.

 
Copper Contributor

@Michael Hildebrand I'm using your script in our environment, but need to make some small changes in it, not sure where to change, when user is getting email from us, the email body showing date format like this

"Password will be expiring on 12/28/2020 08:44:22 UTC."

But, I want the date format like this >> it looks like this “5 April 2021 at 18:04:36 UTC”.

 

Microsoft

@Anup_Pachkude You can use the get-date -format switch to modify how the date presents itself. More documenation can be found here: Get-Date (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Docs.

Copper Contributor

Hi @Michael Hildebrand  how often do you recommend running this script? I have set 180 days to expire password.

Thank you

Copper Contributor

@Anup_Pachkude 

Hi i use like this:


$expireson = $passwordsetdate + $maxPasswordAge
$NiceDateExp = Get-Date $expireson -Format "dddd dd.MMMM yyyy at HH:mm"
$today = (get-date)
$daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days

 

Microsoft

@Zdenek_Jurak , The number of times to run the script greatly depends on each organization. I would suggest running the script weekly as a starting point to see if it meets your needs. I hope this helps.

Copper Contributor

@Michael Hildebrand Finally, I set the interval to every other day. By notification 8 days before the password expires. Therefore, the user will receive a maximum of 4 notifications. This will prevent the password from expiring on the weekend. Because a lot of people are in the home office and I use LDAP for VPN authentication. I hope that's enough for that.

 

Thank you, the whole article was very useful for me.

Copper Contributor

@Michael Hildebrand , thanks for a great script. Is it possible to run it with gMSA account to avoid creating another service account with "well-known" password? :)

Microsoft

@kirte You can always setup the script to run as a scheduled task and configure a GMSA. A decent article can be found here: [SOLVED] Using a group managed account to run a scheduled task on Server 2012R2 - Windows Server - S...

Copper Contributor

How would I run this script against entire Forest or against specific Domains within the AD Forest? I have a Parent Domain with multiple child domains but would like to exclude some Domains. I'm guessing it would need to be run against GC using port 3268 somehow.

Microsoft

@Bishop777 , the easiest way would be to run the script from multiple machines. (One in each domain in your forest) You can setup a scheduled task running with a Group Managed Service Account or similar function. 

Copper Contributor

@Michael Kullish , Thanks I suspected this but was hoping I didn't have to.

Copper Contributor

Hi Michael,

You can download the script from the following link . ( https://gallery.technet.microsoft.com/Password-Expiry-Email-177c3e27 )

 

This PS script is not available for download, please help me get it.

 

Microsoft

@amit07 As mentioned above, you can download the script from Robert's blog here: Original Robert Pearman v1.4 Password Change Notification via https://web.archive.org/web/2016111622...

Copper Contributor

Hi Michael,

 

its giving error:

 

PS C:\script> C:\script\Password Change Notification v1.4 Robert Pearman.ps1


Directory: C:\script


Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 6/21/2021 8:46 PM 0 mylog.csv
Send-Mailmessage : Unable to connect to the remote server
At C:\script\Password Change Notification v1.4 Robert Pearman.ps1:120 char:9
+ Send-Mailmessage -smtpServer $smtpServer -from $from -to $ema ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.Mail.SmtpClient:SmtpClient) [Send-MailMessage], SmtpException
+ FullyQualifiedErrorId : SmtpException,Microsoft.PowerShell.Commands.SendMailMessage

Copper Contributor

Is there a way to configure this to use smtp.office365.com using TLS/port 587 and an authenticated user?  ...or, if using my internal SMTP server, how can I get the email to show up FROM "IT"?  Our SMTP server only allows no-reply@company.com so the emails that are received do not show the name, only "No-Reply".

Microsoft

@amit07 It looks like you have one of the variables that is incorrect. Without seeing the entire message, or script, I'm afraid I am not sure how to help with this one.

Microsoft

@rerhart, please take a look at the following link to help with smtp syntax: Send-MailMessage (Microsoft.PowerShell.Utility) - PowerShell | Microsoft Docs, I hope this helps.

Copper Contributor

@ALL ,

Thanks,

I used the script but getting below error 

#####################################################################

PS C:\Users\XXXXX> C:\Passnotification.ps1
Send-Mailmessage : Error in processing. The server response was: 5.7.3 STARTTLS is required to send mail [PN2PR01CA0019.INDPRD01.PROD.OUTLOOK.COM]
At C:\Passnotification.ps1:122 char:9
+ Send-Mailmessage -smtpServer $smtpServer -from $from -to $ema ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.Mail.SmtpClient:SmtpClient) [Send-MailMessage], SmtpException
+ FullyQualifiedErrorId : SmtpException,Microsoft.PowerShell.Commands.SendMailMessage

 

##########################################################################################

So as suggested in comments added "-UseSsl " in the script 
command added:

####################################################################################

 # Send Email Message
Send-Mailmessage -smtpServer $smtpServer -from $from -to $emailaddress -subject $subject -body $body -bodyasHTML -priority High -Encoding $textEncoding -UseSsl"

After adding  "UseSsl" getting below error :

"PS C:\Users\XXXX> C:\Passnotification.ps1
Send-Mailmessage : The SMTP server requires a secure connection or the client was not authenticated. The server response was: 5.7.57 Client not authenticated to send mail.
[PN1PR01CA0079.INDPRD01.PROD.OUTLOOK.COM]
At C:\Passnotification.ps1:122 char:9
+ Send-Mailmessage -smtpServer $smtpServer -from $from -to $ema ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.Mail.SmtpClient:SmtpClient) [Send-MailMessage], SmtpException
+ FullyQualifiedErrorId : SmtpException,Microsoft.PowerShell.Commands.SendMailMessage

##########################################################################

requesting you to please help us and suggest the solution for this.

NOTE: I am testing this from my office system( Win10) on  Windows Powershell ISE

Thanks and Best Regards.

Copper Contributor

Provisioning of Mail Server for this setup is required?

Microsoft

@Cedrick014 Yes, and email server is required. The script is designed to email users, so you would need an SMTP server for it to work.

Copper Contributor

Hi @Michael Kullish , can Office 365 (aka smtp.office365.com along with a dedicated account) be used in place of an SMTP server for this script to work? 

Copper Contributor

Hi @Michael Kullish 

 

Will there be an updated version of the script that will use Graph and Rest API's to send the emails instead of connecting to a SMTP server? As SMTP only supports single factor authentication.

Microsoft

@Graham_Tinkers There is currently no plan that I am aware of, but this is a great idea! Let me consult a few colleagues and see what we can come up with for a future release.

Microsoft

I have good news. A new article will be published very soon regarding how to use O365 to send email eliminating any smtp requirements. Please stay tuned.

Microsoft
Copper Contributor

Is there anyone willing to help me figure out why I can't get this script to display expires on a friendly looking date?  I've added the following and thought I had it right, tested manually the variables and it shows properly but when I run this script it doesn't show the correct date.

I'm happy to send the entire script to someone but I've also copy pasted below what I did.  Help please if anyone is able and willing I'd greatly appreciate it.  I'm trying to have the subject line show the variable results $expirationsdatesubjectline, instead of just x number of days as the original script does.  Unfortunately went out today and all those that fit the formula were told their passwords expired today :(

$date = Get-Date -format ddMMyyyy
$dateFormat = 'MM-dd-yyy'
$messageDays = $daystoexpire
$expirationdate=(Get-Date).AddDays($daystoexpire)
$expirationdatesubjectline= Get-Date -Date $expirationdate -Format $dateFormat

Copper Contributor

Hi @Michael Hildebrand ,

 

Thanks for your topic. I read it all but I want to send the email to notify expired password from workspace google. I try to run your script unsuccessfully. Please help me if I can send it from workspace google. Thanks so much.

 

 

Copper Contributor

I've updated and tested the scripts. Please feel feel to download from here.

https://summalai.com/wp-content/uploads/2022/10/Password-Expire-Email_Public-1.txt

 

Copper Contributor

Dear @Michael Hildebrand

The company i work at, doesn't work on hybrid AD azure, but we do work with Office365 (E3 License's) & AD Server.

Our users not getting an email notification before their 365 password expires. Whis means when they sign in to Office 365 and their password has expired, they'll be prompted to change their password at that time without any advance notice.

 

Now, I spoke with Microsoft ISR support representative & sent this article, they said is ONLY relevant for hybrid azure AD.

May you please tell me if is true ? i can't believe there's no answer to our issue, i must a soulution.

I found another this kind of PS script.. please take a look.

 

#################################################################################################################
# Version 1.0 September 2016
# Fernando Pérez
# Based on Robert Pearman (WSSMB MVP)
#
# Script to Automated Email using Office 365 account to remind users Passwords Expiracy.
# Office 365 require SSL
# Requires: Windows PowerShell Module for Active Directory
#
#
##################################################################################################################
# Please Configure the following variables....
$smtpServer="smtp.office365.com" # Office 365 official smtp server
$expireindays = 10 # number of days for password to expire
$from = "Your email address <email address removed for privacy reasons>" # email from
$logging = "Enabled" # Set to Disabled to Disable Logging
$logFile = "c:\Scripts\PasswordChangeNotification.csv" # ie. c:\Scripts\PasswordChangeNotification.csv
$testing = "Disabled" # Set to Disabled to Email Users
$testRecipient = "email address removed for privacy reasons"
$date = Get-Date -format ddMMyyyy
#
###################################################################################################################

# Add EMAIL Function
Function EMAIL{

Param(
$emailSmtpServer = $smtpServer, #change to your SMTP server
$emailSmtpServerPort = 587,
$emailSmtpUser = "email address removed for privacy reasons", #Email account you want to send from
$emailSmtpPass = "passsword", #Password for Send from email account
$emailFrom = "email address removed for privacy reasons", #Email account you want to send from
$emailTo,
$emailAttachment,
$emailSubject,
$emailBody
)
Process{

$emailMessage = New-Object System.Net.Mail.MailMessage( $emailFrom , $emailTo )
$emailMessage.Subject = $emailSubject
$emailMessage.IsBodyHtml = $true
$emailMessage.Priority = [System.Net.Mail.MailPriority]::High
$emailMessage.Body = $emailBody

$SMTPClient = New-Object System.Net.Mail.SmtpClient( $emailSmtpServer , $emailSmtpServerPort )
$SMTPClient.EnableSsl = $true
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential( $emailSmtpUser , $emailSmtpPass );

$SMTPClient.Send( $emailMessage )
}
}

# Check Logging Settings
if (($logging) -eq "Enabled")
{
# Test Log File Path
$logfilePath = (Test-Path $logFile)
if (($logFilePath) -ne "True")
{
# Create CSV File and Headers
New-Item $logfile -ItemType File
Add-Content $logfile "Date,Name,EmailAddress,DaystoExpire,ExpiresOn"
}
} # End Logging Check

# Get Users From AD who are Enabled, Passwords Expire and are Not Currently Expired
Import-Module ActiveDirectory
$users = get-aduser -filter * -properties Name, PasswordNeverExpires, PasswordExpired, PasswordLastSet, EmailAddress |where {$_.Enabled -eq "True"} | where { $_.PasswordNeverExpires -eq $false } | where { $_.passwordexpired -eq $false }
$DefaultmaxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge

# Process Each User for Password Expiry
foreach ($user in $users)
{
$Name = $user.Name
$emailaddress = $user.emailaddress
$passwordSetDate = $user.PasswordLastSet
$PasswordPol = (Get-AduserResultantPasswordPolicy $user)
# Check for Fine Grained Password
if (($PasswordPol) -ne $null)
{
$maxPasswordAge = ($PasswordPol).MaxPasswordAge
}
else
{
# No FGP set to Domain Default
$maxPasswordAge = $DefaultmaxPasswordAge
}

$expireson = $passwordsetdate + $maxPasswordAge
$today = (get-date)
$daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days

# Set Greeting based on Number of Days to Expiry.

# Check Number of Days to Expiry
$messageDays = $daystoexpire

if (($messageDays) -ge "1")
{
$messageDays = "in " + "$daystoexpire" + " days."
}
else
{
$messageDays = "today."
}

# Email Subject Set Here
$subject="Your password will expire $messageDays"

# Email Body Set Here, Note You can use HTML, including Images.
$body ="
<p>Dear $name,<br></P><br>
<p>Your Password will expire $messageDays.<br>
Please change your password before it expires to avoid problems accessing to your work services. <br></P><br>
<p>Thanks, <br>
</P><br><br>
<p>Dear $name,<br></P><br>
<P>Su contrase&ntilde;a caducar&aacute; en $daystoExpire d&iacute;as.<br>
Por favor cambie su contrase&ntilde;a antes de que &eacute;sta expire para evitar problemas al acceder a su entorno de trabajo. <br></P><br>
<P>Gracias, <br>
</P><br><br>
<p>Caro $name,<br></P><br>
<p>A sua password vai expirar dentro de $daystoExpire dias<br>
Por favor altere a mesma antes dela expirar de forma a evitar ter problemas de acesso ao seu ambiente de trabalho. <br></P><br>
<p>Obrigado, <br>
</P>"


# If Testing Is Enabled - Email Administrator
if (($testing) -eq "Enabled")
{
$emailaddress = $testRecipient
} # End Testing

# If a user has no email address listed
if (($emailaddress) -eq $null)
{
$emailaddress = "email address removed for privacy reasons"
}# End No Valid Email

# Send Email Message
if (($daystoexpire -ge "0") -and ($daystoexpire -lt $expireindays))
{
# If Logging is Enabled Log Details
if (($logging) -eq "Enabled")
{
Add-Content $logfile "$date,$Name,$emailaddress,$daystoExpire,$expireson"
}

EMAIL -emailTo $emailaddress -emailSubject $subject -emailBody $body

} # End Send Message

} # End User Processing

 

# End

#################################################################################################################

 

Regards,

Daniel, IT Administrator.

email address removed for privacy reasons

Copper Contributor

I've built a portal that makes it even easier to schedule email notifications via a self service portal https://infrasos.com/active-directory-password-reset-tool-self-service-portal/

Version history
Last update:
‎Oct 13 2021 08:43 AM
Updated by: