The script sends email notifications to users whose password is to expire in a predefined number of days. To execute the script, create a scheduled task configured for the Domain-DNS object type and add a managed domain to the Activity Scope.
Parameters:
- $days - Specifies amounts of days users passwords should expire in for the script to send them emails.
- $messageTemplate - Specifies a template for the email notification. In the template, the {0} placeholder will be replaced with the number of days before the recipient password expiration.
- $subjectTemplate - Specifies a template for the email notification subject. In the template, the {0} placeholder will be replaced with the number of days before the recipient password expiration.
PowerShell
$days = @(5,7,14) # TODO: modify me
$messageTemplate = "Your password will expire in {0} days" #TODO: modify me
$subjectTemplate = "Your password will expire in {0} days" # TODO: modify me
# Build search filter
# We filter out user accounts whose password never expires or must be changed at next logon,
# as well as interdomain trust accounts and accounts required to use smart cards
$filterUsers = "(&(sAMAccountType=805306368)(|(!(msExchRecipientTypeDetails=*))(!(msExchRecipientTypeDetails:1.2.840.113556.1.4.804:=7276219883574))))"
$filterPasswordLastSet = "(!(pwdLastSet=0))"
$filterPasswordNeverExpires = "(!(userAccountControl:1.2.840.113556.1.4.803:=65536))"
$filterSmartCardLogon = "(!(userAccountControl:1.2.840.113556.1.4.803:=262144))"
$filterInterdomainTrust = "(!(userAccountControl:1.2.840.113556.1.4.803:=2048))"
$filter = "(&" + $filterUsers + $filterPasswordLastSet + $filterPasswordNeverExpires +
$filterSmartCardLogon + $filterInterdomainTrust + ")"
$searcher = New-Object Softerra.Adaxes.Adsi.Search.DirectorySearcher $NULL, $False
$searcher.SearchParameters.Filter = $filter
$searcher.VirtualRoot = $True
$searcher.SearchParameters.SearchScope = "ADS_SCOPE_SUBTREE"
$searcher.SearchParameters.ReferralChasing = "ADS_CHASE_REFERRALS_NEVER"
$searcher.SearchParameters.PageSize = 500
$searcher.SetPropertiesToLoad(@("msDS-UserPasswordExpiryTimeComputed", "msDS-ResultantPSO", "pwdLastSet", "mail"))
# Map domain names to maximum password age specified in the default domain password policy
$domainToMaxPwdAge = @{}
try
{
# Execute search
$searchResultIterator = $searcher.ExecuteSearch()
$searchResults = $searchResultIterator.FetchAll()
$now = Get-Date
foreach ($searchResult in $searchResults)
{
$expiryTime = $searchResult.Properties["msDS-UserPasswordExpiryTimeComputed"].Value
if ($NULL -eq $expiryTime) # Fine-Grained Password Policies not supported
{
# Get the maximum password age from the default domain password policy
$domain = $Context.GetObjectDomain($searchResult.AdsPath.DN)
if (-not $domainToMaxPwdAge.Contains($domain))
{
# Bind to the default naming context
$nc = $Context.BindToObjectEx("Adaxes://$domain", $False)
# Get maximum password age
$maxPwdAgeLargeInt = $nc.Get("maxPwdAge")
$domainToMaxPwdAge[$domain] =
[Softerra.Adaxes.Adsi.AdsLargeInteger]::ToInt64($maxPwdAgeLargeInt)
}
$maxPwdAge = $domainToMaxPwdAge[$domain]
if ($maxPwdAge -eq 0x8000000000000000) # no maximum password age
{
continue
}
else
{
$passwordLastSet = $searchResult.Properties["pwdLastSet"].Value
$expiryTime = $passwordLastSet - $maxPwdAge
}
}
if ($expiryTime -eq 9223372036854775807)
{
continue # No password expiration specified
}
$expiresOnDate = [DateTime]::FromFiletime([Int64]::Parse($expiryTime))
$timeSpanTillExpiration = New-TimeSpan -Start $now -End $expiresOnDate
$pwdExpiresDaysLeft = $timeSpanTillExpiration.Days
if ($days -notcontains $pwdExpiresDaysLeft)
{
continue
}
# Send mail
$mail = $searchResult.Properties["mail"].Value
if ([System.String]::IsNullOrEmpty($mail))
{
$userName = $Context.GetDisplayNameFromAdsPath($searchResult.AdsPath)
$Context.LogMessage("User $userName does not have an email address.", "Warning")
}
else
{
$message = [System.String]::Format($messageTemplate, $pwdExpiresDaysLeft)
$subject = [System.String]::Format($subjectTemplate, $pwdExpiresDaysLeft)
$Context.SendMail($mail, $subject, $message, $NULL)
}
}
}
finally
{
# Release resources
if ($searchResultIterator) { $searchResultIterator.Dispose() }
}
The scheduled task should be configured for the Domain-DNS object type and have only a single managed domain in the Activity Scope. The script will always search through all users in all managed domains. There is no need to add more than one managed domain to the Activity Scope as it will make the script run twice and thus all users will be notified twice.