Because of this, I wrote a simple Powershell script that is scheduled to run every day and creates a list of user accounts which passwords will expire in less than 7 days and sends them an e-mail notification that they should change their password. Once a user receives this notification, he can use Outlook Web Access to change his password.
Some notes about the script:
- The script uses System.Globalization.CultureInfo object to format the date returned for Croatian region (hr-HR). If you come from US, you can change hr-HR in the script to en-US
- The $msg.Body property in the script contains HTML formatted text that user receives as an e-mail message body. You can modify this HTML code, for example, to include your company branding
- You can modify the $OU property so that script only scopes your external users, users from a specific department, etc.
- The script does not send e-mail to users whose passwords are already expired or who have "Password never expires" property set
- You can modify how many days are left until password expiry before you start notifying users. Currently, script notifies users whose passwords expire in less than 7 days.
You can modify the following line: $DaysToExpire.Days -lt 7 - The policy works with default domain password policy and with Password Settings Object, a feature of Windows Server 2008 Active Directory
- You must run the script on a domain controller or on a server with Remote Server Administration Tools installed
- You must change the variable $smtpServer to your internal mail server (usually Exchange Hub Transport server) that can send e-mails to your users
Here is the script:
Import-Module ActiveDirectory #System globalization $ci = New-Object System.Globalization.CultureInfo("hr-HR") #SMTP server name $smtpServer = "smtp.domain.local" #Creating SMTP server object $smtp = new-object Net.Mail.SmtpClient($smtpServer) #E-mail structure Function EmailStructure($to,$expiryDate,$upn) { #Creating a Mail object $msg = new-object Net.Mail.MailMessage $msg.IsBodyHtml = $true $msg.From = "administrator@domain.com" $msg.To.Add($to) $msg.Subject = "Password expiration notice" $msg.Body = "<html><body><font face='Arial'>This is an automatically generated message from Service Provider Exchange service.<br><br><b>Please note that the password for your account $upn will expire on $expiryDate.</b><br><br>Please change your password immediately or at least before this date as you will be unable to access the service without contacting your administrator.</font></body></html>" return $msg } #Set the target OU that will be searched for user accounts $OU = "OU=Users,DC=domain,DC=local" $ADAccounts = Get-ADUser -LDAPFilter "(objectClass=user)" -searchbase $OU -properties PasswordExpired, PasswordNeverExpires, PasswordLastSet, Mail, Enabled | Where-object {$_.Enabled -eq $true -and $_.PasswordNeverExpires -eq $false} Foreach ($ADAccount in $ADAccounts) { $accountFGPP = Get-ADUserResultantPasswordPolicy $ADAccount if ($accountFGPP -ne $null) { $maxPasswordAgeTimeSpan = $accountFGPP.MaxPasswordAge } else { $maxPasswordAgeTimeSpan = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge } #Fill in the user variables $samAccountName = $ADAccount.samAccountName $userEmailAddress = $ADAccount.Mail $userPrincipalName = $ADAccount.UserPrincipalName if ($ADAccount.PasswordExpired) { Write-host "The password for account $samAccountName has expired!" } else { $ExpiryDate = $ADAccount.PasswordLastSet + $maxPasswordAgeTimeSpan Write-host "The password for account $samAccountName expires on: $ExpiryDate" $TodaysDate = Get-Date $DaysToExpire = $ExpiryDate - $TodaysDate #Write-Host $DaysToExpire.Days if ($DaysToExpire.Days -lt 7) { $expiryDate = $expiryDate.ToString("d",$ci) #Generate e-mail structure and send message $msg = EmailStructure $userEmailAddress $expiryDate $userPrincipalName $smtp.Send($msg) Write-Host "The expiration notification e-mail was sent to $userEmailAddress" } } }
Hi,
ReplyDeleteIn the notification email there is an issue, the recepient is present many times in the "$to" , do you know why ?
thank you!
To patch, here is :
Function EmailStructure($smtpServer,$to,$expiryDate,$upn)
{
#Creating SMTP server object
$smtp = new-object system.net.mail.smtpClient($smtpServer)
#Creating a Mail object
$msg = new-object System.Net.Mail.MailMessage
$msg.IsBodyHtml = $true
$msg.From = "you@mail.com"
$msg.To.Add($to)
Write-Host $to
$msg.Subject = "your subject here"
$msg.Body = "your body here"
$smtp.send($msg)
}
Thank you very much for pointing out an issue. There was a bug in the code where I used the same $msg object over and over and was just appending the e-mail addresses in the to list.
DeleteIt has now been corrected.
Enjoy and thanks once again!
You're welcome :)
ReplyDeleteI also modify :
$DaysToExpire.Days -eq 7
now I can run the script every day and the user receive just one email :)
This is a really good article.
ReplyDeleteHow do you schedule this Powershell script to run once a day? Do you use the task scheduler in Administrative Tools?
Thanks
RK
Thanks Rob! Yes, I use task scheduler in Administrative Tools to schedule it once a day.
DeleteMr Xhark,
ReplyDeleteWhere did you put the "$DaysToExpire.Days -eq 7" so users only receive one email?
Hi Dinko,
ReplyDeleteIs the script at the top of the page the latest? I'm asking because its not working for me as expected. I'm receiving emails from the script, but it doesn't specify the date of expiration. Below are the errors I'm receiving:
-- any help would be greatly appreciated:
Get-ADDefaultDomainPasswordPolicy : Cannot find an object with identity: 'Micro
soft.ActiveDirectory.Management.ADDefaultDomainPasswordPolicy' under: 'DC=child,DC
=domain,DC=local'.
At C:\free_script.ps1:41 char:80
+ $maxPasswordAgeTimeSpan = (Get-ADDefaultDomainPasswordPoli
cy <<<< ).MaxPasswordAge
+ CategoryInfo : ObjectNotFound: (Microsoft.Activ...nPasswordPoli
cy:ADDefaultDomainPasswordPolicy) [Get-ADDefaultDomainPasswordPolicy], ADI
dentityNotFoundException
+ FullyQualifiedErrorId : Cannot find an object with identity: 'Microsoft.
ActiveDirectory.Management.ADDefaultDomainPasswordPolicy' under: 'DC=child,DC
=domain,DC=local'.,Microsoft.ActiveDirectory.Management.Commands.GetADDe
faultDomainPasswordPolicy
Cannot convert argument "1", with value: "", for "op_Addition" to type "System.
TimeSpan": "Cannot convert null to type "System.TimeSpan"."
At C:\script.ps1:52 char:46
+ $ExpiryDate = $ADAccount.PasswordLastSet + <<<< $maxPasswordAgeTimeSpan
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
The password for account jdoe expires on:
The operation '[$null] - [System.DateTime]' is not defined.
At C:\script.ps1:56 char:33
+ $DaysToExpire = $ExpiryDate - <<<< $TodaysDate
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NotAdefinedOperationForTypeType
You cannot call a method on a null-valued expression.
At C:\script.ps1:59 char:39
+ $expiryDate = $expiryDate.ToString <<<< ($ci)
+ CategoryInfo : InvalidOperation: (ToString:String) [], RuntimeE
xception
+ FullyQualifiedErrorId : InvokeMethodOnNull
Adrian,
DeleteThis is the latest script. It seems that the problem could be something with your environment.
Can you just run the following two lines in your Powershell window on domain controller and see where it takes you.
Import-Module ActiveDirectory
Get-ADDefaultDomainPasswordPolicy
If everything is okay, you should get the domain password settings in the output.
Regards,
Dinko
This comment has been removed by the author.
ReplyDeletePS C:\Windows\system32> Import-Module ActiveDirectory
ReplyDeletePS C:\Windows\system32> Get-ADDefaultDomainPasswordPolicy
Get-ADDefaultDomainPasswordPolicy : Cannot find an object with identity: 'Microsoft.ActiveDirectory.Management.ADDefaul
tDomainPasswordPolicy' under: 'DC=child,DC=domain,DC=local'.
At line:1 char:34
+ Get-ADDefaultDomainPasswordPolicy <<<<
+ CategoryInfo : ObjectNotFound: (Microsoft.Activ...nPasswordPolicy:ADDefaultDomainPasswordPolicy) [Get-A
DDefaultDomainPasswordPolicy], ADIdentityNotFoundException
+ FullyQualifiedErrorId : Cannot find an object with identity: 'Microsoft.ActiveDirectory.Management.ADDefaultDoma
inPasswordPolicy' under: 'DC=child,DC=domain,DC=local'.,Microsoft.ActiveDirectory.Management.Commands.GetADDefaultD
omainPasswordPolicy
Hi Dinko,
ReplyDeletePlease see the post just before this one for the output after running the two lines as requested. Also, thanks again for your assistance here.
Adrian
Really nice work Dinko. I am trying to work the script to against local accounts instead of AD. Any tips on the matter?
ReplyDelete