DCSIMG
How to inform users that their password is about to expire - Shay Levy

Shay Levy

If you repeat it, PowerShell it!

How to inform users that their password is about to expire

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
  }

}

Comments

How to inform users that their password is about to expire – Shay Levy « Domain Namez said:

Pingback from  How to inform users that their password is about to expire – Shay Levy «  Domain Namez

# August 2, 2009 7:54 PM

Greg Brown said:

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

# August 6, 2009 12:15 AM

ScriptFanatic said:

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

# August 7, 2009 10:45 AM

Greg Brown said:

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?

# August 7, 2009 9:45 PM

ScriptFanatic said:

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

# August 13, 2009 11:21 AM

Greg Brown said:

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

# August 17, 2009 5:19 AM

ScriptFanatic said:

Learnt something myself, thanks Greg.

BTW, '$_.usermustchangepassword:$false' doesn't work, you need to use the equel operator, as in:

'$_.usermustchangepassword -eq $false'.

# August 17, 2009 9:34 AM

Greg Brown said:

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

# August 17, 2009 7:38 PM

ScriptFanatic said:

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

$smtp.UseDefaultCredentials

$smtp.Credentials

# August 18, 2009 2:22 PM

Tommy Doan said:

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.

# August 30, 2009 8:46 AM

ScriptFanatic said:

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

www.microsoft.com/.../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.

# August 30, 2009 10:38 AM

Kevin said:

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?

# November 11, 2009 8:26 PM

ScriptFanatic said:

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?

# November 13, 2009 4:52 PM

Evren said:

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

# January 4, 2010 5:01 PM

ScriptFanatic said:

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

# January 4, 2010 5:28 PM

Evren said:

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..

# January 6, 2010 3:56 PM

Evren said:

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..

# January 17, 2010 2:31 PM

ScriptFanatic said:

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

}

# January 17, 2010 4:54 PM

Evren said:

One another issue,

can't it say "your password will expire in 1 days 4 hours" or "0 days 6 hours" ?

# January 18, 2010 1:17 PM

ScriptFanatic said:

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

# January 18, 2010 2:02 PM

Evren said:

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

# January 18, 2010 3:25 PM

ScriptFanatic said:

Sory, I had a typo. Try this:

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

# January 18, 2010 3:43 PM

Ev said:

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.

# January 18, 2010 4:29 PM

jeremy said:

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?

# August 31, 2010 4:21 PM

ScriptFanatic said:

Hi Jeremy

Can you elaborate on the use of 'Password Settings Object'?

# September 5, 2010 12:21 PM

JT said:

Hi everyone,

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

# September 8, 2010 2:30 PM

ScriptFanatic said:

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 ...

# September 8, 2010 2:49 PM

Jeremy said:

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.

# September 10, 2010 6:23 PM

Jeremy said:

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.

# September 10, 2010 6:26 PM

ScriptFanatic said:

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

# September 14, 2010 10:54 PM

Edzilla said:

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.

# November 30, 2010 10:51 AM

ScriptFanatic said:

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

 

# December 1, 2010 10:59 AM

Ozgur said:

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

# January 31, 2011 6:42 PM

ScriptFanatic said:

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

blogs.microsoft.co.il/.../expired.txt

# February 1, 2011 11:10 AM

Tovia said:

Hi

can anyone tell me  how do i run this script

Tovia

# March 9, 2011 4:56 PM

ScriptFanatic said:

@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

# March 10, 2011 5:11 PM

Ale said:

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?

# July 8, 2011 8:56 PM

ScriptFanatic said:

@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'

# July 12, 2011 12:16 PM

Free Beer Corp. said:

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

# October 6, 2011 11:24 PM

HL said:

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!

# March 9, 2012 7:18 PM

ScriptFanatic said:

You get an email once per script run.

# March 9, 2012 9:03 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: