Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This article shows you how to create a comprehensive password expiration report for licensed users in your Microsoft Entra ID tenant using Microsoft Entra PowerShell. The script generates a CSV report with detailed password status information, statistics, and alerts for users with expiring or expired passwords.
Prerequisites
The scenario outlined in this article assumes that you already have the following items:
- Microsoft Entra PowerShell module installed. Follow the installation guide to install the module.
- One of the following roles: Global Reader, User Administrator, Authentication Administrator, or Cloud application administrator
- Permissions to your Microsoft Entra tenant:
User.Read.All
- Required to read user profile informationDomain.Read.All
- Required to read ___domain password policiesOrganization.Read.All
- Required to read organization informationAuditLog.Read.All
- Required to read user sign-in activity (optional)
Note
Sign-in activity data (AuditLog.Read.All
) might not be available in all Microsoft Entra PowerShell environments. The script handles this and continues without sign-in data if unavailable.
Before running the script, establish a connection to your Microsoft Entra tenant:
Connect-Entra -Scopes "User.Read.All", "Domain.Read.All", "Organization.Read.All", "AuditLog.Read.All"
Understand the password expiration report
The script performs the following operations:
- Retrieves tenant password policy by getting the default ___domain password validity period
- Filters for active users with assigned licenses
- Calculates password expiration dates using the last password change date and policy settings
- Generates comprehensive reports by creating a CSV output file
- Provides statistical analysis that shows averages and trends
- Highlights urgent cases for passwords expiring within 30 days or already expired
Run the password expiration report script
The following script creates a complete password expiration report:
$Documents = [Environment]::GetFolderPath("MyDocuments")
$CSVOutputFile = Join-Path $Documents "PasswordExpirationReport-Entra.CSV"
# Check what the tenant password expiration policy is
try {
$DefaultDomain = Get-EntraDomain | Where-Object {$_.IsDefault -eq $true}
$PasswordLifetime = $DefaultDomain.PasswordValidityPeriodInDays
If ($PasswordLifetime -eq 2147483647) {
$TenantPasswordExpirationDisabled = $true
# adjust the value otherwise the date calculation will fail
$PasswordLifetime = 20000
} Else {
$TenantPasswordExpirationDisabled = $false
}
} catch {
$PasswordLifetime = 90
$TenantPasswordExpirationDisabled = $false
}
# Get organization name
try {
$OrgName = Get-EntraOrganization | Select-Object -ExpandProperty DisplayName
} catch {
$OrgName = "Unknown Organization"
}
# Find licensed member accounts
try {
[Array]$Users = Get-EntraUser -All -Property Id, DisplayName, UserPrincipalName, Country, Department, `
AssignedLicenses, JobTitle, AccountEnabled, CreatedDateTime, PasswordPolicies, LastPasswordChangeDateTime, `
UserType, CompanyName |
Where-Object { $_.AssignedLicenses.Count -gt 0 -and $_.UserType -eq 'Member' } |
Sort-Object DisplayName
} catch {
throw "Failed to retrieve users: $($_.Exception.Message)"
}
# Extract Information about each user
$Report = [System.Collections.Generic.List[Object]]::new()
ForEach ($User in $Users) {
$DisabledPasswordExpiry = $false
$WarningMessage = $null
$LastSignIn = $null
$DaysSinceLastSignIn = "N/A"
# Check if the user account password policy disables password expiration
If ($User.PasswordPolicies -like "*DisablePasswordExpiration*") {
$DisabledPasswordExpiry = $true
}
# Handle cases where LastPasswordChangeDateTime might be null
if ($User.LastPasswordChangeDateTime) {
# Calculate the password expiry date
[datetime]$PasswordExpiryDate = $User.LastPasswordChangeDateTime.AddDays($PasswordLifetime)
# Calculate the number of days to password expiration
$DaystoExpiration = ($PasswordExpiryDate - (Get-Date)).Days
$PasswordLastChanged = $User.LastPasswordChangeDateTime | Get-Date -Format 'dd-MMM-yyyy HH:mm:ss'
$PasswordExpiryFormatted = $PasswordExpiryDate | Get-Date -Format 'dd-MMM-yyyy HH:mm:ss'
} else {
# Handle users who have never changed their password
$DaystoExpiration = "Unknown"
$PasswordLastChanged = "Never changed"
$PasswordExpiryFormatted = "Unknown"
}
# Try to get sign-in activity (this may not be available in all Microsoft Entra PowerShell versions)
try {
$SignInActivity = Get-EntraUserSignInActivity -UserId $User.Id -ErrorAction SilentlyContinue
if ($SignInActivity.LastSignInDateTime) {
$LastSignIn = $SignInActivity.LastSignInDateTime | Get-Date -Format 'dd-MMM-yyyy HH:mm:ss'
$DaysSinceLastSignIn = ((Get-Date) - $SignInActivity.LastSignInDateTime).Days
}
} catch {
# Sign-in activity not available - this is common in some Microsoft Entra PowerShell versions
$LastSignIn = "Not available"
$DaysSinceLastSignIn = "N/A"
}
# Generate warning message for passwords expiring soon
if ($DaystoExpiration -is [int] -and $DaystoExpiration -lt 30 -and $DaystoExpiration -gt 0) {
$WarningMessage = ("Password expires in {0} days" -f $DaystoExpiration)
} elseif ($DaystoExpiration -is [int] -and $DaystoExpiration -le 0) {
$WarningMessage = "Password has expired"
}
$ReportLine = [PSCustomObject][Ordered]@{
UserDisplayName = $User.DisplayName
UserPrincipalName = $User.UserPrincipalName
Department = $User.Department
'Job title' = $User.JobTitle
'Last sign in' = $LastSignIn
'Days since sign in' = $DaysSinceLastSignIn
'Password last changed' = $PasswordLastChanged
PasswordExpiryDate = $PasswordExpiryFormatted
DaysToExpiration = $DaystoExpiration
'Account Password Expiry Disabled' = $DisabledPasswordExpiry
'Account enabled' = $User.AccountEnabled
Status = $WarningMessage
}
$Report.Add($ReportLine)
}
# Export CSV report
$Report | Export-Csv -Path $CSVOutputFile -NoTypeInformation -Encoding UTF8
The script generates a CSV report with detailed data for all processed users.
Each user record includes:
- User information - Display name, UPN, department, job title
- Password data - Last change date, expiry date, days to expiration
- Sign-in activity - Last sign-in date and days since last sign-in (when available)
- Policy status - Whether password expiration is disabled
- Account status - Whether the account is enabled
Customize the script
You can modify the script for your specific needs using the following customization options:
Customization | Description | Code example |
---|---|---|
Change the warning threshold | Modify the number of days before password expiration to trigger warnings | # Set your desired warning threshold (e.g. 14 days) if ($DaystoExpiration -is [int] -and $DaystoExpiration -lt 14 -and $DaystoExpiration -gt 0) { # … } |
Filter by Department | Process only users from a specific department | # Add department filtering [Array]$Users = Get-EntraUser -All -Property … | Where-Object { $.AssignedLicenses.Count -gt 0 -and $.UserType -eq 'Member' -and $_.Department -eq "IT Department" } | Sort-Object DisplayName |
You can also schedule this script to run regularly to keep your password expiration data up-to-date.
To run this report regularly, consider the following options:
- Create a scheduled task in Windows Task Scheduler
- Use PowerShell scheduled jobs with
Register-ScheduledJob
- Implement in automation platforms like Azure Automation
Troubleshoot common issues
Use the following troubleshooting guidance to resolve common issues when running the password expiration report:
Issue | Description | Solution |
---|---|---|
Permission denied errors | Access denied when running the script due to insufficient permissions | Ensure your account has the required roles and permissions. Reconnect with all necessary scopes:Connect-Entra -Scopes "User.Read.All", "Domain.Read.All", "Organization.Read.All", "AuditLog.Read.All" |
Missing sign-in data | Sign-in activity data is not available or cannot be retrieved | Sign-in activity may not be available in all environments. The script handles this issue and continues without this data. |
Large tenant performance | Script runs slowly or times out when processing large numbers of users | For tenants with many users, consider: - Running during off-peak hours - Implementing progress tracking - Adding error retry logic |