PS: Notify User Password Expires

This script sends out mail notification to users when their password is about to expire. It provides a html based message to the users where you can place company specific information to reset password. If a used has no mail attribute specified an alternate admin recipient can be specified that receives notifications for those accounts so at least an admin is informed.
The script can be used with parameters however it is easier to modify the parameters inside the script and copy it if there are different settings required. Please run this script in test mode (without enabled parameter SendMailEnabled) to verify the users for the first run... You can also use the  command explained in >> KB 10109) to determine the users that will get notified by the script...


Requirements

Active Directory Module for Power Shell >> KB10102


Usage

This script sends out mail notification to users when their password is about to exire. It provides a html based message to the users where you can place company specific information
to reset password. If a used has no mail attribute specified an alternate admin recipient can bes specified that receives notifications for thos accounts so at least an admin is informed.
The HTML texts can contain variables such as to individualize the messages. - VAR_Surname -- Surname of the user object.

  • VAR_GivenName --
    Given Name of the user object.
  • VAR_Name --
    Common Name (CN) of the user object.
  • VAR_ADDSMinPasswordLength --
    Local ADDS value for the minimum password length.
  • VAR_PasswordRemainingDays --
    Days until the password will expire.
  • VAR_PasswordExpiresDate --
    Date and time when the password will expire.
  • VAR_PasswordString --
    Combined string for password requirements (This one is created dynamic during runtime)
  • VAR_ADDSComplexityEnabled --
    Returns True or False depending if the password complexity in ADDS is enabled or not.
  • VAR_ADDSPasswordHistoryCount --
    Length of ADDS password history.
  • VAR_ADDSMaxPasswordAge --
    Maximum password age configured in ADDS.
  • VAR_UPN --
    User principal name
  • VAR_sAMAccountName --
    sAMAccountName

Version History:

  • 1.0.0 - Initial release
  • 1.0.1 - Several bug fixes (original version was currupted due to character conversion errors...)
  • 1.0.2 - Script tried to send mail even if no address was specified

Script code:

	
##------------------------------------------------------------------------------------------------
##
##  Notify-UserPasswordExpires.ps1
##
##   Version 1.0.2
##
##   (c) 2015 Martin Mueller
##		www.sh-soft.com
##
##  Licence: Feel free to use and redistribute this script!
##
##------------------------------------------------------------------------------------------------

<#
.SYNOPSIS
This script sends out mail notification to users when their password is about to expire. It provides a html based message to the users where you can place company specific information
to reset password. If a used has no mail attribute specified an alternate admin recipient can be specified that receives notifications for those accounts so at least an admin is informed.
The script can be used with parameters however it is easier to modify the parameters inside the script and copy it if there are different settings required.
Please run this script in test mode (without enabled parameter SendMailEnabled) to verify the users for the first run...
You can also use the command explained in KB 10109 (https://www.sh-soft.com/en/knowledgebase/kb10109-ps-getaduserswithexpiringpassword.html) to determine the users that will get
notified by the script...


.DESCRIPTION
This script sends out mail notification to users when their password is about to expire. It provides a html based message to the users where you can place company specific information
to reset password. If a used has no mail attribute specified an alternate admin recipient can be specified that receives notifications for those accounts so at least an admin is informed.
The HTML texts can contain variables such as to individualize the messages.
 - VAR_Surname -- Surname of the user object.
 - VAR_GivenName -- Given Name of the user object.
 - VAR_Name -- Common Name (CN) of the user object.
 - VAR_ADDSMinPasswordLength -- Local ADDS value for the minimum password length.
 - VAR_PasswordRemainingDays -- Days until the password will expire.
 - VAR_PasswordExpiresDate -- Date and time when the password will expire.
 - VAR_PasswordString -- Combined string for password requirements (This one is created dynamic during runtime)
 - VAR_ADDSComplexityEnabled -- Returns True or False depending if the password complexity in ADDS is enabled or not.
 - VAR_ADDSPasswordHistoryCount -- Length of ADDS password history.
 - VAR_ADDSMaxPasswordAge -- Maximum password age configured in ADDS.
 - VAR_UPN -- User principal name
 - VAR_sAMAccountName -- sAMAccountName
 

.PARAMETER RemindDaysBeforeExpiration
The amount  of days before the script warns the user for password expiration

.PARAMETER pwdStringBasic
HTML: Password basic hints (See example in code...)

.PARAMETER pwdStringComplex
HTML: Password hint used when complex password are required

.PARAMETER pwdStringNotComplex
HTML: Can be used if there are open html tags that need to be closed...

.PARAMETER mailSubjectFirst
Mail subject if the password will expire in at least two days

.PARAMETER mailSubjectOneDay
Mail subject if the password will expire on the next day

.PARAMETER mailSubjectToday
Mail subject if the password will expire on the same day

.PARAMETER mailSubjectExpired
Mail subject if the password is already expired

.PARAMETER mailBody
HTML: This is basically the mail body in html it can contain several variables (See DESCRIPTION) and should contain the variable 
$PasswordRequirementsString because this contains information about how the new password should look like.

.PARAMETER mailBodyExpired
HTML: Mail Body for already expired passwords. This should contain information about how to reset a password or get further assistance

.PARAMETER DoNotifyOnExpiredPassword
Enable or disable notification for passwords that are already expired...

.PARAMETER mailSender
needs to be a valid SMTP address. This one is used as the sender of the notification mails

.PARAMETER adminRecipientAddress
needs to be a valid SMTP address. This on is used as alternate or additional admin recipient for the notifications

.PARAMETER adminRecipientAlwaysInCopy
if enable this puts the admin recipient always in copy

.PARAMETER adminRecipeintUseIfNoMailAvailableOnUser
if enabled it uses the admin recipient if the user object has no mail address configured (maybe for service or admin accounts...)

.PARAMETER SMTPServerName
DNS name or IP address of the SMTP server. A server with no authentication is required.

.PARAMETER ADDSSearchBase
Can be used if only users in a given OU should be processed by the script

.PARAMETER LogFilePath
Path where the log file should be created (If logging is enabled)

.PARAMETER LogFileBaseName
Name of the log file. This string gets appended to the date time value (.PARAMETER LogFileDateTime)
Default path is the script executable directory

.PARAMETER LogFileDateTime
Date time formatting string that can be used to create log files on for example a daily basis (yyyyMMdd)

.PARAMETER NoLogging
Disabled by default. This parameter can be used to prevent log file creation

.PARAMETER DebugLogging
Disabled by default. This parameter can be used to enable debug logging (this increases the size of the log file)
To enable debug logging the parameter NoLogging needs to be $false

.PARAMETER DebugToScreen
Also displays log output on the screen (DebugLogging needs to be enabled for this)

.PARAMETER SendMailEnabled
This one is disabled by default for security reasons. It needs to be enabled to send out the notifications. 

#>

#------------------------------------------------------------------------------------------------
# Parameter block
#------------------------------------------------------------------------------------------------
param(
	#RemindDaysBeforeExpiration
	[int] $RemindDaysBeforeExpiration = 14,
	#BasicPWDString
	[String] $pwdStringBasic = "<br><h2>Requirements for a new password</h2>The new password must meet the following requirements :<ul><li>Minimum length: <b>VAR_ADDSMinPasswordLength</b> characters</li><li>it cannot be identical to the last <b>VAR_ADDSPasswordHistoryCount</b> passwords</li>",
	#ComplexPasswordRequiredString
	[String] $pwdStringComplex = "<li>it needs to fulfil the complexity requirements. So it needs to match three out of the following four criteria:<ul><li>Capital letters (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)</li><li>small letters (a b c d e f g h i j k l m n o p q r s t u v w x y z)</li><li>Numbers (0 1 2 3 4 5 6 7 8 9)</li><li>Special characters (~ ! @ # $ % ^ & * ( ) _ + - = { } | \ : ; ' < > ? , .)</li></ul></li></ul>",
	[String] $pwdStringNotComplex = "</ul>",
	#Mail Subjects
	[String] $mailSubjectFirst = "Reminder: Your password will expire in VAR_PasswordRemainingDays days!",
	[String] $mailSubjectOneDay = "Reminder: Your password will expire in one day!",
	[String] $mailSubjectToday = "Reminder: Your password will expire TODAY!",
	[String] $mailSubjectExpired = "Your password is already expired!",
	#Mail Body
	[String] $mailBody = "<html><h1>VAR_GivenName VAR_Surname (VAR_Name), Your computer password for the account VAR_UPN will expire on VAR_PasswordExpiresDate </h1>This mail should remind you that you password will expire in VAR_PasswordRemainingDays days. Please change your password when you have the chance to do so!<br><br>VAR_PasswordString</html>",
	[String] $mailBodyExpired = "<html><h1>You password expired on VAR_PasswordExpiresDate </h1>This mail should help you to get access to the required system. Please contact to IT support for further assistance if you can no longer log on to the system!</html>",
	#Notify on expired password
	[bool] $DoNotifyOnExpiredPassword = $true,
	#Mail Sender
	[String] $mailSender = "passwordreminder@your.domain",
	#AdminRecipient
	[String] $adminRecipientAddress = "passwordreminder@your.domain",
	[bool] $adminRecipientAlwaysInCopy = $false,
	[bool] $adminRecipeintUseIfNoMailAvailableOnUser = $true,
	#SMTP Server
	[String] $SMTPServerName = "your-smpt-server.name",
	#ADDS Search Base
	[String] $ADDSSearchBase = "",
	#LOGGING
	[String] $LogFilePath = (Split-Path -Parent -Path $MyInvocation.MyCommand.Definition),
	[String] $LogFileBaseName = "_NotifyUsers.log",
	[String] $LogFileDateTime = "yyyyMMdd.HHmmss",
	[bool] $NoLogging = $false, 
	[bool] $DebugLogging = $false, 
	[bool] $DebugToScreen = $false,
	[bool] $SendMailEnabled = $false
)


#------------------------------------------------------------------------------------------------
# Configure Environment and basic functions
#------------------------------------------------------------------------------------------------
import-module ActiveDirectory	
# LOGWRITER
$Date = Get-Date -Format $LogFileDateTime
function Write-Log ($Entry, $DebugEntry) {
	$DateTime = Get-Date -Format "yyyyMMdd;HHmmss;"
	$FileName = Join-Path -Path $LogFilePath -ChildPath ($Date + $LogFileBaseName)
	## USER INTERFACE
	if ($DebugToScreen) {
		if ($Entry) {
			Write-Host -ForegroundColor Cyan ($DateTime+$Entry)
		}
		if ($DebugEntry) {
			Write-Host -ForegroundColor Magenta ($DateTime+"[DEBUG]") -nonewline
			Write-Host -ForegroundColor Cyan $DebugEntry
		}
	}
	if ($LogFilePath.Length -eq $null) {
		$FileName = ($Date + "-" + $LogFileBaseName)
	}
	if ($Entry) {
		Add-Content -Path $FileName -Value ($DateTime + $Entry)
	}
	if ($DebugLogging -and $DebugEntry) {
		Add-Content -Path $FileName -Value ($DateTime +" [DEBUG] "+ $DebugEntry)
	}
}
#------------------------------------------------------------------------------------------------
# Replace Variables
function Replace-Variables ($Value) {
	$ReplacedValue = $Value.Replace("VAR_Name", $Name)
	$ReplacedValue = $ReplacedValue.Replace("VAR_Surname", $Surname)
	$ReplacedValue = $ReplacedValue.Replace("VAR_GivenName", $GivenName)
	$ReplacedValue = $ReplacedValue.Replace("VAR_ADDSMinPasswordLength", $ADDSMinPasswordLength)
	$ReplacedValue = $ReplacedValue.Replace("VAR_PasswordRemainingDays", $PasswordRemainingDays)
	$ReplacedValue = $ReplacedValue.Replace("VAR_PasswordExpiresDate", $PasswordExpiresDate)
	$ReplacedValue = $ReplacedValue.Replace("VAR_ADDSComplexityEnabled", $ADDSComplexityEnabled)
	$ReplacedValue = $ReplacedValue.Replace("VAR_ADDSPasswordHistoryCount", $ADDSPasswordHistoryCount)
	$ReplacedValue = $ReplacedValue.Replace("VAR_ADDSMaxPasswordAge", $ADDSMaxPasswordAge)
	$ReplacedValue = $ReplacedValue.Replace("VAR_PasswordString", $pwdString)
	$ReplacedValue = $ReplacedValue.Replace("VAR_UPN", $userPrincipalName)
	$ReplacedValue = $ReplacedValue.Replace("VAR_sAMAccountName", $sAMAccountName)
	return $ReplacedValue
}


#------------------------------------------------------------------------------------------------
# retrieve environmental parameters (ADDS, ....)
#------------------------------------------------------------------------------------------------
#get ADDS values for
$ADDSpwdPolicy = Get-ADDefaultDomainPasswordPolicy
$ADDSMaxPasswordAge = $ADDSpwdPolicy.MaxPasswordAge.Days
Write-Log -DebugEntry ("[ADDSMaxPasswordAge] $ADDSMaxPasswordAge")
$ADDSComplexityEnabled = $ADDSpwdPolicy.ComplexityEnabled
Write-Log -DebugEntry ("[ADDSComplexityEnabled] $ADDSComplexityEnabled")
$ADDSPasswordHistoryCount = $ADDSpwdPolicy.PasswordHistoryCount
Write-Log -DebugEntry ("[ADDSPasswordHistoryCount] $ADDSPasswordHistoryCount")
$ADDSMinPasswordLength = $ADDSpwdPolicy.MinPasswordLength
Write-Log -DebugEntry ("[ADDSMinPasswordLength] $ADDSMinPasswordLength")

#------------------------------------------------------------------------------------------------
# Build password string
#------------------------------------------------------------------------------------------------
$pwdString = $pwdStringBasic
if ($ADDSComplexityEnabled -eq "True") {
	$pwdString = $pwdString + $pwdStringComplex
}
else {
	$pwdString = $pwdString + $pwdStringNotComplex
}


#------------------------------------------------------------------------------------------------
# Define some variables
#------------------------------------------------------------------------------------------------
$MaxPWDAgeInDays = $ADDSMaxPasswordAge - $RemindDaysBeforeExpiration
Write-Log -DebugEntry ("[MaxPWDAgeInDays] $MaxPWDAgeInDays")
$LastAccepatableDay = (Get-Date).AddDays((0-$MaxPWDAgeInDays))
Write-Log -DebugEntry ("[LastAccepatableDay] $LastAccepatableDay")

#------------------------------------------------------------------------------------------------
# Retrieve Users
#------------------------------------------------------------------------------------------------
$UsersToNotify = @()
if ($ADDSSearchBase.Length -gt 0) {
	Write-Log -Entry "Doing search with SearchBase enabled"
	$UsersToNotify = Get-ADUser -SearchBase "$ADDSSearchBase" `
								-Filter "pwdLastSet -lt $($LastAccepatableDay.ToFileTimeUTC()) -and pwdLastSet -ne 0 -and PasswordNeverExpires -eq 'False' -and enabled -eq 'true'" `
								-Properties pwdLastSet, Mail | `
										Sort-Object -Property pwdLastSet | `
										Select-Object -Property Name, Surname, userPrincipalName, sAMAccountName, GivenName, Mail , @{name='pwdLastSet'; expression={[datetime]::fromFileTime($_.pwdLastSet)}}
}
else {
	Write-Log -Entry "Doing search with SearchBase disabled"
	$UsersToNotify = Get-ADUser -Filter "pwdLastSet -lt $($LastAccepatableDay.ToFileTimeUTC()) -and pwdLastSet -ne 0 -and PasswordNeverExpires -eq 'False' -and enabled -eq 'true'" `
								-Properties pwdLastSet, Mail | `
									Sort-Object -Property pwdLastSet | `
									Select-Object -Property Name, Surname, userPrincipalName, sAMAccountName, GivenName, Mail, @{name='pwdLastSet'; expression={[datetime]::fromFileTime($_.pwdLastSet)}}
}

if ($SendMailEnabled) {
	Write-Host -ForegroundColor Red "Running in test mode! No Mail will be send!"
}

#------------------------------------------------------------------------------------------------
# Check whether there are users returned
#------------------------------------------------------------------------------------------------
if ($UsersToNotify.Count -gt 0) {
	foreach ($User in $UsersToNotify) {
		#------------------------------------------------------------------------------------------------
		# Create Message 
		$Message = new-object Net.Mail.MailMessage
		$Message.From = $mailSender
		$Message.IsBodyHtml = $true
		Write-Log -Entry ("Processing user: "+$User.Name)
		Write-Log -DebugEntry ("[pwdLastSet]: "+$User.pwdLastSet)
		$PasswordExpiresDate = ($user.pwdLastSet).AddDays($ADDSMaxPasswordAge)
		Write-Log -DebugEntry ("[PasswordExpiresDate]: $PasswordExpiresDate")
		$PasswordRemainingDays = ($PasswordExpiresDate - (Get-Date)).Days
		Write-Log -DebugEntry ("[PasswordRemainingDays]: $PasswordRemainingDays")
		#Replacement variables
		$Name = $User.Name
		$Surname = $User.Surame
		$GivenName = $User.GivenName
		$userPrincipalName = $User.userPrincipalName
		$sAMAccountName = $User.sAMAccountName
		
		#------------------------------------------------------------------------------------------------
		# Notify the user when password is already expired
		if ($PasswordRemainingDays -lt 0 -and $DoNotifyOnExpiredPassword) {
			$Message.Subject = $mailSubjectExpired
			$Message.Body = $mailBodyExpired
		}
		#------------------------------------------------------------------------------------------------
		# Notify the user to change the password
		else {
			#Subject
			switch ($PasswordRemainingDays) {
				1{
					$Message.Subject = $mailSubjectOneDay
				}
				0{
					$Message.Subject = $mailSubjectToday
				}
				default{
					$Message.Subject = $mailSubjectFirst
				}
			}
			#Body
			$Message.Body = $mailBody
		}
		if ($user.Mail) {
			$Message.To.Add($User.Mail)
			if ($adminRecipientAlwaysInCopy -and $adminRecipientAddress) {
				$Message.CC.Add($adminRecipientAddress)
				Write-Log -DebugEntry ("adding admin recipient to CC...")
			}
		}
		else {
			Write-Log -Entry ("No mail address found on ADDS user: "+$User.Name)
			if ($adminRecipeintUseIfNoMailAvailableOnUser -and $adminRecipientAddress) {
				Write-Log -DebugEntry ("Sending to admin recipient instead...")
				$Message.To.Add($adminRecipientAddress)
			}
		}
		
		#------------------------------------------------------------------------------------------------
		# Replace variables in input
		$Message.Subject = Replace-Variables -Value $Message.Subject
		$Message.Body = Replace-Variables -Value $Message.Body
		#Doing this a second time to replace variables in the password string
		$Message.Body = Replace-Variables -Value $Message.Body

		#------------------------------------------------------------------------------------------------
		# Send Mail or not...
		if ($PasswordRemainingDays -gt 0 -or ($PasswordRemainingDays -lt 0 -and $DoNotifyOnExpiredPassword)) {
			Write-Log -DebugEntry ("[MSG-To] "+$Message.To)
			Write-Log -DebugEntry ("[MSG-CC] "+$Message.CC)
			Write-Log -DebugEntry ("[MSG-Subject] "+$Message.Subject)
			Write-Log -DebugEntry ("[MSG-Body] "+$Message.Body)
			if ($SendMailEnabled -and $Message.To.Count -ne 0) {
				$SMTPConnection = new-object Net.Mail.SmtpClient($SMTPServerName)
				$SMTPConnection.Send($Message)
				Write-Log -DebugEntry ("[MSG-SEND]")
			}
		}
		else {
			Write-Log -DebugEntry ("[NO-MSG] Password already expired notification is disabled.")
			Write-Log -DebugEntry ("[NO-MSG] No notification will be sent out to this user!")
		}
	}
}
else {
	Write-Log -Entry "No users found to inform."
}
Write-Log -Entry "Finished script execution."
	

Download the script:

>> Version 1.0.2 (current)
(MD5: c2f0ecb83734e629d146f10bee44d456)
(SHA1: 88b29247fd3d31840e18c8190d0cfe8676b2aa73)


>> syntax highlighting powered by highlight.js