"Hello Again World!"
Hi there! Mike Kullish, here. I'm a Microsoft Customer Engineer (CE) based just off the Gulf Coast of Florida with a focus on AD, Hyper-V and DFS, but I try to help customers with anything on the Windows Desktop and/or Server platforms. (Also, this whole Azure thing has become a big deal, so I dabble with that as well…) I have been with Microsoft for over nine years and this is a follow-up to my first blog post written about 6 years ago which can be found here: How to Setup a Password Expiration Notification Email Solution - Microsoft Tech Community. The changes below help to eliminate the use of SMTP servers and allow you to use Microsoft Graph to send emails. This is a more modern take on the original article. I must acknowledge two of my CE colleagues that helped this update come together. First David Morillo, who assisted in wordsmithing the article, and secondly the mastermind behind the script that makes it all work, Daniel Carroll.
Have you ever had a need to configure notifications for user's password expirations but found that existing solutions didn't quite fit the bill? We all know you can use built-in solutions with Windows and Active Directory/Group Policy but this requires users to interactively log-on to a domain joined computer. What about those BYOD or mobile users or users of web apps/email? Typically, these users will have to call the helpdesk because they had no idea their domain passwords were going to expire. Statistics show that some of the most common calls to the helpdesk are password-related and implementing a process like the one covered here could really make a dent in your helpdesk call volume and costs.
The first article mentioned above enabled you to use an existing SMTP server to configure and send emails to users that had passwords which were about to expire. This solution was based on the Send-MailMessage cmdlet which is now considered obsolete. Below, we will describe the process to setup a script that uses Microsoft Graph to send email using your M365 subscription.
I thought it would make a helpful blog post to cover some of the details and considerations when implementing a solution like this. As mentioned, Daniel Carroll deserves credit for the script that follows.
DISCLAIMER:
You can download the script from the following link . I will also post a full copy of the code within the article, but updates will be made more frequently on GitHub. ( Script to Automate Email Reminders when Users Passwords due to Expire using O365 Shared Mailbox · Gi... )
Click on the blue box and save the file to a workstation or member server. Obviously, a DC would work but likely isn't the best choice. The workstation or member server needs the RSAT tools for Active Directory installed. If you already have an "admin server" system where you have existing scripts, tools, Scheduled Tasks, etc., that would be a logical place for this. I would also suggest creating a folder such as C:\temp on the tools machine in order to ensure you can follow along easily with the instructions below.
#################################################################################################################
#
# Original Robert Pearman v1.4 Passowrd Change Notification
# - Adapted to support O365 SendAS Shared Mailbox
# Script to Automate Email Reminders when Users Passwords due to Expire using O365 Shared Mailbox.
#
# Requires:
# Windows PowerShell Module for Active Directory
# Azure AD Application registration with MS Graph Application Mail.Send permission
#
#
##################################################################################################################
# Please Configure the following variables....
$expireindays = 21
$logging = "Enabled" # Set to Disabled to Disable Logging
$logFile = "" # ie. c:\mylog.csv
$testing = "Enabled" # Set to Disabled to Email Users
$testRecipient = ''
$clientId = '' # App registration ID used to send on behalf of shared mailbox
$clientSecret = (Import-Clixml -Path $PSScriptRoot\SendEmailSecret.ps1.credential).GetNetworkCredential().Password #Client Secret credential file
$tenantName = '' #TenantName
$SendEmailAccount = '' #SharedMailbox name
$resource = 'https://graph.microsoft.com' #Graph Endpoint https://graph.microsoft.com or https://graph.microsoft.us or https://dod-graph.microsoft.us
#
###################################################################################################################
$ReqTokenBody = @{
Grant_Type = "client_credentials"
Scope = "$($resource)/.default"
client_Id = $clientID
Client_Secret = $clientSecret
}
Try {
$params = @{
Uri = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
Method = "POST"
ErrorAction = "Stop"
}
$TokenResponse = Invoke-RestMethod @params -Body $ReqTokenBody
if ($TokenResponse) {
# Check Logging Settings
if (($logging) -eq "Enabled")
{
# Test Log File Path
$logfilePath = (Test-Path $logFile)
if (($logFilePath) -ne "True")
{
# Create CSV File and Headers
New-Item $logfile -ItemType File
Add-Content $logfile "Date,Name,EmailAddress,DaystoExpire,ExpiresOn,Notified"
}
} # End Logging Check
# System Settings
$textEncoding = [System.Text.Encoding]::UTF8
$date = Get-Date -format ddMMyyyy
# End System Settings
# Get Users From AD who are Enabled, Passwords Expire and are Not Currently Expired
Import-Module ActiveDirectory
$users = get-aduser -filter * -properties Name, PasswordNeverExpires, PasswordExpired, PasswordLastSet, EmailAddress |where {$_.Enabled -eq "True"} | where { $_.PasswordNeverExpires -eq $false } | where { $_.passwordexpired -eq $false }
$DefaultmaxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge
# Process Each User for Password Expiry
foreach ($user in $users)
{
$Name = $user.Name
$emailaddress = $user.emailaddress
$passwordSetDate = $user.PasswordLastSet
$PasswordPol = (Get-AduserResultantPasswordPolicy $user)
$sent = "" # Reset Sent Flag
# Check for Fine Grained Password
if (($PasswordPol) -ne $null)
{
$maxPasswordAge = ($PasswordPol).MaxPasswordAge
}
else
{
# No FGP set to Domain Default
$maxPasswordAge = $DefaultmaxPasswordAge
}
$expireson = $passwordsetdate + $maxPasswordAge
$today = (get-date)
$daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days
# Set Greeting based on Number of Days to Expiry.
# Check Number of Days to Expiry
$messageDays = $daystoexpire
if (($messageDays) -gt "1")
{
$messageDays = "in " + "$daystoexpire" + " days."
}
else
{
$messageDays = "today."
}
# If Testing Is Enabled - Email Administrator
if (($testing) -eq "Enabled")
{
$emailaddress = $testRecipient
} # End Testing
# If a user has no email address listed
if (($emailaddress) -eq $null)
{
$emailaddress = $testRecipient
}# End No Valid Email
# Email Subject Set Here
$subject="Your password will expire $messageDays"
# Email Body Set Here, Note You can use HTML.
$body = @"
{
"Message": {
"Subject": "$($subject)",
"importance":"High",
"Body": {
"ContentType": "HTML",
"Content": "<p>Dear $($name),</p>
<p> Your Password will expire $($messageDays)<br>
To change your password on a PC press CTRL ALT Delete and choose Change Password <br>
<p>Thanks, <br>
</P>"
},
"ToRecipients": [
{
"EmailAddress": {
"Address": "$($emailaddress)"
}
}
]
},
"SaveToSentItems": "false",
"isDraft": "false"
}
"@
# Send Email Message
if (($daystoexpire -ge "0") -and ($daystoexpire -lt $expireindays))
{
$sent = "Yes"
# If Logging is Enabled Log Details
if (($logging) -eq "Enabled")
{
Add-Content $logfile "$date,$Name,$emailaddress,$daystoExpire,$expireson,$sent"
}
# Send Email Message
$apiUrl = "$resource/v1.0/users/$SendEmailAccount/sendMail"
Invoke-RestMethod -Headers @{Authorization = "Bearer $($Tokenresponse.access_token)"} -Uri $apiUrl -Body $Body -Method Post -ContentType 'application/json'
} # End Send Message
else # Log Non Expiring Password
{
$sent = "No"
# If Logging is Enabled Log Details
if (($logging) -eq "Enabled")
{
Add-Content $logfile "$date,$Name,$emailaddress,$daystoExpire,$expireson,$sent"
}
}
}
} # End User Processing
} catch {
[System.ApplicationException]::new("Failed to aquire token")
}
Once you have downloaded or created your own copy of the script:
$cred = ’3AA7Q~YSlo1235Pxvjq6U7vE6uanqXYZTOqC5'
[System.Management.Automation.PSCredential]::new( "SendEmailCred", (ConvertTo-SecureString -String $cred -AsPlainText -Force) ) | Export-Clixml -Path C:\temp\test.ps1.credential
$cred = '3BW7Q~YSlo2oS5Pxvjq6U7vE6uanqQnGTOqC5'
[System.Management.Automation.PSCredential]::new( "SendEmailCred", (ConvertTo-SecureString -String $cred -AsPlainText -Force) ) | Export-Clixml -Path C:\temp\SendEmailSecret.ps1.credential
#The credential specified in $cred above should be gathered when creating the App Registration
From: someone@company.com [mailto:someone@company.com]
Sent: Thursday, September 28, 2021 12:52 PM
To: Someone@company.com
Dear chptest,
To change your password on a PC press CTRL-ALT-Delete and choose Change Password
Thanks,
Now, at some pre-determined time, you or one of your staff can execute the script to generate the 'password expiry notification email' to the affected users.
For those who don't want to manually run the script, it's a simple process to create a Scheduled Task to run the script automatically. I would strongly suggest investigating the use of a Group Managed Service account rather than a traditional user account that runs a script or service, but that is a topic for another article.
There are numerous other ways to address this need; I have talked to many people who have developed their own processes, scripts and/or code for this. This particular process was pretty easy to implement, and I was able to work with my customers and my own lab environment to get the whole thing working in a short amount of time.
One last point of consideration would be to start moving away from passwords altogether. Think about multi-factor authentication (MFA), or passwordless solutions for an added bonus. One article that will help get you started can be found here: Azure Active Directory passwordless sign-in | Microsoft Docs.
Thanks again to David Morillo, Daniel Carroll, and everyone that responded to the original post to inspire this update.
See you all next time!
Mike "CANNONBALL!" Kullish
Michael Kullish -or- aka.ms/michaelkullish
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.