How to inform users that their password is about to expire

August 2, 2009

41 comments

I got several requests to publish the latest version of a script I wrote once to retrieve all mail enabled user accounts that have the password configured to never expire. Once the accounts are retrieved, based on the DaysToExpire variable value, a mail message is sent to the users stating that their password will expire in X days.

Notice that the script requires the latest version of Quest AD cmdlets and is also compatible with PowerShell version 1 or 2.

$ReqVersion = [version]"1.2.2.1254"
$QadVersion = (Get-PSSnapin Quest.ActiveRoles.ADManagement).Version

if($QadVersion -lt $ReqVersion)
{
   
throw "Quest AD cmdlets version ‘$ReqVersion’ is required. Please download the latest version"
}

function Send-Mail
{
  
param($SmtpServer,$From,$To,$Subject,$Body)

   $smtp = new-object system.net.mail.smtpClient($SmtpServer)
  
$mail = new-object System.Net.Mail.MailMessage
  
$mail.from= $From
  
$mail.to.add($To)
  
$mail.subject= $Subject
  
$mail.body= $Body
  
$smtp.send($mail)
}

$MaxPassAge = (Get-QADObject (Get-QADRootDSE).defaultNamingContextDN).MaximumPasswordAge.days

if($MaxPassAge -le 0)

  
throw "Domain ‘MaximumPasswordAge’ password policy is not configured."
}

$DaysToExpire = 14 
$MailForm = "you@domain.com"
$PSEmailServer = "exServerName"

Get-QADUser -Enabled -PasswordNeverExpires:$false -SizeLimit 0 -Email * |`
Select-Object Name,Email,@{Name="Expires";Expression={ $MaxPassAge $_.PasswordAge.days }} |`
Where-Object {$_.Expires -gt 0 -AND $_.Expires -le $DaysToExpire } | Foreach-Object {
 
 
$Subject="Password reminder: Your Windows password will expire in $($_.Expires) days"
 
 
if($PSVersionTable)
  {
    
# PowerShell Version 2 detected  
     Send-MailMessage -From $MailForm -To $_.Email -Subject  $Subject -Body  $Subject 
  }
 
else
  {
    
# code for PowerShell v1
     Send-Mail -SmtpServer $PSEmailServer -From $MailForm -To  $_.Email -Subject  $Subject -Body  $Subject
  }

}

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

