Description

In today’s increasingly mobile workforce, many users don’t login to Active Directory on a domain joined computer. Some use only Outlook Web Access/App, some use non-domain joined machines external to the company network, some just use mobile devices such as smartphones and tablets. And others may use Macs.

OWA users see a notification when they login as well. In OWA 2007 running on IIS6, this can be adjusted via PasswordExpirePrenotifyDays. In fact, with OWA 2007 and 2010, you can even change your password after it expires, using the Password Reset Feature in Exchange 2007 and 2010. However, there are times when that’s just not a remedy. The password reset feature requires the Exchange server to be running on Windows 2008 or later, as it relies on IIS 7. Many Exchange 2007 shops are not on that platform yet.

Anyone who’s ever worked on a Help Desk knows that a LOT of users call to say they can’t login, only to determine it’s because their password expired. Many, if not most, are those types of users mentioned above. Others don’t notice, or simply ignore the notice when logging in. So let’s really make sure we notify them of the pending expiration. There are some third-party tools, including some that run on SharePoint, that enable a user to reset their password. But this is after the fact. Sure, we could use some third-party application to send a reminder, but… well… why? PowerShell to the rescue!

In the pre-Windows 2008 domain functional level days, we could just peek at the Default Domain GPO, and grab the Maximum Password Age parameter, since it was a global setting. Then we could go through Active Directory, find users who are not set to “never expire”, use some math, and come up with a list of users whose password expired soon.

But with the changes implemented with Windows Server 2008, we can now have Fine Grained Password policies, which allows us to have more than just one password policy in our organization. So, Executives get one password, IT people with elevated rights get another, etc. Cool in theory, but frustrating in our endeavor to notify users when they’ll expire.

I blatantly admit that I used part of a script by M. Ali, who wrote a blog post Find out when your Password Expires. The script looks checks Get-AdDomain, and looks at the DomainMode parameter in the results. From here, we know whether we can just peek at the Default Domain policy, or if we need to look deeper. Regardless of which way, we look through the users using Get-AdUser, and grab the PasswordExpired, PasswordNeverExpires, and PasswordLastSet fields. Obviously, if the account is expired, no need to keep reminding the user. And if the password never expires, then we also don’t need to notify the user. With PasswordLastSet, our math comes into play to determine when the password will expire. Not terribly short and sweet, but effective.

Once we know when the password will expire, we can then set a window for when we should notify the users. It makes sense to match what’s in the GPO so that notifications are consistent regardless of platform. This script is set to 14 days by default.

Next, we need to craft some information that we want to convey to the user. In this case, we’ll use some HTML formatting so that we can properly convey the importance of the info, as well as include some additional formatting. I’ve mocked up something based on some third-party tools, and on the comments and recommendations of IT Professionals and users. It’s simple enough to change, but be warned that many clients, including Outlook, don’t strictly adhere to HTML standards. So it can take quite a bit of trial and error to find out what does actually appear the way you want it to.

Installation and Setup

First, you need a receive connector that will accept mail from PowerShell. I cover that in Creating a receive connector to use for sending email from PowerShell. Next, the script must run on a machine with PowerShell 2.0 installed. This is a prerequisite for Exchange 2010 (and installed by default on Windows 2008 R2), but not for Exchange 2007. If you’re reluctant to upgrade PowerShell on your 2007 box, it can be run from any other box that has PowerShell 2.0 and the Exchange Management tools installed. Note: Exchange Management tools should always be updated and patched to the same level that your Exchange servers are.

Second, you’ll need the ActiveDirectory module available on the machine that will run the script. The ActiveDirectory module is installed when you add the Remote-Server Administration Tools feature in Windows Server 2008 R2. If the module is not detected, the script will attempt to install it automatically the first time it runs.

Next, grab the latest zip file from the DOWNLOAD section below. It includes the script and ScriptImages.zip contains a couple of images that are used in the warning for users who’s password expires in < 24 hours (seen in the Outlook screenshot above). The images need to be accessible to all users who will receive the reminder emails. This is likely to be a public web site.

Crack open the script in your favorite editor and update the lines in the param() block to match your environment. This includes $Company, $OwaUrl, $PSEmailServer, $EmailFrom, $HelpDeskPhone, $HelpDeskURL and $DaysToWarn. If you want to target a specific OU, set $OU. Also, set $ImagePath to a path holding the included image files (or those you add/edit). This path should be available to all users who may receive the reminder message. This is probably a public server.

Open an Exchange Management Shell session and run the script in demo mode to see a list of users that are expiring soon.The script won’t email the users in demo mode. It merely shows you who it WOULD, and how long till their password expires.

.\New-PasswordReminder.ps1 -demo

As we see in the example screenshot, Claudia’s password expires in 5 days, and the password policy that applies to her requires the password to be changed every 42 days. If we run the script normally, Claudia will receive the email reminder since it’s within the 14 day window defined in the script.

To run the script normally (non-demo mode), manually, just omit the -demo. There is no output to the screen when run normally, as the script is designed to be run as a scheduled task.

Once you’re satisfied that the script is running correctly, we can set it to run as a scheduled task. I have a blog post Running PowerShell scripts via Scheduled Tasks that details everything. In my production environment, it runs at 6am each day.

One of the hardest parts was getting a decently formatted email that looked good. This could take some trial and error, and the original script didn’t really have a way built in to preview what the end user would see. As a result, some hapless users would be flooded with your “test” messages. I fixed that by creating a preview mode. Manually run the script with the preview switch, and a user to send the email to. For example

.\New-PasswordReminder.ps1 -Preview -PreviewUser bgates

This will send an email to the user, bgates. The email is formatted for a password that expires in one day, so the user gets the additional banner near the top as well.

Next up was creating a scheduled task. Not really terribly difficult to do manually, but I could see where it might take some trial and error. So, I added the install switch, which will create a scheduled task for the script, configuring it to run at 6am each day. Of course, that time can be manually adjusted by opening the scheduled task once it’s created. The install mode will ask for credentials to run the scheduled task under. Install it as so:

.\New-PasswordReminder.ps1 -Install

Note: The scheduled task is configured to point to where the script is when you run the install switch. So don’t move it later!

To send an email that does not contain the images or their related formatting, specify $NoImages when running the script. This will send essentially an HTML formatted text email.

Next up, I added some simple logging to the application event log. The script will write a single entry when it starts, and a single entry when it finishes, noting how many users were processed (sent an email). I would love to hear how this script works in large environments. If you’re willing, please let me know (via comments below) how long it’s taking to run in your environment, and the number of users in AD.

Please send me your suggestions!

Donations

I’ve never been one to really solicit donations for my work. My offerings are created because *I* need to solve a problem, and once I do, it makes sense to offer the results of my work to the public. I mean, let’s face it: I can’t be the only one with that particular issue, right? Quite often, to my surprise, I’m asked why I don’t have a “donate” button so people can donate a few bucks. I’ve never really put much thought into it. But those inquiries are coming more often now, so I’m yielding to them. If you’d like to donate, you can send a few bucks via PayPal at https://www.paypal.me/PatRichard. Money collected from that will go to the costs of my website (hosting and domain names), as well as to my home lab.

Syntax

Demo Runs the script in demo mode. Demo mode displays users who are expiring soon, but does not send them the reminder email.

Install Creates a scheduled task to run the script automatically every day at 6:00am

PreviewUser
Defines the user to send the preview email to.

NoImages
Specifies that a HTML text only message should be sent instead of one that contains the fancy formatting.

Installation

Execution Policy: Third-party PowerShell scripts may require that the PowerShell Execution Policy be set to either AllSigned, RemoteSigned, or Unrestricted. The default is Restricted, which prevents scripts – even code signed scripts – from running. For more information about setting your Execution Policy, see Using the Set-ExecutionPolicy Cmdlet.

In addition to the info listed above:

If you leave the following parameters blank, the related text will be removed from the email sent to users: $HelpDeskURL. This will get expanded in the future.

You can change the format of the date displayed in the email by changing the value of $DateFormat. The default is “d”, which yields a date such as 09/07/2012 (MM/dd/yyyy). If you’d like the European style, use “MM/dd/yyyy” instead.

I have some more idea: It would be very fine, if you can choose how often a mail will send out to the user. For example: the first information will come 21 days before the password will expired, the second mail will come 12 days before the password will expire. And the last 7 days the mail will be send out daily.

The script calls the module for AD. It doesn’t use snapins. If that feature (RSAT-AD-PowerShell) isn’t installed, it should install it (assuming it’s running with an account that has rights to do that).

I tried the 2.4 version also. But still only users with no Policy assigned to (i.e. domain admins) shows up data for days to expire. No other users with password policies can be found. Tried to set DaysToWarn to 80 or 90 (policy says 90 days).

Best of its kind script. But kinda confused which server i should run it on. I am running an exchange server 2007 sp2 on windows server 2008. Domain controllers are windows server 2008 r2. Domain functional level is 2003. Can i just run it on a DC where exchange management tools is not installed ?

I tried your script and it works perfectly. thanks for the great effort

I need you help to achieve the following:
I need the script to send the email to the users 1 month before his password expires and again send the email to the users 15 days before his password expires, then send it if the password will expire in 9 days.

Great Script! Been hunting for a while and this is the best one by far.

I too have the scheduled task issue and wonder if it’s UAC related? Will try commenting out the eventvwr write commands.

Also, you can set the $PSEmailServer var but I do see where it’s applied. I added the -smtpServer $PSEmailServer to the send-mailmessage command, and can now direct emails through any smtp server I define.

