Password Expiry Notification Script

Going back to basics can often be a good solution to a problem. Emailing users letting them know that their password will expire soon is usually the most broad way of letting everyone know. If they are using ActiveSync only to get their emails, they won’t be notified when their password expires until it stops working.

With that in mind, I set out to find a simple script that runs daily, to let people know when their password is due to expire.

There’s a lot out there, but I wanted to use PowerShell and set it as a daily scheduled task.

Technet had a great one here from Johan Dahlbom. Except it didn’t work for me, as I recieved the error when testing:

get-aduser : One or more properties are invalid.

After some research, I found this blog post which had my exact issue. It seems that PowerShell v4 which comes with Windows 8.1 and Windows Server 2012 R2 doesn’t like the wildcard for -properties when running a get-aduser command, such as :

get-aduser -filter * -properties *

Richard Siddaway’s solution was to pipe it out and use get-object instead, but that doesn’t give all the same results as the original.

Instead I chose to specify the actual fields needed which turned the command into:

get-aduser -filter * -properties enabled, passwordneverexpires

That worked perfectly. So after adjusting a few parts of the script, I had it working.

I then decided that I didn’t want a daily email going out saying ‘You have 7 days” then “You have 6 days” etc, but just 2 variables – 7 days and 1 day.

So, here is the script (downloadable here: Password Change Notification)

# Please Configure the following variables….
# expireindays1 + 2 = At what count of days left on a password do you want a notification?
$expireindays1 = 7
$expireindays2 = 1
$from = “Name <[email protected]>”

#Get Users From AD who are enabled
Import-Module ActiveDirectory
$users = get-aduser -filter * -Properties enabled, passwordneverexpires, passwordexpired, emailaddress, passwordlastset |where {$_.Enabled -eq “True”} | where { $_.PasswordNeverExpires -eq $false } | where { $_.passwordexpired -eq $false }

foreach ($user in $users)
$Name = (Get-ADUser $user | foreach { $_.Name})
$emailaddress = $user.emailaddress
$passwordSetDate = (get-aduser $user -properties passwordlastset | foreach { $_.PasswordLastSet })
$PasswordPol = (Get-AduserResultantPasswordPolicy $user)
# Check for Fine Grained Password
if (($PasswordPol) -ne $null)
$maxPasswordAge = ($PasswordPol).MaxPasswordAge

$maxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge

$expireson = $passwordsetdate + $maxPasswordAge
$today = (get-date)
$daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days
$subject=”Your password will expire in $daystoExpire days”
$body =”
Dear $name,
<p> Your password will expire in $daystoexpire day(s).<br>
To change your password, do these things<br>
For remote password changes, sign in to this address and change it there’ <br>
<p>Thanks, <br>

if (($daystoexpire -eq $expireindays1) -or ($daystoexpire -eq $expireindays2))
Send-Mailmessage -smtpServer $smtpServer -from $from -to $emailaddress -subject $subject -body $body -bodyasHTML -priority High



43 thoughts on “Password Expiry Notification Script

  1. Hello,

    your script looks great, now do I implement this script in AD or in my Exchange? and if I only run it once in my AD or my Exchange? and it should sent emails every time the user’s password is about to expire.

  2. Thanks, the script is going really well for my company too since deploying, no complaints. Implement in AD (anywhere really, as it imports the AD module), there’s no Exchange commands in this. You’d specify your smtp server (which is probably on exchange!) near the end of the script. You’d need to schedule this script to run daily or at whatever interval you want, using scheduled tasks is the easiest way. I do it at midnight daily.

    To test, just change the last part of the script from “-to $emailaddress” to “to [email protected]” and you’ll see all the emails yourself instead of going out to end users.

    Let us know how you go!

  3. Running into a issue with the script, and not sure where to go with it.
    Running the script on a DC with admin creds as a scheduled task. during the run job i receive an error message with the following –
    Send-MailMessage : Cannot validate argument on parameter ‘To’. The argument is null or empty. Supply an argument that is not null or empty and then try the command again.
    At %Location%\passwordnotification.ps1:52 char:62
    + Send-MailMessage – smtpServer $smtpserver -from $from -to $emailaddress -subje….. – With the $emailaddress underlined
    + CategoryInfo :InvalidData: (:) [Send-MailMessage], ParameterBindingValidationException
    + FullyQualifiedErrorID : ParameterArgumentValidationError, Microsoft.Powershell.Commands.SendMailMessage

    Thanks in advance for your help!

    1. Hi Sam,
      You can work backwards from this. Seems like it has an issue with the $emailaddress variable. Earler in the script you can see $emailaddress = $user.emailaddress. Hard code that to your own address so it reads $emailaddress = [email protected] and test.

      If that then works, look at $user.emailaddress which gets a bit more tricky. $user is set from the results above it, and then it’s getting the email address field. Grab the command that sets the $users (note the S on the end) and change that to one user instead of wildcard *, and then see if you can get the email address out of that.

      In saying all that, does the script work? You might just be getting some results back of accounts that don’t have email addresses, which would be safe to ignore.

  4. Hi Adam,
    I was just wondering if this will work on Zimbra email server. Or is it designed for Exchange only?
    I am looking for a script that will email our users when their AD password is about to expire when I bumped into your page. TiA

    1. Hi Mervin,
      Test the send-mail message command separately first, if that works against your SMTP server. It also relies on the email field being used in AD. I haven’t used Zimbra before but if those two parts are OK then it should work.

      Let me know how you go with it!

  5. Adam, This has been a big help for our company. The only problem i have is that I have set the $expireindays = 21 and it doesn’t seem to notify them until about 10 days before. Is there something else i should change?


    1. OK time to do some troubleshooting. $expireindays1 = 21, so we need to test that the variable works. After running the script, type ‘echo $expireindays1’ and it should come back with 21. Easy. Next, it only triggers if $daystoexpire -eq $expireindays1, you could try changing that to just say 21 and see if it works or not. If not, you’ll have to work backwards through the variables in the script to try and work out what it doesn’t like. It could be the MaxPasswordAge setting from Get-ADDefaultDomainPasswordPolicy where some maths doesn’t add up nice when you’ve got it set to 21.

      I can’t see anything else obvious which might cause that issue, so really run through it step by step and check each variable is giving back the result you want and go from there. Let us know how you go!

  6. Hi Adam,

    thanks for your blog. As i am new to windows, could you please suggest where and how i can run this script. Can i run this on a system which is connected to AD via powershell or this should run on AD only?

    I got following error when i ran this script on computer connected to AD which is Windows 7 professional R2 Powershell.

    PS C:\Windows\system32> & ‘D:\Documents\LDAP\Active Directory\PasswordNotify_new
    You must provide a value expression on the right-hand side of the ‘-eq’ operato
    At D:\Documents\LDAP\Active Directory\PasswordNotify_new.ps1:12 char:143
    + $users = get-aduser -filter * -Properties enabled, passwordneverexpires, pass
    wordexpired, emailaddress, passwordlastset |where {$_.Enabled -eq <<<< â?oTrue
    â??} | where { $_.PasswordNeverExpires -eq $false } | where { $_.passwordexpire
    d -eq $false }
    + CategoryInfo : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : ExpectedValueExpression

    Appreciate your help.


    1. Hi Sam,
      Bit hard to tell from what you pasted, seems to have added some special characters in, but it is expecting something after one of the -eq bits (143 characters into the line). Did you try downloading the script I linked rather than copying/pasting the text on this page?

      The error you’re getting means you should be fine the way you’re running it, otherwise you’d be getting errors about an unknown cmdlet, or unable to connect to AD.

  7. This is awesome, I have adjusted the searchbase in the third line, so it only lifts the users from a specific OU, like:
    From so:
    $users = get-aduser -filter * etc.
    To so:
    $users = Get-ADUser -SearchBase “OU=Europe,DC=YourCORP.,DC=COM” -Filter * etc.

    This was a great help, certainly only mailing two mails at given days before the actual expiration.

    1. Glad to hear it helped you out – and good point on OU filtering, and hopefully helps someone else too :) I might actually adjust my live version with this so other accounts don’t get spammed (didn’t even think about that!)

  8. Have any suggestions for having this only look at users in a certain OU? Also what about pulling a different attribute other than the email address field for the actual email address?

    1. Hi Steve,
      Amnon above has described how to do the OU filtering. I’m not sure why you’d use another field than the email address to use as an email address, but all you’d need to do is change this line:

      $emailaddress = $user.emailaddress


      $emailaddress = $user.newattribute

      newattribute you should be able to work out from the get-aduser command against an account, and use the field specified that has the value you want.

      1. Awesome. Yeah there is a need to use an extended attribute for a separate group of users.

        Do you know if you can use a security group instead of all users? Similar to an OU?

      2. The $name variable is where you’d need to do this. You should be able to work out how to modify the get-aduser command to only get users from a specific security group. Once you’ve done that, the rest of the script should work as per usual. This is the beauty of using variables in scripts :)

  9. How do you get the script to where it will go out as a daily email saying ‘You have 7 days” then “You have 6 days” instead of just the two variables? Thank you!

    1. I had to remind myself how the script worked :)

      As a quick fix, and this isn’t the most efficient way of doing it, but should work – there’s two steps. First, add the other variables you want:
      $expireindays1 = 7
      $expireindays2 = 1
      already there, add the other day counts you want:
      $expireindays3 = 2
      $expireindays4 = 3

      then, in the line if (($daystoexpire -eq $expireindays1) -or ($daystoexpire -eq $expireindays2))
      change to
      if (($daystoexpire -eq $expireindays1) -or ($daystoexpire -eq $expireindays2) or ($daystoexpire -eq $expireindays3) or ($daystoexpire -eq $expireindays4))
      adding each extra variable.

      Let me know how you go.

      1. Thanks for the reply Adam. I ended up doing what you suggested above yesterday and it does return results but it is not reporting back all the users especially compared to the list from yesterday.

      2. Please ignore my last reply. It seems to be working.
        Do you know of a script that will email when the Activesync policy pin will expire on mobile device?

      3. Hi Deborah, I don’t know of any that do this, and this script can’t do it without a bit of extra work, and it may need to use the Exchange PowerShell snapin too.
        Maybe I should start a donation fund for this :)

    1. Hi Deborah, Powershell turns out to be a little fiddly as I found out when it comes to embedding advanced HTML tags, bold and italic are fine and such, but embedding a picture… Hmm..

      Instead, I went the easier route and send a PDF as an attachment in the email, that describes how to reset your password for the three different user types (Mac, Wintel and remote users). (make sure you compress it as small as possible)
      Here’s the bit of code for that:

      Send-Mailmessage -smtpServer $smtpServer -from $from -to $emailaddress -bcc [email protected] -subject $subject -body $body -bodyasHTML -Attachments “C:\DATA\ScheduledScripts\PasswordExpiry\Change_Password.pdf” -priority High

      Maybe that works for you?

  10. I am trying to use this script in my domain controller Win2003 64bit as a .ps1 file. It never runs or throws an error. When i try to run the same from Win2008R2 under my user profile. I get an a code (0x41301). What am i doing wrong.

    1. Hi,
      Are you running this from Task Scheduler? Based on your error code I’m guessing so….
      First on your 2003 box, make sure you actually have PowerShell installed, then try the ps1 from a PowerShell command line. You should get a fairly specific error.

      Do the same on your 2008 R2 box, although it’l already have PowerShell installed. That error means it’s stuck running, so it may be waiting for some sort of input. Run it from the PowerShell command line and post back the error or problem you get.

    2. First hit in Google on 0x41301 (when indeed using the task scheduler, make sure it can also be run when no-one is logged on by the way):
      1. Make sure the account to run task scheduler is in group “log on as batch job” rights (on server side).
      Control Panel\|Admin Tools\Local Security Policy\Local Policies\User Rights Assignments\Log on as a batch job
      2. Check if the script runs under a path where the logon domain user has the proper permission. If not, you may add a path like “c:\users\%username%\desktop” into the Scheduled Task start in properties.

      But yeah, test it manually first, so you won’t miss out any error codes derived from PowerShell.

  11. Adam,
    This script is awesome. I tested it and it worked great. I added a few days so we could have a countdown and then saw a post about it. And it worked like a charm, tested it a few times, but now without any changes I am testing it and it seems to have a problem with the $expireson = $passwordsetdate + $maxpasswordage line of the script.

    I get this:
    Method invocation failed because [Microsoft.ActiveDirectory.Management.ADUser] doesn’t contain a method named ‘op_Addition’.
    At line:6 char:32
    + $Expireson = $passwordsetdate + <<< $maxpasswordage

    I don’t know what else to try to get the date to add in the 60 days to reflect the expireon date.

    Here is the shortened script that I am testing with to see if it will even get the expireson to work with just my account.

    Import-Module ActiveDirectory
    $passwordSetDate = (get-aduser acfreema -properties passwordlastset | select { $_.PasswordLastSet })
    $PasswordPol = (Get-AduserResultantPasswordPolicy acfreema)
    $maxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge.days
    $Expireson = $passwordsetdate + $maxPasswordAge
    $today = (get-date)
    $daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days

    Any help would be greatly appreciated and like I said it was working – sometimes I would get this error then it would work fine. Really strange. The script is rock awesome and everyone was really impressed but now I can’t get it to work.

    1. Thanks for the feedback :) I’ve run the script and get the same error.
      After echoing each of the two variables you’re adding, I’m guessing it doesn’t like the different formats as one comes back as a single number, and the other in full date/time format.

      I did a bit of digging around, and this script seems to have a solution:

      I haven’t tested it, but it’s doing a conversion of the pwdlastset value to a date/time format, which then should let your addition work.

      Let me know how you go :)

      1. Thanks for the feed back. I tried formatting it and doing some other things. I kept digging and found this that seems to work so i am trying to fit this in to the modified script.
        $DaysTilExpire = (([datetime]::FromFileTime((Get-ADUser -Identity $user -Properties “msDS-UserPasswordExpiryTimeComputed”).”msDS-UserPasswordExpiryTimeComputed”))-(Get-Date)).Days

        This just spits out a number that i can then use to send out the emails.

        thanks for your awesome IT/Powershell skills…i am checking out the other expiry stuff you sent out today

      2. Great info Adrian, thanks. Glad it’s helped!
        My PowerShell skills aren’t that good to be honest :) I can do some stuff, but I’m no programmer so can’t do complex scripts! As an IT Pro, I just want tools that accomplish a goal, not fancy scripts!

  12. nice script…i used $users=get-adgroupmember “groupname” instead of the get-aduser filter and it worked nicely…
    also added another variable, $username = $user.SamAccountName to store user account names, so when the user gets an email, they can see which account it is(some have admin accounts they use which differ from their regular account)

  13. Hi Can this be implemented for Office 365? If so where can we place the SMTP login credentials? Thanks.

  14. Hi Adam, I came across this blog and was relieved that it ticked all the boxes for tackling both Fine Grained and Default Domain based Policies (as we operate both solutions across 17 Domains) as well as being relatively simple to customize BUT while it works perfectly on a single Domain, trying to adapt to query multiple Domains (via a single central server which has the remote access to ALL servers), using the standard PS command for iteratively connecting to remote environments (see line at bottom of this reply), that I place before in between the section to iterate through the various domain environments and your script. When I run the full script, and type output, it does produce the correct details of users with suitably expired passwords BUT I cannot see how your script actually determines these users ?

    Is it the command before the smtp command (see below), that actually creates the correct output that is then used by the SMTP command. The reason I ask, is I need to work out how to break out of the iterative part of my script that remotes onto every PDC and then runs your script and into the email section for each environment before it goes back to iterating onto the next PDC

    Whole script is shown at the bottom

    f (($daystoexpire -eq $expireindays1) -or ($daystoexpire -eq $expireindays2))
    Send-Mailmessage -smtpServer $smtpServer -from $from -to $emailaddress -subject $subject -body $body -bodyasHTML -priority High



    Remote Access Script invoke command
    $output = Invoke-Command -Computername $server -Credential $credential -ScriptBlock {

    1. Hi John,
      The $users line is how it’s originally getting the list of users, then that gets parsed to $passwordsetdate which runs a get-aduser with the passwordlastset properly, and for each of those results, does the checks around password policy, and works out if the password is X days or less from expiry.

      Does that answer what you were looking for?

  15. Thanks Adam, yes that’s great (hadn’t noticed that the script cleverly filters out users with passwords that have already expired because another script inundated users with emails for accounts that they obviously weren’t too fussed about.

    So with your script, BEFORE the smtp line, I need to collate all the email addresses into a variable which I can then refrence in an smtp OUTSIDE of the main part of your script, so it can break out and run the smtp line back on the central server.

    I tried using the line below ( $emaillist+=$emailaddress ) to append all the relevant user emails ($emailaddress) into a new variable ($emaillist) which I could then iterate through back on the central server but when I try to output that variable (using return $emaillist), I don’t get an output on the screen (even though earlier on in the script, I have confirmed it is populating the $users variable with valid users

    # This line identifies whether the criteria for $daystoexpire equals any of the specified time limits and if does send the email to each user and then iterates back through to the next user
    if (($daystoexpire -eq $expireindays1) -or ($daystoexpire -eq $expireindays2) -or ($daystoexpire -eq $expireindays3) -or ($daystoexpire -eq $expireindays4)) {
    return $emaillist


    foreach ($emailaddressfound in $emaillist) {
    Send-Mailmessage -smtpServer $smtpServer -from $from -to $emailaddressfound -subject $subject -body $body -bodyasHTML -priority High

Leave a Reply

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