41 comments

  1. Greg BrownAugust 6, 2009 ב 00:15

    Hello Once Again!
    Shay, after working on my own version of this script for a few weeks now, I think I finally have licked it. However, if the user has “password Must Be Changed at Next Logon” checked, it returns a $null and halts right there…
    Man, I am SOOO close!! lol
    Thanks for any help you can give.
    gb

    Reply
  2. ScriptFanaticAugust 7, 2009 ב 10:45

    Greg, try to add -ErrorAction SilentlyContinue on the failing cmdlet. I will try to test it my self.

    Reply
  3. Greg BrownAugust 7, 2009 ב 21:45

    I have a variable that calls up all the $users_to_be_notified. The thing is, if their password is expiring in less than 14 days they will land in that variable. But, if the user has “change pw at next logon” checked, it makes the $days_remaing variable barf. Below is shortened to fit.

    $days_remaining = ($user.PasswordExpires-$today).days

    When the script hits $user and that user has change password at next logon checked, it dies… Any ideas?

    Reply
  4. ScriptFanaticAugust 13, 2009 ב 11:21

    Greg, with the following function you can find users that has “change pw at next logon” checked and filter them out:

    function Convert-ADSLargeInteger([object]$LargeInteger)
    {
    $type = $LargeInteger.GetType()
    $highPart = $type.InvokeMember(“HighPart”,”GetProperty”,$null,$LargeInteger,$null)
    $lowPart = $type.InvokeMember(“LowPart”,”GetProperty”,$null,$LargeInteger,$null)

    $bytes = [System.BitConverter]::GetBytes($highPart)
    $tmp = New-Object System.Byte[] 8
    [Array]::Copy($bytes,0,$tmp,4,4)
    $highPart = [System.BitConverter]::ToInt64($tmp,0)
    $bytes = [System.BitConverter]::GetBytes($lowPart)
    $lowPart = [System.BitConverter]::ToUInt32($bytes,0)
    $lowPart + $highPart
    }

    Add the bellow command between the Get-QADUser and Select-Object commands:

    Where-Object { (Convert-ADSLargeInteger $_.PSBase.DirectoryEntry.pwdLastSet[0]) -ne 0 }

    HTH

    -Shay

    Reply
  5. Greg BrownAugust 17, 2009 ב 05:19

    Well, after 3.5 weeks I found the answer. I have been reading Payette’s book. Cover to cover… In his discussion on how to form WHERE statements, it dawned on me that perhaps the .NET object exposed by the Quest cmdlet had something to offer. After doing a GET-MEMBER on the QDAUSER object, I realized that USERMUSTCHANGEPASSWORD is a property. I formulated the query to include an -AND $_.usermustchangepassword:$false, and it worked like a champ. I would modify your cmdlet above to reflect this. It’s the new powershell way!! lol
    Thx again.
    gb

    Reply
  6. ScriptFanaticAugust 17, 2009 ב 09:34

    Learnt something myself, thanks Greg.
    BTW, ‘$_.usermustchangepassword:$false’ doesn’t work, you need to use the equel operator, as in:
    ‘$_.usermustchangepassword -eq $false’.

    Reply
  7. Greg BrownAugust 17, 2009 ב 19:38

    Yep, you’re correct- you have to use -eq… Typo on my part…
    Where in your mail function would you set authentication info? I thought adding credentials would be a no brainer, but obviously not! lol
    My HT requires auth…
    gb

    Reply
  8. ScriptFanaticAugust 18, 2009 ב 14:22

    I would try to set one of the following (or both):

    $smtp.UseDefaultCredentials
    $smtp.Credentials

    Reply
  9. Tommy DoanAugust 30, 2009 ב 08:46

    Shay, where can I find more information on this construct?

    @{Name=”Expires”;Expression={ $MaxPassAge – $_.PasswordAge.days }}

    It seems you are assigning the name Expires to an expression, all inside a hash table. This is very interesting but I haven’t been able to find information on the details.

    Reply
  10. ScriptFanaticAugust 30, 2009 ב 10:38

    It is called ‘Calculated Property’ and you can find more information about it in this article:

    http://www.microsoft.com/technet/scriptcenter/resources/pstips/apr08/pstip0425.mspx

    There are also some code examples in the help files. See examples 5 and 6 in Format-Table and #4 in Select-Object.

    Reply
  11. KevinNovember 11, 2009 ב 20:26

    This is great. I appluad your efforts.

    I’m new to Powershell and I’m wondering how to break up the $subject line to include more “formatted” text. In other words – I want to link to an internal help page and provide some basic instructions to the recipient of the email. I’m finding it extremely difficult to accomplish this.

    Any thoughts?

    Reply
  12. ScriptFanaticNovember 13, 2009 ב 16:52

    You can add to internal url the subject, chnage it to:

    $Subject=”Password reminder: Your Windows password will expire in $($_.Expires) days, for more help see: http://server/page.htm

    What other information you’d like to include?

    Reply
  13. EvrenJanuary 4, 2010 ב 17:01

    so with all the comments, what should be the latest code for us to use 🙂

    Reply
  14. ScriptFanaticJanuary 4, 2010 ב 17:28

    Evren, you can start with the code of the post. Anything special you want to integrate into it?

    Reply
  15. EvrenJanuary 6, 2010 ב 15:56

    Well, i need to run this script in an hosted exchange environment only for companies who want to apply password policy. So

    So i can not apply a password policy from domain level, instead i use fine grained password policy for specific companies.

    I get the error below when i run the script:

    Domain ‘maximumpasswordage’ password policy is not configured.
    At C:\PS\preminder.ps1:28 char:9
    + throw < <<< "Domain 'maximumpasswordage' password policy is not configured." This is why; [PS] C:\PS>(Get-QADObject (Get-QADRootDSE).defaultNamingContextDN).MaximumPasswordAge.days
    -10675199

    So does anyone have a suggestion to aply this kind of script in an hosted environment which we don’t set a password policy on domain level..

    Reply
  16. EvrenJanuary 17, 2010 ב 14:31

    Can we add an administrator report in to script which will send an email to administrator about which users have been emailed that their password will expire..

    Reply
  17. ScriptFanaticJanuary 17, 2010 ב 16:54

    Sure you can:

    1. After the ‘$PSEmailServer = “exServerName”‘ line add: $AdminReport=$null

    2. Add this line after the $Subject assignment:

    $AdminReport += “Password reminder has been sent to: $($_.Name)`n”

    3. At the end of the script add:

    # for PowerShell 2.0
    if($AdminReport -ne $null)
    {
    Send-Mail -SmtpServer $PSEmailServer -From admin@domain.com -To admin@domain.com -Subject “User Password notifications” -Body $AdminReport
    }

    Reply
  18. EvrenJanuary 18, 2010 ב 13:17

    One another issue,

    can’t it say “your password will expire in 1 days 4 hours” or “0 days 6 hours” ?

    Reply
  19. ScriptFanaticJanuary 18, 2010 ב 14:02

    You can add another calcualted property to the Select-Object cmdlet, make it look like:

    Select-Object Name,Email,@{Name=”Expires”;Expression={ $MaxPassAge – $_.PasswordAge.days }},@{Name=”PasswordAge”;Expression={$_.PasswordAge }}

    Now change $subject to:
    $subject = “Your Password expires in: {0} Days {1} Hours” -f days $_.PasswordAge.days, $_.PasswordAge.Hours

    Reply
  20. EvrenJanuary 18, 2010 ב 15:25

    I get this error?

    You must provide a value expression on the right-hand side of the ‘-f’ operator.
    At D:\Mesajlasma\Evren\preminder.ps1:46 char:61
    + $subject = “Your Password expires in: {0} Days {1} Hours” -f <<<< days $_.PasswordAge.days, $_.PasswordAge.Hours

    Reply
  21. ScriptFanaticJanuary 18, 2010 ב 15:43

    Sory, I had a typo. Try this:

    $subject = “Your Password expires in: {0} Days {1} Hours” -f $_.PasswordAge.days, $_.PasswordAge.Hours

    Reply
  22. EvJanuary 18, 2010 ב 16:29

    great! But, this returns the amount of time since the user changed his password.

    We need to subtract the value from $MaxPassAage.

    sorry for these questions but i am not good at powershell 🙂 trying to learn.

    Reply
  23. jeremyAugust 31, 2010 ב 16:21

    This is a great script. Has anyone tried to have it check each user for a Password Settings Object rather than always assume the $MaxPassAge is in the default domain policy?

    Reply
  24. ScriptFanaticSeptember 5, 2010 ב 12:21

    Hi Jeremy

    Can you elaborate on the use of ‘Password Settings Object’?

    Reply
  25. JTSeptember 8, 2010 ב 14:30

    Hi everyone,

    What could I add to have the script skip an OU and all its sub-OUs?

    Reply
  26. ScriptFanaticSeptember 8, 2010 ב 14:49

    You can filter out users from a top ou (and sub-ous)by testing if the parent OU distinguished name contains the top ou name. Insert the below where-object between get-qaduser and select-object commands.

    Get-QADUser -Enabled -PasswordNeverExpires:$false -SizeLimit 0 -Email * | Where-Object {$_.ParentContainerDN -notmatch ‘ou=Name of OU’} | Select-Object …

    Reply
  27. JeremySeptember 10, 2010 ב 18:23

    To elaborate on the use of ‘Password Settings Object’

    In a 2008 domain we could have 2 or more PSO’s for example the PSO “CN=PSO-Name,CN=Password Settings Container,CN=System,DC=child,DC=parent,DC=com” would have a value in the msDS-MaximumPasswordAge attribute of 30 days. The msDS-PSOAppliesTo attribute on the PSO would have the DN of a Security Group, and members of that SG would enforce the maximum password age of the PSO (30 days) not the maximum password age specified in the default domain policy which could be (45 days). This way we could have a subset of users with a different value for the $maxpassage variable, but any user that is not a member of a security group associated with a PSO would still use the maximum password age in the default domain policy.

    Reply
  28. JeremySeptember 10, 2010 ב 18:26

    I have found a few commands that could be helpful, but have not decided on the best way to use them yet.

    get-qadpasswordsettingsobject
    get-qadpasswordsettingsobjectappliesto

    Thank you for any suggestions.

    Reply
  29. ScriptFanaticSeptember 14, 2010 ב 22:54

    I have found those commands as well but haven’t had the time to play with them yet.

    Reply
  30. EdzillaNovember 30, 2010 ב 10:51

    Is there a way to do this without using the Quest cmdlets?
    My boss won’t let us install them on server 2008R2 machines, he wants me to use the microsoft cmdlets.

    Reply
  31. ScriptFanaticDecember 1, 2010 ב 10:59

    You don’t have to install the Quest cmdlets on your DC’s, you can install them on your local machine and connect to your domain.

    With regard to the Get-ADUser cmdlet, check this post from the Active Directory Powershell Blog:

    Find out when your Password Expires
    http://blogs.msdn.com/b/adpowershell/archive/2010/02/26/find-out-when-your-password-expires.aspx

     

    Reply
  32. OzgurJanuary 31, 2011 ב 18:42

    it just wont work. i am pretty sure its something very simple but i just cannot figure out the problem.
    what i get is;

    The term ‘ ‘ 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:\password_expire.ps1:35 char:76
    + Get-QADUser -Enabled -PasswordNeverExpires:$false -SizeLimit 0 -Email * |` <<<< + CategoryInfo : ObjectNotFound: ( :String) [], CommandNotFoundException + FullyQualifiedErrorId : CommandNotFoundException

    Reply
  33. ScriptFanaticFebruary 1, 2011 ב 11:10

    It’s probably a formatting issue. You can get the script from here:

    http://blogs.microsoft.co.il/blogs/scriptfanatic/expired.txt

    Reply
  34. ToviaMarch 9, 2011 ב 16:56

    Hi

    can anyone tell me how do i run this script

    Tovia

    Reply
  35. ScriptFanaticMarch 10, 2011 ב 17:11

    @Tovia

    Copy the code to a file and save it as c:\expired.ps1
    Now launch PowerShell.exe and type the path to the script to execute it:

    PS > c:\expired.ps1

    You need to have Quest cmdlets installed on the box before running the script. Give me a call (050-9750407) if you need further assistance.

    Shay

    Reply
  36. AleJuly 8, 2011 ב 20:56

    I am trying to run this against an OU and not the entire domain, where can I modify this in the script?

    We also are enforcing a password policy but when i try to run the script it gives me this error:

    At C:\installs\passwordnotice1.ps1:7 char:13
    + Add-PSSnapIn <<<< -Name Quest.ActiveRoles.ADManagement + CategoryInfo : InvalidArgument: (Quest.ActiveRoles.ADManagement:String) [Add-PSSnapin], PSArgumentExcep tion + FullyQualifiedErrorId : AddPSSnapInRead,Microsoft.PowerShell.Commands.AddPSSnapinCommand Domain 'MaximumPasswordAge' password policy is not configured. At C:\installs\passwordnotice1.ps1:30 char:22 + throw <<<< "Domain 'MaximumPasswordAge' password policy is not configured." + CategoryInfo : OperationStopped: (Domain 'Maximum...not configured.:String) [], RuntimeException + FullyQualifiedErrorId : Domain 'MaximumPasswordAge' password policy is not configured. Any thoughts on what I'm doing wrong?

    Reply
  37. ScriptFanaticJuly 12, 2011 ב 12:16

    @Ale

    Locate the following line:

    Get-QADUser -Enabled -PasswordNeverExpires:$false -SizeLimit 0 -Email *

    And add it the SearchRoot parameter with the value of the OU Distinguished Name. For example:

    Get-QADUser -Enabled -PasswordNeverExpires:$false -SizeLimit 0 -Email * -SearchRoot ‘OU=test,DC=domain,DC=com’

    Reply
  38. Free Beer Corp.October 6, 2011 ב 23:24

    Slight Correction:

    values returned from the QADuser commandlet are negative:

    “Where-Object {$_.Expires -gt 0 -AND $_.Expires -le $DaysToExpire }…

    use the -lt value to resolve any output issues.

    “Where-Object {$_.Expires -lt 0 -AND $_.Expires -le $DaysToExpire }…

    sample output:
    Name : Stalin
    Email : Stalinfortime@docs.linenlinedlenins.org
    Expires : -44

    Reply
  39. HLMarch 9, 2012 ב 19:18

    So, do you get one email per day if you are within the $DaystoExpire period or do you get one email on the $daystoexpire day?

    Great script btw. Thanks!

    Reply
  40. ScriptFanaticMarch 9, 2012 ב 21:03

    You get an email once per script run.

    Reply