My scheduled task inserted by the tool was not working. Eventually I determined the issue is related to the command line arguments not being enclosed in double quotes which is need when the path contains spaces. I manually added the double quotes below to the scheduled task.

I need your help to achieve the following:
I need the script to send the email to the users 1 month before his password expires and again send the email to the users 15 days before his password expires, then send it on daily basis if the password will expire in 9 days.

Hello
I managed to let the script to send the email to the users 1 month before his password expires and again send the email to the users 15 days before his password expires, then send it on daily basis if the password will expire in 9 days.

Pat further testing has shown the script does work, I appear to have misinterpreted the â€˜Previewâ€™ function. I ran against my AD account where the password is not due to expire. I ran against an alternate account where the password was due to expire and they received the email. Not ideal as I wanted to preview the email prior to going live with the scripts.

Pat I now have the script working but I note an error. The demo correctly displayed password expiry for user accounts. Using one as an example password expiry was 9 days (confirmed by DumpSec). I sent a preview email to this account. The email password reminder advised the user that their password expired in 1 day 10/03/2012 (this should have been 9 days 18/03/2012).

First let me just say thank you, this is exactly what I’ve been looking to do to deal with users who REFUSE to log off their PCs more than twice a year.

I’ve been able to get everything working just great except the scheduled task doesn’t seem to work properly giving me a results code of 0xFFFC0000. Running the script manually from the command line is flawless tho. Any idea what the issue might be?

The images need to be in a location accessible by EVERY user who may receive the email. That includes users outside your environment who may be reading the email via OWA. I always use a subfolder of the organization’s website, but have gotten it to work with folders on a CAS server – but that brings up extra complexity if you have more than one CAS server.

This script is working great except the password policy listed at the bottom of the email has the settings for my default domain policy and not the settings from the fine grained password policy. Anyone else run into this?

I just want to thank you for the script and your efforts.
I was able to setup V2.4 successfully with the steps described above and the note from Chris (missing /malformed path in the Scheduled Task â€œC:\Program Files\Microsoft\Exchange Server\V14\\Bin\exshell.psc1â€³ after using the -install option).

I’ve been running this script as a test with my existing Domain admin account which has full permission across the enteprise. As I want to set this up to run under a service account what permissions are needed on Exchange/Domain/Local Computer for it to run?

Pat,
I am new to PowerShell scripting but I am trying to learn on my own. I want to thank you for your work on this script, if I can get it to work this will be a huge benefit to my help desk.
I am trying to run the script in Preview and Demo mode but each time I do it returns an error,
Missing ‘)’ in function parameter list.
At C:\scripts\New-PasswordReminder.ps1:174 char:108
+ [parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Mandatory=$true, HelpMessage=” <<<
< No module name specified!")]
+ CategoryInfo : ParserError: (CloseParenToken:TokenId) [], ParseException
+ FullyQualifiedErrorId : MissingEndParenthesisInFunctionParameterList

Hi PAT, We have tested this script in our environment and it works great, but before we implement this, our team suggest that we should re-edit the PASSWORD POLICY part. Can this be done as i understand the linec 437-442 call this table directly from AD. Or at least isthere is a way to remove it completely? and maybe insert a customized table.?

The issue came down to the AD Management Gateway Service on the PDC. After that was resolved the script ran fine.
I did have one other issue with selecting a specific OU, do I need to have the entire path in quotes or do I just use the name of the OU?

hi pat, thx for your outstanding script. for me it works perfect in demomode or when i run it from powershell. but doesn’t work as scheduled task, and when i run from the cli, it runs but on the end it reports the following error:
**************
Get-Content : Cannot find path ‘C:\New-PasswordReminder.ps1’ because it does no
t exist.
At C:\scripts\chkexpiredpw\New-PasswordReminder.ps1:347 char:23
+ $result = Get-Content <<<< $path |
+ CategoryInfo : ObjectNotFound: (C:\New-PasswordReminder.ps1:Str
ing) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetCo
ntentCommand
*************
script is running on a 2008 R2 DC with exchange2007 management tools installed.
i would appreciate any help!
regards martin

ok, great. the line on the bottom reads like: “Remove-ScriptVariables($MyInvocation.MyCommand.Name)” i put a # in front, now it works. another tip: do nut create the task as a privilidged user other the one the task is running under. means, if you want to run the task as administrator”, create it also as administrator, othewrwise it wont work.
thx to pat again, great job done!
martin

It’s because one or more of the accounts involved in the query has the field MAIL empty in A.D. , fill them up (create to ALL of them (the list of -demo mode) a mailbox and it works), but now mine works only if I run it, I read somewhere about a double slash mistake and can’t find it anymore… someone can help me?

Only DC ip should be mentioned in Exchange SMPTP relay agent right ?
Email id will be picked from DC account properties ( Email attribute ) ?
I have to run only this script in DC ?
Any additional requirement needs to be done on Exchange Server ?

We did run into one issue while testing. We did the preview command line but a couple of the admin users we tried to test against have their password set not to expire so they didn’t even get the preview email and they don’t show up in the demo list either.

I haven’t looked to change the code but if it’s possible to not skip people like this in preview mode AND flag them in demo mode that would be great. In fact “demo” would really become “audit” at that point!

Hello, thanks very much for this script. It is awesome! The only thing I can’t seem to get working is the transcript. Looks like for each email sent, it should write to a .log file. I did not see any instructions on how to config this to work. Any help would be appreciated!

Are you using the -transcript option? There’s probably not going to be much in there since the script is designed to run as a scheduled task (no real screen output). I’ll clarify that in the next version.

thanks for this awesome script Pat.
A question for you though, when I run this in demo mode, the script only finds one user that is in the Built in Users OU. It does not see the other users in the Users OU nor does it find any other users in other OUs. I’m running this on a windows 2008 R2 server that has exchange 2010 installed and the domain functional level is 2008 R2. What am i missing?

Thanks for this, I was able to customize it to do exactly what I want! The one thing I would like to request is the ability to email a list (formatted like the -demo option) to myself, or my team. Basically, it would be nice to receive an email every Monday with a list of the users with passwords expiring that week so we can also check remind them to change it, or at-least we will know to expect issues with them later in the week assuming they will still forget to change it (even after daily reminder emails). I’ve tried figuring out how to pipe the output of the -demo option to an email, but I’m still getting used to powershell. It would be awesome if a -admin option (or something like that) could be added to allow the list to be emailed to a specified address. If-not, maybe someone where can help me figure out how to properly pipe the output from -demo to the send-mailmessage cmdlet or something like that?

You made my day Pat, that Script is fantastic. I am still trying to follow all that functions and code you wrote 🙂

The script itself is working fine, but I receive an error Message when the scripts hits an user without a mailbox, nevertheless the script is still excecuting like it should.

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 C:\New-Password\New-PasswordReminder.ps1:333 char:29
+ Send-MailMessage -To $accountObj.mail -Subject “Password will expire in ….”

Is there any way to suppress that error? The first that is poping in my mind is to add that line on top of the script:

Great script. Exactly what I’ve been looking for as many users we have use their mobile devices etc and never get the warning otherwise.

It would be great to have some sort of log. Perhaps the output of the -demo switch could be sent to the helpdesk email every morning which would provide an overview of which users have had communication about their password.

I think that would be quite easy to implement in the current script and perhaps you could include it as part of your 1.6 release?

Yeah, I’ve got some ideas for stuff like that, but I really need to build a lab that has some fine grained password policies so I can track down how to get the policy for a user. That’s a critical request, but something I haven’t had time to do.

Please do let me know once you have done testing with it, as i want to run the test on a small OU and donot know if it would be executing all the 6000+ users as it doing with preview command, instead of child OU only.(3 users only)

When i run the script manually it works fine, Mailing works. (demo or live)
After i have run the command -install. The task is scheduled. But when the schedulling runs, no one receives an email. (not in spam or something like that)
(even run it under domainadmin to be sure there are enough right) in the scheduler is see last run result (0X1) Also tried run with higest privileges. Also doenst work. Tried with or without quotes. Could you help.

I am trying to run the script on a remote system.
I can run the -demo with no error
I can run the -Preview -PreviewUser username with no errors and I get the e-mail
I can run the -install, and it creates the task with no error

[PS] C:\Scripts>.\New-PasswordReminder.ps1 -demo
You must provide a value expression on the right-hand side of the ‘/’ operator.
At C:\Scripts\New-PasswordReminder.ps1:160 char:16
+ $env:UserName / <<<

The preview user doesnt show any details at all, and no emails are sent…
Our passwords are not currently set to expire as we have lots of remote users who have issues with this. Which is why i want to implement the script, but checks its all ok first..

Is there an option i can change to say – if the password is set to not expire then email??

Preview option won’t show anything on the screen – it just sends the email to the user defined in -previewuser, regardless of what their expiration status is. If that’s not working, we need to find out why.

Pat, thanks for the great script.. I dont have windows 2008 r2 domain controllers yet and its giving me, Unable to find a default server with Active Directory Web Services running. Can this script work without r2 domain controllers?

When I run demo from a 2010 server I don’t get any errors, however, I don’t get any results either. I set the days value to 60, 180, and as much as 1,000. I only changed the default values and not the OU.

Thanks for the great script. I have a question – our Exchange servers are in a resource forest seperate from the user accounts we want to report on. How can I get this script to run against user accounts in the user forest rather than the linked objects in the Exchange resource forest?

Pat, great job, thanks!
1 question and 1 comment: When you specify an OU, does it recurse the sub OU’s under that?
I second the request to (if possible of course) send an email to a set address with the list of who the password warning was sent to the last time the script was run.

I have a problem with the HelpdeskURL. I will not have any image in the e-mail. I not set the $HelpdeskURL but nothing change. I try to set the variable with $null value but nothing changes. Anyone have issue with this variables?

No decent editor here,, so I can’t giove you the line number.
Look for “if ($demo) {Write-Host (“{0,-25}{1,-8}{2,-12}” -f $accountObj.Name, $DaysTillExpire, $PolicyDays)}”
change $accountobj.name to $accountobj.displayname and give it a whirl

Thanks a lot for this great script. Recently it worked great but today when I add the parameter “demo” I don’t get any results. I have verified the pwdLastSet attribute in my ad and no one has changed the password yet. How is this possible?

Very strange, it stopped to work for 3h. Then I tried to debug it with “-v” for “verbose” and saw everything went through but no email was sent. Suddenly it started to work again, but there has never been an error given out so far.

While my “-demo” stopped to work also the “-PreviewUser” didn’t send an email even tough it went through what I’ve seen so far with “verbose” output.

I will monitor it a few days and report in my findings. If I have time I will also try to add functions like logging to email or a path for admins the actual result and also a text-only email instead of html email for instance as a switch.

User Expires Policy
======================== ======= ===========
VERBOSE: Setting event log configuration
VERBOSE: Getting password policy configuration
VERBOSE: Getting the user info for user1
VERBOSE: verifying that the password is not expired, and the user is not set to PasswordNeverExpires
VERBOSE: Verifying if the date the password was last set is available
VERBOSE: Getting the user info for user2
VERBOSE: verifying that the password is not expired, and the user is not set to PasswordNeverExpires
VERBOSE: Verifying if the date the password was last set is available
VERBOSE: Getting the user info for user3
VERBOSE: verifying that the password is not expired, and the user is not set to PasswordNeverExpires
VERBOSE: Verifying if the date the password was last set is available
VERBOSE: Getting the user info for user4
VERBOSE: verifying that the password is not expired, and the user is not set to PasswordNeverExpires
VERBOSE: Verifying if the date the password was last set is available
…
_____

I think it has something to do with this line and the “SYSTEM” account on line 199:
if ($demo) {Write-Host (“{0,-25}{1,-8}{2,-12}” -f $accountObj.Name, $DaysTillExpire, $PolicyDays)}

I tried to debug the script and found out, that the “pwdLastSet” field read by the script is empty. But when I use for instance AD Explorer from Sysinternals I can see that the fields aren’t empty. Any ideas for this temporary bug?

Hello, thanks for all the hard work you’ve been putting on this script! The demo portion of it works great, but I’m not able to preview the e-mail. I’m having an issue with the send-MailMessage cmdlet. “Unable to connect to the remote server”. I’ve tried the FQDN and IP and it still doesn’t go through. Things to note:

1.) Exchange 07, windows 2000 domain
2.) Trying to run this script on another server.
3.) I’ve ran other powershell scripts off that server before, so not sure why i’m getting this message..
4.) Using v2.6 of the script.

Send-Mail doesn’t require that the email server be Exchange. It’s just an SMTP cmdlet. So what’s likely the cause is you don’t have a receive connector in Exchange that will accept from the server running the script.

I got it figured out, I thought it was the receive connector too, we had one enabled for this IP range (for the network printer e-mail functionality). I created one specifically for this one too but that did not resolve the issue. Turns out.. it was McAfee OAS blocking the SMTP attempts. Just ran a scheduled task (had to modify some settings thanks to lvjoe for his contribution) and so far everything has been running smooth.

Only minor issue I’m having is that it doesn’t e-mail external users (non-domain e-mails). But that’s an issue unrelated to the script.

A very nice way to test mail functionality without to bother people is to use a dummy mail server for preview. Hassle free emulation of a working of a smtp server with just double click:http://smtp4dev.codeplex.com/

Thank-you for producing a really great script. I implemented it at my office with a customization of the email message body. I have scheduled a task to run a couple of times a week for anyone whose password needs to be changed within the next 14 days.

When I run the script using demo mode, I get an expected listing of about a couple of dozen users and the days till expirartion and policy. When I use the -previewuser switch and my network username, I get a nicely formatted email So far so good.

When the script runs as a scheduled task without these switches, the event log shows that it only processed just 2 or 3 users. The rest do not get emails. What can I do to troubleshoot and resolve this difficulty?

As a follow up to my prior comment, I have some new information. I had the scheduled task set to run using a service account that I typically use for such things. The task was set to run whether or not the account was logged in. When I reconfigured the task to run using my own domain admin account and watched the command windows pop up, it processed completely. The event log shows that all 28 accounts were correctly processed. I confirmed that users did get an email.

So it seems to be related to how the task is run and not so much the script itself.

You were spot on with your suggestion to check “run with highest privileges.” That definitely solved it for me. The scheduled task is running as expected with the service account and console logged out. Thanks so much!

