# ============================================================================================= Name: Send Password Expiry Notifications to users in Microsoft 365 Version: 1.0 Website: o365reports.com Script Highlights: ~~~~~~~~~~~~~~~~~ 1. Sends password expiry notifications to users about upcoming password expiry. 2. Filters results to display licensed users alone. 3. Export a list of users who fall under the given criteria 4. Automatically install the Microsoft Graph PowerShell module (if not installed already) upon your confirmation. 5. The script can be executed with MFA and non-MFA accounts. 6. It can be executed with certificate-based authentication (CBA) too. 7. The script is schedular-friendly – automatically schedule the script in the task scheduler to automate sending password expiry notifications. For detailed Script execution: https://o365reports.com/2025/02/11/send-password-expiry-notification-in-microsoft-365/ ============================================================================================ #> Param ( [Parameter(Mandatory = $True)] [int]$DaysToExpiry, [Parameter(Mandatory = $false)] [switch]$LicensedUsersOnly, [switch]$Schedule, [string]$FromAddress, [string]$ClientId, [string]$TenantId, [string]$CertificateThumbprint, [Parameter(DontShow = $True)] [switch]$DoNotShowSummary ) $Date = Get-Date $CSVFilePath ="$(Get-Location)\PasswordExpiryNotificationSummary_$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).csv" [datetime]$StartTime = ($Date).AddDays(1).Date.AddHours(10) #Update the value of AddHours to change the Schedule start time. $ScriptPath = $MyInvocation.MyCommand.Path # Function to connect to Microsoft Graph function Connect_ToMgGraph { # Check if Microsoft Graph module is installed $MsGraphModule = Get-Module Microsoft.Graph -ListAvailable if ($MsGraphModule -eq $null) { Write-Host "`nImportant: Microsoft Graph module is unavailable. It is mandatory to have this module installed in the system to run the script successfully." $confirm = Read-Host "Are you sure you want to install Microsoft Graph module? [Y] Yes [N] No" if ($confirm -match "[yY]") { Write-Host "Installing Microsoft Graph module..." Install-Module Microsoft.Graph -Scope CurrentUser -AllowClobber Write-Host "Microsoft Graph module is installed in the machine successfully" -ForegroundColor Magenta } else { Write-Host "Exiting. `nNote: Microsoft Graph module must be available in your system to run the script" -ForegroundColor Red Exit } } Write-Host "`nConnecting to Microsoft Graph..." if (($TenantId -ne "") -and ($ClientId -ne "") -and ($CertificateThumbprint -ne "")) { # Use certificate-based authentication if TenantId, ClientId, and CertificateThumbprint are provided Connect-MgGraph -TenantId $TenantId -AppId $ClientId -CertificateThumbprint $CertificateThumbprint -NoWelcome } else { # Use delegated permissions (Scopes) if credentials are not provided Connect-MgGraph -Scopes "User.Read.All", "Domain.Read.All", "Mail.Send.Shared" -NoWelcome } # Verify connection if ((Get-MgContext) -ne $null) { if ((Get-MgContext).Account -ne $null) { Write-Host "Connected to Microsoft Graph PowerShell using account: $((Get-MgContext).Account)" } else { Write-Host "Connected to Microsoft Graph PowerShell using certificate-based authentication." } } else { Write-Host "Failed to connect to Microsoft Graph." -ForegroundColor Red Exit } } Connect_ToMgGraph if ((Get-MgContext).Account -ne $null){ if ([string]::IsNullOrEmpty($FromAddress)) { $FromAddress = (Get-MgContext).Account } } else { if ([string]::IsNullOrEmpty($FromAddress)) { Write-Host "`nError: FromAddress is required when using certificate-based authentication." -ForegroundColor Red Exit } } # Schedule Task if ($Schedule.IsPresent) { Write-Host "Configuring scheduled task to send password expiry notification..." # Validate mandatory parameters for scheduling if (-not $TenantId -or -not $ClientId -or -not $CertificateThumbprint) { Write-Host "`nError: TenantId, ClientId, and CertificateThumbprint are mandatory for scheduling the script." -ForegroundColor Red Exit } # Define the action and trigger for the schedule to execute the script $Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File `"$($ScriptPath)`" -DoNotShowSummary -TenantId `"$($TenantId)`" -ClientId `"$($ClientId)`" -CertificateThumbprint `"$($CertificateThumbprint)`" -DaysToExpiry $($DaysToExpiry) -FromAddress `"$($FromAddress)`" -WindowStyle Hidden" $Trigger = New-ScheduledTaskTrigger -Daily -At $StartTime $Principal = New-ScheduledTaskPrincipal -UserId $env:UserName -LogonType Interactive -RunLevel Highest # Register the task $TaskName = "Password Expiry Notification" try { Register-ScheduledTask -Action $Action -Trigger $Trigger -Principal $Principal -TaskName $TaskName -Description "Runs the Password Expiry Notification script" -ErrorAction Stop | Out-Null Write-Host "`nScheduled task '$TaskName' created successfully and it will run daily at $($StartTime.ToString('hh:mm tt')) from $($StartTime.ToString('yyyy-MM-dd'))." -ForegroundColor Cyan } catch { Write-Host "`nError: Failed to register the scheduled task." -ForegroundColor Red Write-Host "Details: $_" -ForegroundColor Red Exit } } $Counter = 0 $PwdExpirigUsersCount = 0 $Domains = @{} Get-MgDomain | Select-Object Id, AuthenticationType, PasswordValidityPeriodInDays | ForEach-Object { if ($_.AuthenticationType -eq "Federated") { $Domains[$_.Id] = 0 } else { if ($_.PasswordValidityPeriodInDays -ne $null) { $Domains[$_.Id] = $_.PasswordValidityPeriodInDays } else { $Domains[$_.Id] = 90 } } } Get-MgUser -Filter "accountEnabled eq true" -All -Property AssignedLicenses, PasswordPolicies, DisplayName, UserPrincipalName, LastPasswordChangeDateTime | Where-Object {$_.PasswordPolicies -notcontains "DisablePasswordExpiration"} | ForEach-Object { $Name = $_.DisplayName $EmailAddress = $_.UserPrincipalName $LicenseStatus = $_.AssignedLicenses $PwdLastChangeDate = $_.LastPasswordChangeDateTime $UserDomain = $EmailAddress.Split('@')[1] $MaxPwdAge = $Domains[$UserDomain] $Counter++ Write-Progress -Activity "Processed Users: $($Counter)" -Status "Processing $($_.DisplayName)" if ($MaxPwdAge -ne 2147483647) { $ExpiryDate = $PwdLastChangeDate.AddDays($MaxPwdAge) $DaysToExpire = ($ExpiryDate.Date - $Date.Date).Days if ($LicenseStatus -ne $null) { $LicenseStatus = "Licensed" } else { $LicenseStatus = "Unlicensed" } if($DaysToExpire -eq 0){ $Msg = "Today" } elseif($DaysToExpire -eq 1){ $Msg = "Tomorrow" } else{ $Msg = "in " + "$DaysToExpire" + " days" } $params = @{ message = @{ subject = "Your Password is About to Expire – Update Required" body = @{ contentType = "HTML" content = "Hello $($Name),
Your Microsoft 365 account password will expire $($Msg).
To avoid disruptions in accessing Microsoft 365 apps and services, please update your password promptly by visiting the secure Microsoft Office portal.
If you have any questions or encounter issues, feel free to contact help desk for assistance.
Thank you for your attention to this matter.
Best regards,