Enhance user MFA method reporting by clarifying sign-in preference handling and adding module dependency checks

This commit is contained in:
Petr Štěpán
2026-07-27 16:53:43 +02:00
parent 6b1595c168
commit 4c49f49f11
+37 -5
View File
@@ -257,17 +257,24 @@ function Get-UserAuthMethodDetail {
if ($friendly.Category -eq 'Phone') { $hasPhoneMethod = $true }
}
# Best-effort default/preferred method. This endpoint reflects the user's
# own sign-in preference and, unlike the tenant-wide usage/insights report,
# is not gated behind a P1/P2 license - but treat failures as "unknown"
# rather than fatal, since behavior can vary by tenant/SDK version.
# Best-effort default/preferred method. This endpoint only reflects an
# explicit override the user (or an admin) has set - unlike the
# tenant-wide usage/insights report, it's not gated behind a P1/P2
# license - but treat failures as "unknown" rather than fatal, since
# behavior can vary by tenant/SDK version. Deliberately NOT guessed/
# inferred when unavailable: Entra ID's actual method selection (e.g.
# under System-preferred MFA) depends on per-user state this API doesn't
# expose, so a guess here can be flatly wrong and is worse than admitting
# it's unknown - this value drives real remediation decisions.
$defaultMethod = $null
try {
$preference = Get-MgBetaUserAuthenticationSignInPreference -UserId $UserId -ErrorAction Stop
if ($preference.UserPreferredMethodForSecondaryAuthentication -and $preference.UserPreferredMethodForSecondaryAuthentication -ne 'none') {
$defaultMethod = $preference.UserPreferredMethodForSecondaryAuthentication
}
}
catch {
Write-Verbose "No sign-in preference available for user '$UserId' (falling back to heuristic): $_"
Write-Warning "No sign-in preference available for user '$UserId': $_"
}
return [PSCustomObject]@{
@@ -403,6 +410,25 @@ $($methodRows -join "`n")
# --- Main --------------------------------------------------------------
try {
# Fail fast and loudly if a module this script depends on isn't
# installed OR can't actually be loaded (e.g. an assembly-version
# conflict with another installed Microsoft.Graph module version),
# rather than letting every per-user call into it fail silently later
# and produce an incomplete/misleading report. Microsoft.Graph.Beta.
# Identity.SignIns provides Get-MgBetaUserAuthenticationSignInPreference,
# which reads each user's actual default sign-in method - there is no
# v1.0/GA equivalent, this data is beta-only in Microsoft Graph.
$requiredBetaModule = 'Microsoft.Graph.Beta.Identity.SignIns'
if (-not (Get-Module -ListAvailable -Name $requiredBetaModule)) {
throw "Required module '$requiredBetaModule' is not installed. It's needed to read each user's actual default sign-in method. Install it with: Install-Module $requiredBetaModule -Scope CurrentUser"
}
try {
Import-Module -Name $requiredBetaModule -ErrorAction Stop
}
catch {
throw "Required module '$requiredBetaModule' is installed but failed to load - this is usually caused by multiple mismatched Microsoft.Graph module versions installed side by side (an assembly-version conflict), not a missing module. Run: Get-Module -ListAvailable 'Microsoft.Graph*' | Group-Object Name | Where-Object { (`$_.Group.Version | Sort-Object -Unique).Count -gt 1 } to find the mismatched ones, update them all to the same version, and remove the old ones. Underlying error: $_"
}
Write-Verbose 'Connecting to Microsoft Graph...'
$requiredScopes = @(
'User.Read.All'
@@ -473,7 +499,13 @@ try {
$_ -notin @('mobilePhone', 'alternateMobilePhone', 'officePhone', 'email', 'password')
}
$hasStrongMethod = [bool]$strongRegistered
# This report field is also "none" (a literal string, not $null)
# for users without an explicit override - most commonly because
# the tenant runs System-preferred MFA. Normalize it so the
# fallback heuristic below actually kicks in instead of the row
# showing the literal word "none" as if it were a real method.
$defaultMethod = $regDetail.UserPreferredMethodForSecondaryAuthentication
if ($defaultMethod -eq 'none') { $defaultMethod = $null }
}
else {
# Fallback path: per-user methods API, works without P1/P2.