Another interesting curiosity is that the built-in Windows balloon help reminder seems to calculate one day less than than your script. A user who got a reminder that his password will expire in 14 days got a Windows balloon that said 13. At first I thought this might be because the script ran a few hours earlier than the balloon, but I ran the script subsequently in demo mode, and it still showed 14 days. I checked the value for “msDS-UserPasswordExpiryTimeComputed” on his account and based upon that, I’d say the balloon help in Windows calculated properly, whereas your script seems to be computing one more day. Any thoughts?

Hi firstly thanks very much for this script, it is exactly what I was looking for. The script ran correctly however in the process it changed the AD account owner of all users from the Exchange Mailbox server to the main admins group. Can you tell me if its possible to remove this part of the script please. Thanks

As a follow up to my previous post this was discovered as users who previously could use the “Send As” feature now cannot use this and it will no longer allow me to enable this feature. Adding the Exchange Mailbox server back as the owner fixes this issue.

Love this!
Question: Can this be installed multiple times from different folders? We want to customize it per client company (we share our server with 3 other companies). This would allow us to customize company name and such for each client.

Also, I’m using the msDS-UserPasswordExpiryTimeComputed ADUser property from the ActiveDirectory module in a logon script for our Remote Desktop servers to remind users on logon of an expiring password. Could that simplify your script? It seems to support fine grained password policies in our environment just fine (2008 R2).

Ok, i read my comment and i think i’m not realy clear. So, I have users in my domain who have contact email like hotmail or gmail or no email adresses configured. So i need to exclude them from been warn.

After working with a couple of other similar scripts before I found this I have to say THIS ROCKS!. I believe a simple tweak will accomplish the restriction however. In one of those I was using a filter such as Get-ADUser -filter {(mail -like “*@domain.com”) -and (Enabled -eq “True”) -and (PasswordNeverExpires -eq “False”)} which not only deals with non-domain emails, but also eliminates the need to check for accounts without emails, etc. I haven’t tried it yet but I’m wondering if that may not also improve processing speed. In my environment this script takes about 90 minutes to process. The other ones a was working with ran in a just a few minutes. They weren’t dealing with FGPP however so that may also be impacting things. Any thoughts as to what actually takes up the time? I’m looking at about 1000 users total, about 600 with emails.

Just a quick question, if I schedule the script to run daily and set the DaysToWarn = 7, will it only send email on the 7th day once? Or it will send email once daily on the 7th, 6th, 5th, 4th …etc day till the exact expiry day?

FYI, I would like the email remind user one week before their password is about to expire, then again when there are two days remaining to expire.

Well I have got this wonderful script running except as a scheduled task. I can run it in demo perfect. I can run it in preview mode perfect. I can just run it in regular mode (no switches) perfect. My environment is 2008r2 DFL and the script is on one of my domain controllers at the moment. I used the install switch and that worked perfectly. I am running the task as my domain admin account and have the run with highest privileges box checked. in the task history it appears to run and complete just fine however there is no associated even in the application log stating that the script processed x number of users and no one recieves an email. Version 2.6 of the script. Any ideas?

Works great in -preview -previewuser mode to myself
The above error is in -demo mode..
Cannot test the script in normal mode as I need to test I have the OU$ parameter is right before hand so as not to annoy other admins in other OU’s ;-))

I must be a bit thick here as far as the OU is concerned.
I have a Forest with three Child Domains. Users are only in the Child Domains. When I leave the $OU blank I only get a notice about one user in the Forest Root, but none in the Child domains.
If I try to set one of the Child Domains in the $OU I get an error.
I tried: $OU = “OU=Users,OU=Domain Users,DC=XXXXX,DC=internal”

User Expires Policy
======================== ======= ===========
VERBOSE: Setting event log configuration
VERBOSE: Getting password policy configuration
VERBOSE: Getting the user info for user1
VERBOSE: verifying that the password is not expired, and the user is not set to PasswordNeverExpires
VERBOSE: Verifying if the date the password was last set is available
VERBOSE: Getting the user info for user2
VERBOSE: verifying that the password is not expired, and the user is not set to PasswordNeverExpires
VERBOSE: Verifying if the date the password was last set is available
VERBOSE: Getting the user info for user3
VERBOSE: verifying that the password is not expired, and the user is not set to PasswordNeverExpires
VERBOSE: Verifying if the date the password was last set is available
VERBOSE: Getting the user info for user4
VERBOSE: verifying that the password is not expired, and the user is not set to PasswordNeverExpires
VERBOSE: Verifying if the date the password was last set is available

it runs against every users but I get no results.
domain level server 2008
Ex 2010
server 2008r2

I have a pso with max password age 365 days applied to all members of a group which is working fine.

Great Script Pat! Please clarify something for me though. Is the process capable of reading a 2008 R2 AD Custom Password Expiration GPO or only the Default Domain Policy with the Password Expiration set? Reason I ask is I created a custom one and linked it to different OU’s and in the demo mode and demo -v mode the OU’s are correctly read but the Password Expiration Custom GPO isn’t. Only when I apply a Password Expiration policy to the Default Domain Policy does the demo mode show the expected results. Thank You.

Goodmorning Pat,
I tried to add it in the scheduled job with my admin account.
I added it manually and with the adminsitrators account.
I can run it manually but for some reason the task won’t run as a scheduled job.
I run it with the highest privilleges but no luck.
The settings are:
Program/Script: c:\windows\system32\windowspowershell\v1.0\powershell.exe
Add arguments(optional): -psconsolefile “C:\Program Files\Microsoft\Exchange Server\V14\Bin\exshell.psc1” -command “New-PasswordReminder.ps1”
Start in: E:\website

Hi Pat,
I think I figured it out. I will share the solution with you. After the command switch you have to include the entire path to the ps script. You should ignore the start in option in the scheduled task. After making this change ( in quotes ) it runs like a charm.

Pat – I’ve put your fantastic script in to a live environment after successful testing. I am getting an Exception calling “WriteEntry” with “3” arguments: The source was not found, but some or all event logs could not be searched. Inaccessible logs: Security

First of all thanks for the script. Can I suggest a modification to the user search filter from * to {(objectclass -eq “User”) -and (Enabled -eq “True”) } in order to only return the non disabled users as there is no point to inform disabled users that their password will expire.

What i would like to have is a builtin “variable”/Funktion so i can install the script towards different domains, i have now in the script changed all the rows that have something with get-ad….. with extension “-server domain.local”.

Hi Pat. I have a domain that is used for terminal services only and has no Exchange server. Will this script be able to work in a domain with out a exchange server but with an SMTP Virtual Server? will it require the Exchange Management tools?

Hey Pat, I am one of your great follower and the scripts have made my line of work so easy. Keep up the good work. my issue here is do you have scripts for office 365. we have just migrated and I do not seem to be receiving the password change email.

Hey Pat, this is the best, i had just implemented this at my office. because we are a big organization, we were asked to migrate to office 365, now i can not receive password expiry mails. How do i integrate this with office365.

I tried changing the perms on the log, but I still can not get it to work. Part of the issue is that I am not sure what user/group/service etc. I should be adding or changing. What should I change exactly? I tried adding domain admins (which should allow my account write access), Local service, network service, Exchange servers, etc… All with no joy.

we had the issue that the script lists all the “hidden” AD users which are created internally for domain trusts. As those users are managed by the AD itself but don’t have a password that never expires they are listed..if you try to mail them..it fails because they don’t have a mail address.. I altered the filter to exclude those users from the get-aduser command.

I used the following LDAPFilter: -ldapfilter ‘(!(name=*$))’ instead of the -filter * part.

I’ve been working with this and during testing it works flawlessly. I’ve run into an odd problem however when the scheduled task runs. It runs, and it appears to do it’s job just fine, but a spot check with some of my users revealed that they weren’t getting the notification emails. I tweaked the script to bcc my test account with all the emails and in this morning’s run, despite the fact that there were 14 users with expiring passwords that should have been notified, only 2 emails got delivered. I ran the script manually and all 14 go through with no problems. Very odd. I’d understand if it didn’t work at all, etc., but why piecemeal? Any ideas?
Thanks

