The default email schedule will email users at 30 days, 15 days, 7 days, 5 days, 3 days, 2 days, 1 day and the day of password expiration. If the user changes their password they will stop receiving email notifications until it becomes time to change their password again.
The script will ignore disabled user accounts and accounts that have the password never expires attribute set. If the user has an associated email address in active directory it will send the reminder to that email address. If the user does not have an associated email address an email will be sent to the address specified by the $adminEmail variable. Thus allowing the admin to be proactive. The script will also email the $adminEmail if a user's account has expired. Again, so that the admin can be proactive. The $adminEmail will receive only one email summary to avoid inbox clutter. The summary will also show user's that are required to change their passwords but have not done so yet. There's usually a good reason they're not logging on. Usually these types of accounts were provision for an employee but that employee never started.
You will need an SMTP server that doesn't require authentication to send out the emails. You will also need the active directory modules in order to run the script. Information regarding that can be found here: http://technet.microsoft.com/en-us/library/dd378937(WS.10).aspx
In a nutshell, if you're running the script on a domain controller you should be set. If you want to run it on a member server then execute the following PowerShell commands:
Import-Module ServerManager
Add-WindowsFeature RSAT-AD-PowerShell
You should create a scheduled task to run the script shortly after midnight each night. Before using it, be sure to set the five configuration variables in the Configurable Settings section at the top of the script.
Note: The script is just using the default domain password policy but if you're using the AD DS Fine-Grained Password Policies you should be able to modify the script fairly easily. You'll just have to invoke the Get-ADUserResultantPasswordPolicy commandlet while iterating through the user objects. Also note that all time is done with GMT and time zones are not taken into account. This shouldn't be an issue unless your enterprise spans the globe. If you're enterprise only spans a few contiguous time zones, have the script run at midnight in the time zone that's closest to GMT.
CODE:
#Create a nightly scheduled task that runs shortly after midnight and execute this script to generate password expiration email reminders
Import-Module ActiveDirectory
#----- Configurable Settings ------------------------------------------------------------------------------------------------------------------------
# Set the $warningInterval array to change when password expiration email reminders go out.
# The default below will email users at 30 days, 15 days, 7 days, 5 days, 3 days, 2 days, 1 day and the day of.
$warningIntervals = 30,15,7,5,3,2,1,0
# Set the $adminEmail to the email address that will receive password expiration reminders if the user does not have an associated mail address in AD
$adminEmail = 'admin@domain.com'
# Set the $fromEmail to the email address that will be used to send out password expiration reminders
$fromEmail = 'from@domain.com'
# Set the SMTP server used for sending out password expiration reminders, no authentication is used
$smtpServer = "smtp.domain.com"
# If $True, a summary will always be sent to $adminEmail. If $False, a summary will only be sent when there are user accounts that need attention.
$alwaysSendAdminSummary = $False
#----- End Configurable Settings -------------------------------------------------------------------------------------------------------------------
#Constant used to determine if password never expires flag is set
$ADS_UF_DONT_EXPIRE_PASSWD = 0x00010000
#Constant used to determine if user must change password at next login
$REQUIRED_PASSWORD_CHANGE_LASTSET = 0
$CurrentDate = [datetime]::Now.Date
$adminEmailContent = ""
$DefaultDomainPasswordPoliy = Get-ADDefaultDomainPasswordPolicy
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
function GetDaysToExpire([datetime] $expireDate)
{
$date = New-TimeSpan $CurrentDate $expireDate
return $date.Days
}
function GetPasswordExpireDate($user)
{
return [datetime]::FromFileTimeUTC($user.pwdLastSet+$DefaultDomainPasswordPoliy.MaxPasswordAge.Ticks)
}
function IsInWarningIntervals([int] $daysToExpire)
{
foreach( $interval in $warningIntervals )
{
if ( $daysToExpire -eq $interval )
{
return $True
}
}
return $False
}
function EmailUser($user)
{
$pwdExpires = GetPasswordExpireDate $user
$daysToExpire = GetDaysToExpire $pwdExpires
if( $daysToExpire -eq 0 )
{
$emailContent = $user.DisplayName + ", your password will expire today at " + $pwdExpires.ToShortTimeString() + " GMT"
}
else
{
$emailContent = $user.DisplayName + ", your password will expire in " + $daysToExpire + " days."
}
$smtp.Send($fromEmail, $user.mail, "Password Expiration Reminder", $emailContent)
}
function EmailAdmin($content)
{
if( [string]::IsNullOrEmpty($content) -and $alwaysSendAdminSummary )
{
$content = "There are no user's with expired passwords or users that need to change their password."
}
if( [string]::IsNullOrEmpty($content) -ne $True )
{
$smtp.Send($fromEmail, $adminEmail, "Password Expiration Summary", $content)
}
}
function AppendAdminEmailNoMail($user)
{
$pwdExpires = GetPasswordExpireDate $user
$daysToExpire = GetDaysToExpire $pwdExpires
return "The password for account """ + $user.samAccountName + """ will expire in " + $daysToExpire + " days at " + $pwdExpires + " and there is no associated email address to send a notification to" + [System.Environment]::NewLine + [System.Environment]::NewLine
}
function AppendAdminEmailExpiredAccount($user)
{
$pwdExpires = GetPasswordExpireDate $user
$daysToExpire = GetDaysToExpire $pwdExpires
if( $user.pwdLastSet -eq 0 )
{
return "The user account """ + $user.samAccountName + """ is set to require a password change at next logon and the user has not yet changed it" + [System.Environment]::NewLine + [System.Environment]::NewLine
}
else
{
return "The password for user account """ + $user.samAccountName + """ expired " + $daysToExpire + " days ago on " + $pwdExpires + [System.Environment]::NewLine + [System.Environment]::NewLine
}
}
#get all users
$users = Get-AdUser -Filter * -Properties userAccountControl, pwdLastSet, userprincipalname, mail, DisplayName, samAccountName, accountExpires, enabled #|
# FT -Property @{Label="UAC";Expression={"0x{0:x}" -f $_.userAccountControl}}, userAccountControl, pwdLastSet, @{Label="pwdLastChanged";Expression={[datetime]::FromFileTimeUTC($_.pwdLastSet)}}, @{Label="pwdExpires";Expression={[datetime]::FromFileTimeUTC($_.pwdLastSet+$DefaultDomainPasswordPoliy.MaxPasswordAge.Ticks)}}, userprincipalname, mail, DisplayName, samAccountName, accountExpires, enabled
#$users
foreach( $user in $users )
{
#if account is enabled and password never expire flag does not exist, then process user
if ( ($user.enabled -eq $True) -and (($user.userAccountControl -band $ADS_UF_DONT_EXPIRE_PASSWD) -eq 0) )
{
$pwdExpires = GetPasswordExpireDate $user
$daysToExpire = GetDaysToExpire $pwdExpires
#if day falls on warning interval
if( IsInWarningIntervals $daysToExpire )
{
#if mail attribute is not found in AD, add to admin email
if ( [string]::IsNullOrEmpty($user.mail) )
{
$adminEmailContent += AppendAdminEmailNoMail $user
}
#otherwise email user
else
{
EmailUser $user
}
}
#if days to expire is negative, password has expired. add to admin email
if( $daysToExpire -lt 0 )
{
$adminEmailContent += AppendAdminEmailExpiredAccount $user
}
}
}
EmailAdmin $adminEmailContent
Hi,
ReplyDeleteGreat script, however I only seem to get the adminemail with list of all users attached. The end user (I have given it a mail value) never seems to get the email?
Any suggestions?
Where/how did you give the user a mail value?
ReplyDeleteHi Scott!
ReplyDeleteTotally agree with Geraint, super script. Im having the same issue he had; im getting the admin email without users getting the alert mail. I likely broke it myself, however we dont have a AD sandbox for testing, so I couldnt kick out twenty tests to my company while I tooled with it.
The "get-user -filter *" I modified to "get-user -filter * -searchbase "OU=testing,OU=users,DC=Domain,DC=local""
Was this enough to break your engine and what do you think would be the best path to repair it?
When your run the get-user line by itself from the powershell prompt does it return a mail field for your users?
DeleteThe emails to the users will only go out at the specified intervals. The default is 30 days, 15 days, 7 days, 5 days, 3 days, 2 days, 1 day and the day of password expiration. This means if today is exactly 30 days prior to the user's password expiration then they will get an email. The next day they will not get one. Then when it's 15 days until expiration they will get their second email.
ReplyDeleteDoes the admin email say that there is no associated email address to email the user? If there is no email address associated with the user, the admin email should include the password expiration reminders that get generated on the intervals. It should only be included in the admin email on the interval though.
This was it exactly. I had that idea that night that I was running the script with several test accounts (with expired passwords) instead of live ones. After appending our entire applicable range of password expiry days on the notification trigger date and adding a live account to the AD OU, Voila.
DeleteThank you again for the great script!
Hi,
ReplyDeleteGreat script. I have been using this script with the domain password policy and it is working fine.
I am having problems understanding where and how to enter the Get-ADUserResultantPasswordPolicy command when using a fine grain password policy.
I have entered the following entry " $DefaultDomainPasswordPoliy = Get-ADFineGrainedPasswordPolicy passwordpolicytest"
thanks for your help
Hey Scott, is there any updates on where/how to use a fine grain password policy?
DeleteTry replacing the GetPasswordExpireDate function. Replace the following:
Deletefunction GetPasswordExpireDate($user)
{
return [datetime]::FromFileTimeUTC($user.pwdLastSet+$DefaultDomainPasswordPoliy.MaxPasswordAge.Ticks)
}
with:
function GetPasswordExpireDate($user)
{
$passwordPolicy = Get-ADUserResultantPasswordPolicy $user.SamAccountName
#Write-Host $passwordPolicy.MaxPasswordAge
if ($passwordPolicy -ne $null)
{
return [datetime]::FromFileTimeUTC($user.pwdLastSet+$passwordPolicy.MaxPasswordAge.Ticks)
}
else
{
return [datetime]::FromFileTimeUTC($user.pwdLastSet+$DefaultDomainPasswordPoliy.MaxPasswordAge.Ticks)
}
}
I do not have fine-grained password policies so I did not test this thoroughly. I think it should work though. It should use the file grained policy if one exists for the user and if not it should revert to the default domain policy.
Do me a favor, run the following power shell snippet and let me know what the output is:
$users = Get-AdUser -Filter * -Properties userAccountControl, pwdLastSet, userprincipalname, mail, DisplayName, samAccountName, accountExpires, enabled
foreach( $user in $users )
{
$passwordPolicy = Get-ADUserResultantPasswordPolicy $user.SamAccountName
Write-Host $passwordPolicy.MaxPasswordAge
}
This little snippet should get all AD users, try to load the password policy for each user and display the max password age associated to that password policy.
If this outputs nothing, then replacing the GetPasswordExpireDate function probably won't work as described and we'll need to dig further. If it displays a bunch of integers then replacing the GetPasswordExpireDate function should work for you.
Let me know.
Quick question: how do I know that indeed the user is being notified at those intervals??? What I mean is: I receive the admin email with a list of expired and who needs to change password on next login but it does not include a list of users that have been notified, what code should I add to the script to do that and where should I insert it?.I am not verse at powershell at all
ReplyDeletethanks
AL
Easiest way would be to ask them. I purposely did not include this in the admin emails as I wanted to only get admin emails when something was not quite right. I didn't want to liter the admin emails with superfluous informational. If you want to modify the script, the easiest way would be to copy the AppendAdminEmailNoMail function and call that after the EmailUser function call. Add this:
Deletefunction AppendAdminEmailMail($user)
{
$pwdExpires = GetPasswordExpireDate $user
$daysToExpire = GetDaysToExpire $pwdExpires
return "The password for account """ + $user.samAccountName + """ will expire in " + $daysToExpire + " days at " + $pwdExpires + [System.Environment]::NewLine + [System.Environment]::NewLine
}
Then change the following:
#otherwise email user
else
{
EmailUser $user
}
to:
#otherwise email user
else
{
EmailUser $user
$adminEmailContent += AppendAdminEmailMail $user
}
Thanks for your work. It is appreciated
DeleteI added your name at the beginning of the scrip on my server for "Credits/Recognition" purposes
Is there a way to create exclusions based userAcountControl attribute??...I am getting notifications about an INTER_DOMAIN_TRUST account:
ReplyDeleteUserAccountControl=512 = NORMAL account (regular users)
You should be able to filter based on a mask of the useraccountcontrol attribute. Can you explain your situation in a little more detail? Do you know which ones should be valid and which ones shouldn't?
DeleteAny way this can be crafted to function with only a particular domain or OU in AD? We have multiple domains and only want this for one.
ReplyDeleteYou should be able to use the Filter or LDAPFilter parameters on the following line:
Delete$users = Get-AdUser -Filter * -Properties userAccountControl, pwdLastSet, userprincipalname, mail, DisplayName, samAccountName, accountExpires, enabled