From 698139884f608fb9903985c7245ef730c18ce339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0t=C4=9Bp=C3=A1n?= Date: Wed, 29 Jul 2026 14:27:01 +0200 Subject: [PATCH] Enhance MFA method reporting by adding new classification for default SMS/Voice methods and updating HTML output for clarity --- Get-UserMfaMethodReport.ps1 | 48 +++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/Get-UserMfaMethodReport.ps1 b/Get-UserMfaMethodReport.ps1 index 3c2f424..304a5a0 100644 --- a/Get-UserMfaMethodReport.ps1 +++ b/Get-UserMfaMethodReport.ps1 @@ -22,14 +22,18 @@ authentication method registrations - so it does not need -WhatIf/-Confirm. Row classification in the HTML/CSV output: - - RED ("SmsVoiceOnly"): the only MFA-capable method(s) registered + - RED ("SmsVoiceOnly"): the only MFA-capable method(s) registered are SMS and/or Voice call. These accounts will hit the blocking passkey prompt on Feb 2027 retirement with no fallback. + - RED ("SmsVoiceDefault"): the user's actual default/preferred + method is SMS or Voice, even though + other methods are also registered - the + direct risk this retirement is about. - YELLOW ("SmsVoiceRegistered"): other MFA methods are registered too, - but SMS or Voice is also registered and - still needs to be removed from the - account ahead of the retirement. + and SMS or Voice is also registered (but + not the default) - still needs to be + removed ahead of the retirement. - (none): no SMS/Voice exposure, or no MFA registered at all (flagged separately in the Notes column - a different problem, not this @@ -70,9 +74,16 @@ .NOTES Author: Petr Štěpán Created: 2026-07-27 - Version: 1.0.0 + Version: 1.0.1 Changelog: 1.0.0 - Initial version + 1.0.1 - Added tenant authentication methods policy overview (console + summary and HTML report), progress bar for per-user + processing, pre-flight Microsoft.Graph module version check + with an offer to auto-repair mismatched/missing modules, + and a new RED classification for accounts whose actual + default sign-in method is SMS/Voice (previously only + flagged yellow unless it was the user's only method). #> #Requires -Version 7.0 @@ -506,6 +517,7 @@ TABLE{border: 1px solid #969595; border-collapse: collapse; font-size: 9pt;} TH{border: 1px solid #969595; background: #dddddd; padding: 5px; color: #000000;} TD{border: 1px solid #969595; padding: 5px;} tr.SmsVoiceOnly{background: #f8d7da;} +tr.SmsVoiceDefault{background: #f8d7da;} tr.SmsVoiceRegistered{background: #fff3cd;} tr.MethodEnabledSmsVoice{background: #fff3cd;} tr.MethodEnabled{background: #d4edda;} @@ -528,7 +540,8 @@ tr.MethodDisabled{background: #e2e3e5; color: #6c6c6c;} $xml.table.Attributes.Append($tableClassAttr) | Out-Null # Walk the generated rows in lock-step with the report rows to apply - # the RowClass (SmsVoiceOnly / SmsVoiceRegistered / none) computed earlier. + # the RowClass (SmsVoiceOnly / SmsVoiceDefault / SmsVoiceRegistered / none) + # computed earlier. $rowIndex = 0 foreach ($tableRow in $xml.table.SelectNodes('tr')) { if ($tableRow.SelectNodes('th').Count -eq 0 -and $rowIndex -lt $Report.Count) { @@ -543,7 +556,8 @@ tr.MethodDisabled{background: #e2e3e5; color: #6c6c6c;} $htmlBody = [string]::Format('
{0}
', $xml.OuterXml) $countOnly = @($Report | Where-Object { $_.RowClass -eq 'SmsVoiceOnly' }).Count - $countDefault = @($Report | Where-Object { $_.RowClass -eq 'SmsVoiceRegistered' }).Count + $countDefault = @($Report | Where-Object { $_.RowClass -eq 'SmsVoiceDefault' }).Count + $countRegistered = @($Report | Where-Object { $_.RowClass -eq 'SmsVoiceRegistered' }).Count $methodPolicyHtml = '' if ($MethodPolicyStates.Count -gt 0) { @@ -567,7 +581,8 @@ $($methodRows -join "`n")

-----------------------------------------------------------------------

Total users reported: $($Report.Count)

Red - only MFA method is SMS/Voice: $countOnly


-

Yellow - SMS/Voice registered alongside other methods: $countDefault

+

Red - default method is SMS/Voice: $countDefault


+

Yellow - SMS/Voice registered, not default: $countRegistered

Reference: https://learn.microsoft.com/en-us/entra/identity/authentication/concept-sms-voice-retirement

"@ @@ -716,10 +731,21 @@ try { } } + # Only matches a verified value (from the Graph API preference read, + # or the tenant-wide report) or our own logical deduction above (not + # a guess) - 'Not reported' and $null correctly fail to match. + $defaultIsPhone = $defaultMethod -match '(?i)^(sms|voice)' + $rowClass = $null if ($hasPhoneMethod -and -not $hasStrongMethod) { $rowClass = 'SmsVoiceOnly' } + elseif ($defaultIsPhone) { + # The user's actual default/preferred method is SMS or Voice - + # this is the direct risk the retirement is about, regardless of + # what other methods are also registered. + $rowClass = 'SmsVoiceDefault' + } elseif ($hasStrongMethod -and $hasPhoneMethod) { # SMS/Voice is registered alongside other methods - not the user's # only option, but it still needs to be removed from their @@ -776,13 +802,15 @@ try { Export-AuthenticationHtmlReport -Report $report -Path $htmlPath -TenantDisplayName $tenantDisplayName -IsPremiumTenant $isPremium -MethodPolicyStates $methodPolicyStates $countOnly = @($report | Where-Object { $_.RowClass -eq 'SmsVoiceOnly' }).Count - $countDefault = @($report | Where-Object { $_.RowClass -eq 'SmsVoiceRegistered' }).Count + $countDefault = @($report | Where-Object { $_.RowClass -eq 'SmsVoiceDefault' }).Count + $countRegistered = @($report | Where-Object { $_.RowClass -eq 'SmsVoiceRegistered' }).Count Write-Host "`n===== SUMMARY =====" -ForegroundColor Magenta Write-Host "Tenant: $tenantDisplayName" Write-Host "Total users reported: $($report.Count)" Write-Host "Red (SMS/Voice only method): $countOnly" -ForegroundColor Red - Write-Host "Yellow (SMS/Voice registered alongside other methods): $countDefault" -ForegroundColor Yellow + Write-Host "Red (SMS/Voice is default method): $countDefault" -ForegroundColor Red + Write-Host "Yellow (SMS/Voice registered, not default): $countRegistered" -ForegroundColor Yellow Write-Host "CSV report: $csvPath" Write-Host "HTML report: $htmlPath"