Get-AdDomain : The term 'Get-AdDomain' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. At C:\Program Files\Microsoft\Exchange Server\V14\Scripts\New-PasswordReminder.ps1:391 char:28
+ $global:dfl = (Get-AdDomain <<<< ).DomainMode
+ CategoryInfo : ObjectNotFound: (Get-AdDomain:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

Get-AdUser : The term 'Get-AdUser' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. At C:\Program Files\Microsoft\Exchange Server\V14\Scripts\New-PasswordReminder.ps1:398 char:22
+ $users = Get-AdUser <<<< -filter * -ResultSetSize $null
+ CategoryInfo : ObjectNotFound: (Get-AdUser:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

User Expires Policy
======================== ======= ===========
Get-ADDefaultDomainPasswordPolicy : The term 'Get-ADDefaultDomainPasswordPolicy' is not recognized as the name of a cmd let, Junction, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. At C:\Program Files\Microsoft\Exchange Server\V14\Scripts\New-PasswordReminder.ps1:420 char:65
+ $DefaultDomainPasswordPolicy = Get-ADDefaultDomainPasswordPolicy <<<<
+ CategoryInfo : ObjectNotFound: (Get-ADDefaultDomainPasswordPolicy:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

Hi Pat, I would like to use this in my organisation. However I want to use it with the -noimages switch. It works correctly except I would like it to use a font that isn’t Times new roman in the body. Is there an easy way I can set this? Sorry I have no HTML knowledge and my fumblings have achieved nothing useful.

Is there an easy way to have it so that the line “ALERT: You must change your password today or you will be locked out!” only shows up on the last day? It would great if I could replace it with “ALERT: If you don’t change your password in days, you will be locked out!” Is this possible?

Love the script, and have it running against a pilot OU right now. I’d like to have one scheduled task that send out a notification only if a user’s password is at 10 days to expiration, and then another at 5 days or less (5,4,3,2,1). Having problems with this though, what’s the best way to modify the script to be able to accomplish this?

Thanks for your help and this has resolved my problem. One question will it be possible for me to test for single user as when I try to execute “.\New-ADPasswordReminder.ps1 -Preview -PreviewUser UserADID” I get below error message

The term 'Get-AdDomain' is not recognized as the name of a cmdlet, function, script file, or operable program. Check th
e spelling of the name, or if a path was included, verify that the path is correct and try again.
At C:\Scripts\New-ADPasswordReminder.ps1:419 char:28
+ $global:dfl = (Get-AdDomain <<<< ).DomainMode
+ CategoryInfo : ObjectNotFound: (Get-AdDomain:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

The term 'Get-AdUser' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the
spelling of the name, or if a path was included, verify that the path is correct and try again.
At C:\Scripts\New-ADPasswordReminder.ps1:428 char:22
+ $users = Get-AdUser <<<< -ldapfilter '(!(name=*$))' -ResultSetSize $null
+ CategoryInfo : ObjectNotFound: (Get-AdUser:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

Many thanks for the great script!. It works like a real smoothie… I had a query regarding our environment. We have an OU which contains non-mail Exchange users, they have external email add configured in the email property of AD account. Is it possible to send mail to external users? Cheers

Hi, Kudos and thank you for the awesome script, this makes things soo easy!!. I am currently facing an issue while running this script int the demo mode. It is showing incorrect value of 180 days whereas the Policy is set to 60 days. Could you please point me to the right direction. Thanks!

Love this script it saved me from having to rewrite the one we were using and broke. One thing though you should check for account disabled since those accounts should not get reminder emails and frequently their email is forwarded to other users so could lead to confusion. This is easy to add in case anyone else wants to just modify the ForEach ($user in $users) loop as follows:

I have had this script running great on my 2008 R2 server for a year and a half. I’m retiring that old server, and moving this to a 2012 server running Exchange 2010. In my testing I’m having issues getting the script to run. I execute with the -demo parameter and get this:

“Import-Module : The ‘C:\Windows\system32\WindowsPowerShell\v1.0\Module\ActiveDirectory\ActiveDirectory.psd1’ module cannot be imported because its manifest contains one or more members that are not valid….. It then lists the valid members and fails on a bunch of other cmdlets.

So, questions. Will this script run on a 2012 server running Exchange 2010? If yes, what do I need to do to make it work? I have confirmed that the AD Module for PowerShell is installed and am running the script as the admin. I’ve tested the old script, and also downloaded the latest version.

I know this thread hasn’t had any activity for a year, but hopefully someone knows something. Thank you.

Hey Pat. We’ve been using this script in our Exchange 2007 environment flawlessly and it has cut down on the amount of servicedesk related tickets for password resets. I would like to sincerely thank you for your work on this script. We have recently migrated to Office 365 (very painfully) and obviously this has broken the password reminder script from emailing our users as our old Exchange server is no longer in production. Any info on how to get this up and running or will I have to contact Microsoft to try and get this to work with their email servers. I’ve tried to point the email server to the correct new address, but still receiving an error: “Unable to read data from the transport connection: net_io_connectionclosed.”. Please tell me there is a chance to get this working with Office 365 servers!

Thanks for writing this script, it works perfectly. You have saved a lot of admin from the headache of password reset/account lockout issues. I was wondering if you have a place to donate to your work? I checked the page, but didn’t seem to find one. Also, I read through the comments and saw a particular feature that would be helpful. I would like to add a CC of the email to a manager of the person that has their password expiration notice sent. Would you mind assisting with this or is that a feature you might add?

I understood that this script ought to take fine grained password policy into consideration too, but at least in demo-mode it doesn’t seem to do that. Couldn’t find a definitive answer from here does it support that or not?

When running Preview or Demo it works great. Get a nicely formatted email from preview and demo does find the users needing the emails as they should. Freaking awesome script.

I’m having an issue when the task runs. It completes as it should no error, but it sends an email to everyone in the company everyday, not just the users under 14 days. The email they get is also only the footer image and the helpdesk link message.

Preview shows everything in the test email correctly.

I’m sure it is something that I have dorked up while editing the code. I am not what you might call well acquainted with Powershell so you might need to use small words.

Thank you for such great post.
I need your help.
the notification sent out should have two headings;
1. in red colour mentioning the action required.
2. in green colour mentioning the steps to change the password.