Created
July 8, 2026 08:17
-
-
Save tijldeneut/2dea8476ddfe96a77e9f251536976b41 to your computer and use it in GitHub Desktop.
Script to find users on a EntraID Tenant without MFA and without activity, output is a CSV (delimited with ";")
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Get-MFAReport.ps1 | |
| .DESCRIPTION | |
| Export Microsoft 365 per-user MFA report with Micrososoft Graph PowerShell. | |
| Also list recent activity to find stale accounts which have no MFA enabled due to not having logged in recently | |
| .LINK | |
| www.alitajran.com/export-office-365-users-mfa-status-with-powershell/ | |
| .NOTES | |
| Written by: ALI TAJRAN | |
| Website: www.alitajran.com | |
| LinkedIn: linkedin.com/in/alitajran | |
| V3 by: Photubias | |
| .CHANGELOG | |
| V1.00, 04/04/2021 - Initial version | |
| V2.00, 08/04/2024 - Rewritten for Microsoft Graph PowerShell | |
| V3.00, 07/07/2026 - Added SignIn logs to detect stale users | |
| .USAGE (PowerShell or PowerShell Core or PWSH on Linux) | |
| Install-Module Microsoft.Graph | |
| Connect-MgGraph -Scopes "User.Read.all","UserAuthenticationMethod.Read.All","UserAuthenticationMethod.ReadWrite.All","AuditLog.Read.all" -UseDeviceAuthentication | |
| .\Get-MFAReport.ps1 | |
| #> | |
| if (-Not (Get-MgOrganization -ErrorAction SilentlyContinue)) { | |
| # Connect to Microsoft Graph with the required scopes | |
| Write-Host "[i] Connecting first" -ForegroundColor Cyan | |
| Connect-MgGraph -Scopes "User.Read.All", "Policy.ReadWrite.AuthenticationMethod", "UserAuthenticationMethod.Read.All" -NoWelcome | |
| } | |
| $Tenant = Get-MgOrganization | |
| Write-Host "[+] Connected to Tenant '$($Tenant.DisplayName)' ($($Tenant.Id))" -ForegroundColor Green | |
| # CSV export file path | |
| $CSVfile = "MFAUsers.csv" | |
| # Get properties | |
| $Properties = @( | |
| 'Id', | |
| 'DisplayName', | |
| 'UserPrincipalName', | |
| 'UserType', | |
| 'Mail', | |
| 'ProxyAddresses', | |
| 'AccountEnabled', | |
| 'CreatedDateTime' | |
| ) | |
| Write-Host "[!] ##### Warning: this script will take a long time (30min+ for 500 users) to generate '$CSVfile'" -ForegroundColor Cyan | |
| Read-Host -Prompt " Press any key to continue or Ctrl+C to quit" | Out-Null | |
| # Get all users | |
| [array]$Users = Get-MgUser -All -Property $Properties | Select-Object $Properties | |
| # Initialize the report list | |
| $Report = [System.Collections.Generic.List[Object]]::new() | |
| # Check if any users were retrieved | |
| if (-not $Users) { | |
| Write-Host "No users found. Exiting script." -ForegroundColor Red | |
| return | |
| } | |
| # Initialize progress counter | |
| $counter = 0 | |
| $totalUsers = $Users.Count | |
| # Loop through each user and get their MFA settings | |
| foreach ($User in $Users) { | |
| $counter++ | |
| $upn = $User.UserPrincipalName | |
| # Calculate percentage completion | |
| $percentComplete = [math]::Round(($counter / $totalUsers) * 100) | |
| # Define progress bar parameters with user principal name | |
| $progressParams = @{ | |
| Activity = "Processing Users" | |
| Status = "User $($counter) of $totalUsers - $($User.UserPrincipalName) - $percentComplete% Complete" | |
| PercentComplete = $percentComplete | |
| } | |
| Write-Progress @progressParams | |
| # Get MFA settings | |
| $MFAStateUri = "https://graph.microsoft.com/beta/users/$($User.Id)/authentication/requirements" | |
| $Data = Invoke-MgGraphRequest -Uri $MFAStateUri -Method GET | |
| # Get the default MFA method | |
| $DefaultMFAUri = "https://graph.microsoft.com/beta/users/$($User.Id)/authentication/signInPreferences" | |
| $DefaultMFAMethod = Invoke-MgGraphRequest -Uri $DefaultMFAUri -Method GET | |
| # Determine the MFA default method | |
| if ($DefaultMFAMethod.userPreferredMethodForSecondaryAuthentication) { | |
| $MFAMethod = $DefaultMFAMethod.userPreferredMethodForSecondaryAuthentication | |
| Switch ($MFAMethod) { | |
| "push" { $MFAMethod = "Microsoft authenticator app" } | |
| "oath" { $MFAMethod = "Authenticator app or hardware token" } | |
| "voiceMobile" { $MFAMethod = "Mobile phone" } | |
| "voiceAlternateMobile" { $MFAMethod = "Alternate mobile phone" } | |
| "voiceOffice" { $MFAMethod = "Office phone" } | |
| "sms" { $MFAMethod = "SMS" } | |
| Default { $MFAMethod = "Unknown method" } | |
| } | |
| } | |
| else { | |
| $MFAMethod = "Not Enabled" | |
| } | |
| # Filter only the aliases | |
| $Aliases = ($User.ProxyAddresses | Where-Object { $_ -clike "smtp*" } | ForEach-Object { $_ -replace "smtp:", "" }) -join ', ' | |
| # Get most recent Sign In activity, can be empty if Guest/Member user | |
| $SignInProperties = @('AppDisplayName','IPAddress','ResourceDisplayName','CreatedDateTime') | |
| $UserSignIn = Get-MGAuditLogSignIn -Filter "userPrincipalName eq '$upn'" -Top 1 -Property $SignInProperties | Select-Object $SignInProperties | |
| if ( $UserSignIn ) { $LastSignInApp = $UserSignIn.AppDisplayName + " ($($UserSignIn.IPAddress))" } | |
| else { $LastSignInApp = "" } | |
| # If Sign In is empty (e.g. Guest or Member User or due to very recent actions), there should be an audit trail | |
| $AuditProperties = @('ActivityDisplayName','Result','Category','ActivityDateTime') | |
| $UserAudit = Get-MgAuditLogDirectoryAudit -Filter "targetResources/any(t:t/userPrincipalName eq '$upn')" -Top 1 -Property $AuditProperties | Select-Object $AuditProperties | |
| # Calculate "Please Disable Or Not" Risk Column (MFA is "Not Enabled", User Enabled and no recent SignIns or Audit Logs) | |
| if ( $User.AccountEnabled -And $MFAMethod -eq "Not Enabled" -And -Not $UserSignIn -And -Not $UserAudit ) { | |
| $UserRisk = "HIGHRISK" | |
| } else {$UserRisk = "OK"} | |
| # Create a report line for each user | |
| $ReportLine = [PSCustomObject][ordered]@{ | |
| UserPrincipalName = $User.UserPrincipalName | |
| DisplayName = $User.DisplayName | |
| MFAState = $Data.PerUserMfaState | |
| MFADefaultMethod = $MFAMethod | |
| PrimarySMTP = $User.Mail | |
| Aliases = $Aliases | |
| UserType = $User.UserType | |
| AccountEnabled = $User.AccountEnabled | |
| CreatedDateTime = $User.CreatedDateTime | |
| LastSignIn = $UserSignIn.CreatedDateTime | |
| LastSignInApp = $LastSignInApp | |
| LastAudit = $UserAudit.ActivityDateTime | |
| LastAuditAction = $LastAudit.ActivityDisplayName | |
| UserAtRisk = $UserRisk | |
| } | |
| $Report.Add($ReportLine) | |
| ## Uncomment this to stop after x users | |
| #if ($counter -eq 10){break} | |
| } | |
| # Complete the progress bar | |
| Write-Progress -Activity "Processing Users" -Completed | |
| # Display the report in a grid view, only on Windows | |
| if (-Not ($PSVersionTable.Platform -And $PSVersionTable -Ne "Linux")){ | |
| $Report | Out-GridView -Title "Microsoft 365 per-user MFA Report" | |
| } | |
| # Export the report to a CSV file | |
| $Report | Export-Csv -Delimiter ';' -Path $CSVfile -NoTypeInformation -Encoding utf8 | |
| Write-Host "Microsoft 365 per-user MFA Report is in $CSVfile" -ForegroundColor Cyan |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment