<# .SYNOPSIS Svendsen Tech Password Expiration Notifier. Emails users a custom notification message the specified number of days before their passwords expire. .DESCRIPTION Edit pwdnot.conf to suit your environment. Edit last_warning.txt and first_warning.txt to contain the first and last warning mail. To only use one, just set the config file value for either the first or the last "days" to something that won't ever be triggered, like 500000. Please refer to the online Svendsen Tech PowerShell Wiki for further documentation: http://www.powershelladmin.com/wiki/Active_directory_password_expiration_notification Author: Joakim Svendsen, Svendsen Tech #> # Hard-coded file names. $errorLog = 'error.log' $mailSentLog = 'mail_sent.log' $mailErrorLog = 'mail_error.log' $firstWarnFile = 'first_warning.txt' $lastWarnFile = 'last_warning.txt' $configFile = 'pwdnot.conf' # Utility function that turns unquoted, space-separated strings into an array. # It means you can write "ql foo bar baz" rather than "@('foo', 'bar', 'baz')". function ql { $args } # Function to parse the configuration file. function Parse-Config { $config = @{} if (-not (Test-Path -PathType Leaf $configFile)) { Write-Host "${date}: Fatal error: File $configFile not found. Processing aborted." "${date}: Fatal error: File $configFile not found. Processing aborted." | Out-File -Append $errorLog exit 1 } Get-Content $configFile | Where-Object { $_ -match '\S' } | # Skip blank (whitespace only) lines. Foreach-Object { $key, $value = $_ -split '\s*=\s*'; $config.$key = $value } $requiredValues = ql smtp smtpPort from firstWarningDays lastWarningDays useDefaultCredentials enableSSL # Initialize this to false and exit after the loop if a required value is missing. [bool] $missingRequiredValue = $false foreach ($requiredValue in $requiredValues) { if (-not $config.ContainsKey($requiredValue)) { Write-Host "${date}: Error: Missing '$requiredValue'. Processing will be aborted." "${date}: Error: Missing '$requiredValue'. Processing will be aborted." | Out-File -Append $errorLog $missingRequiredValue = $true } } # Set the default e-mail address LDAP attribute/field unless specified (default: 'Email') if (-not $config.ContainsKey('emailAttribute')) { $config.'emailAttribute' = 'Email' } # Exit the program if a required value is missing in the configuration file. if ($missingRequiredValue) { exit 2 } $config } # Function to actually send the mail function Send-Warn-Mail { param( [string] $private:type, [string] $private:to, [string] $private:message, [string] $private:username ) if ($private:type -ieq 'first') { $private:subject = 'Your Password Will Expire In ' + $config.firstWarningDays + ' Days' } elseif ($private:type -ieq 'last') { $private:subject = 'Your Password Will Expire In ' + $config.lastWarningDays + ' Days!' } else { Write-Host "${date}: Wrong type (not first/last) received in Send-Warn-Mail for $private:to. Skipping." "${date}: Wrong type (not first/last). Error sending to $private:to" | Out-File -append $mailErrorLog return } $private:SmtpClient = New-Object System.Net.Mail.SmtpClient($config.smtp, $config.smtpPort) # If useDefaultCredentials is set to true, change the property on the SMTP client object. # It is false by default. if ($config.useDefaultCredentials -imatch 'true') { $private:SmtpClient.UseDefaultCredentials = $true } # If enableSSL is set to "true" in the config file, enable SSL. if ($config.enableSSL -imatch 'true') { $private:SmtpClient.enableSSL = $true } # Send the mail $private:SmtpClient.Send($config.from, $private:to, $private:subject, $private:message) if ( $? ) { "${date}: Sent $private:type warning mail to $private:to (Username: $private:username)" | Out-File -Append $mailSentLog } else { "${date}: Error sending $private:type warning mail to $private:to (Username: $private:username): " + $error[0].ToString() | Out-File -Append $mailErrorLog } # "Clear" the object/variable. Seems necessary. $private:SmtpClient = $null } # Simple function to get the number of days until the passwords expire. function Get-Pwd-Days { param([datetime] $private:pwdExpires) ($private:pwdExpires - $(Get-Date)).Days } # Function to iterate the first and last user sets and call the mail sending function. Function Iterate-Users { Param([Parameter(Mandatory=$true)][string] $private:type, [Parameter(Mandatory=$true)] $private:iterateUsers) foreach ($user in $private:iterateUsers) { if (-not $user.($config.emailAttribute)) { Write-Host "${date}: Error: No value for email address (LDAP: $($config.emailAttribute)) for '" $user.SamAccountName ` "'. Name:" $user.GivenName $user.Sn "${date}: Error: No email address (LDAP: $($config.emailAttribute)) for '" + $user.SamAccountName + ` "'. Name: " + $user.GivenName + ' ' + $user.Sn | Out-File -Append $mailErrorLog continue } $private:mailGreeting = 'Dear ' + $user.GivenName +' '+ $user.Sn + ' (username: ' + $user.SamAccountName.ToLower() + ").`n`n" $private:to = ($user.($config.emailAttribute)).ToLower() if ($private:type -ieq 'first') { $private:message = "$private:mailGreeting`n" + ($firstWarnText -join "`n") } else { $private:message = "$private:mailGreeting`n" + ($lastWarnText -join "`n") } Send-Warn-Mail $private:type $private:to $private:message $user.SamAccountName.ToLower() } } ######### END OF FUNCTIONS ########## # Get current year, month and day for later use. $date = Get-Date -uformat %Y-%m-%d # Parse the configuration file (very little sanity checking). $config = Parse-Config $configFile # Set this to true if a file isn't found, and exit after the foreach [bool] $private:notFound = $false foreach ($private:file in @($firstWarnFile, $lastWarnFile)) { if ( ! (Test-Path $private:file) ) { Write-Host ${date}": Fatal error: Did not find $private:file." "${date}: Fatal error: Did not find $private:file." | Out-File -Append $errorLog $private:notFound = $true } } if ($private:notFound) { Write-Host ${date}": Fatal error: See $private:errorLog" exit 3 } # We know that these exist from earlier verification. $firstWarnText = Get-Content $firstWarnFile $lastWarnText = Get-Content $lastWarnFile Add-PSSnapin Quest.ActiveRoles.AdManagement $private:users = Get-QADUser -SizeLimit 0 | Select-Object SamAccountName, GivenName, Sn, $config.emailAttribute, PasswordExpires # Get users whose passwords will expire in the given amounts of days $private:firstWarnUsers = $private:users | Where-Object { $_.PasswordExpires -and ((Get-Pwd-Days $_.PasswordExpires) -eq $config.firstWarningDays) } $private:lastWarnUsers = $private:users | Where-Object { $_.PasswordExpires -and ((Get-Pwd-Days $_.PasswordExpires) -eq $config.lastWarningDays ) } <# Debug '';'';'First:';'';'' $private:firstWarnUsers '';'';'Last:';'';'' $private:lastWarnUsers #> if ($private:firstWarnUsers) { Iterate-Users 'first' $private:firstWarnUsers } if ($private:lastWarnUsers) { Iterate-Users 'last' $private:lastWarnUsers }