Enhance user MFA method reporting by clarifying sign-in preference handling and adding module dependency checks
This commit is contained in:
@@ -257,17 +257,24 @@ function Get-UserAuthMethodDetail {
|
|||||||
if ($friendly.Category -eq 'Phone') { $hasPhoneMethod = $true }
|
if ($friendly.Category -eq 'Phone') { $hasPhoneMethod = $true }
|
||||||
}
|
}
|
||||||
|
|
||||||
# Best-effort default/preferred method. This endpoint reflects the user's
|
# Best-effort default/preferred method. This endpoint only reflects an
|
||||||
# own sign-in preference and, unlike the tenant-wide usage/insights report,
|
# explicit override the user (or an admin) has set - unlike the
|
||||||
# is not gated behind a P1/P2 license - but treat failures as "unknown"
|
# tenant-wide usage/insights report, it's not gated behind a P1/P2
|
||||||
# rather than fatal, since behavior can vary by tenant/SDK version.
|
# 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
|
$defaultMethod = $null
|
||||||
try {
|
try {
|
||||||
$preference = Get-MgBetaUserAuthenticationSignInPreference -UserId $UserId -ErrorAction Stop
|
$preference = Get-MgBetaUserAuthenticationSignInPreference -UserId $UserId -ErrorAction Stop
|
||||||
|
if ($preference.UserPreferredMethodForSecondaryAuthentication -and $preference.UserPreferredMethodForSecondaryAuthentication -ne 'none') {
|
||||||
$defaultMethod = $preference.UserPreferredMethodForSecondaryAuthentication
|
$defaultMethod = $preference.UserPreferredMethodForSecondaryAuthentication
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch {
|
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]@{
|
return [PSCustomObject]@{
|
||||||
@@ -403,6 +410,25 @@ $($methodRows -join "`n")
|
|||||||
|
|
||||||
# --- Main --------------------------------------------------------------
|
# --- Main --------------------------------------------------------------
|
||||||
try {
|
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...'
|
Write-Verbose 'Connecting to Microsoft Graph...'
|
||||||
$requiredScopes = @(
|
$requiredScopes = @(
|
||||||
'User.Read.All'
|
'User.Read.All'
|
||||||
@@ -473,7 +499,13 @@ try {
|
|||||||
$_ -notin @('mobilePhone', 'alternateMobilePhone', 'officePhone', 'email', 'password')
|
$_ -notin @('mobilePhone', 'alternateMobilePhone', 'officePhone', 'email', 'password')
|
||||||
}
|
}
|
||||||
$hasStrongMethod = [bool]$strongRegistered
|
$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
|
$defaultMethod = $regDetail.UserPreferredMethodForSecondaryAuthentication
|
||||||
|
if ($defaultMethod -eq 'none') { $defaultMethod = $null }
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# Fallback path: per-user methods API, works without P1/P2.
|
# Fallback path: per-user methods API, works without P1/P2.
|
||||||
|
|||||||
Reference in New Issue
Block a user