PowerShell Microsoft Graph: Audit MFA Status for All Users

PowerShell Microsoft Graph: Audit MFA Status for All Users

PowerShell Tips Editor 4 min read
PowerShell Microsoft Graph: Audit MFA Status for All Users

MSOnline Is Deprecated — Use Graph Instead

For years, Get-MsolUser from the MSOnline module was the go-to for checking MFA status. Microsoft deprecated that module, and its MFA properties were always limited — they reflected legacy per-user MFA settings, not Conditional Access policies or modern authentication methods like FIDO2 keys and Authenticator app. The Microsoft Graph authentication methods API is the correct modern approach: it returns every registered method for every user, works with Entra ID’s full method catalog, and supports the same scripting patterns as the rest of the Graph SDK.

Quick Answer

Connect with Connect-MgGraph -Scopes UserAuthenticationMethod.Read.All,User.Read.All, page through users with Get-MgUser, retrieve each user’s authentication methods with Get-MgUserAuthenticationMethod, and flag accounts where the only method is PasswordAuthenticationMethod.

Required Graph Permissions: UserAuthenticationMethod.Read.All

The authentication methods API requires the UserAuthenticationMethod.Read.All permission, plus User.Read.All to enumerate all users. For unattended scripts, register an app registration in Entra ID with these application permissions and connect with a client certificate or client secret.

# Interactive sign-in (delegate permissions)
Connect-MgGraph -Scopes 'UserAuthenticationMethod.Read.All', 'User.Read.All'

# Verify connected context
$ctx = Get-MgContext
Write-Host "Connected as: $($ctx.Account)"
Write-Host "Scopes      : $($ctx.Scopes -join ', ')"
Connected as: [email protected]
Scopes      : UserAuthenticationMethod.Read.All, User.Read.All

Getting All Users with Get-MgUser and Paging

Large tenants require paging through the user list. The Graph SDK handles pagination automatically when you use -All on Get-MgUser. For very large tenants, consider filtering to licensed users only with -Filter to reduce API call volume.

# Get all licensed users (excludes guest accounts without licenses)
$users = Get-MgUser -All `
    -Filter "accountEnabled eq true" `
    -Property Id, DisplayName, UserPrincipalName, AccountEnabled `
    -ErrorAction Stop

Write-Host "Retrieved $($users.Count) enabled users"

Retrieving Authentication Methods with Get-MgUserAuthenticationMethod

For each user, retrieve their registered authentication methods. The AdditionalProperties hashtable on each method object contains the @odata.type property that identifies the method type. Extract this to classify each user’s MFA posture.

function Get-UserMfaMethods {
    param([string]$UserId)

    try {
        $methods = Get-MgUserAuthenticationMethod -UserId $UserId -ErrorAction Stop
        $methods | ForEach-Object {
            $_.AdditionalProperties['@odata.type'] -replace '#microsoft.graph.', ''
        }
    }
    catch {
        Write-Warning "Could not retrieve methods for user $UserId`: $_"
        @('retrievalError')
    }
}

# Test against a single user first
Get-UserMfaMethods -UserId $users[0].Id
passwordAuthenticationMethod
microsoftAuthenticatorAuthenticationMethod
phoneAuthenticationMethod

Categorizing Methods: FIDO2, Authenticator App, Phone

Map the raw method type strings to human-readable categories. A user with only passwordAuthenticationMethod has no MFA registered at all — they are the accounts your security team cares most about.

$methodDisplayNames = @{
    'passwordAuthenticationMethod'                  = 'Password'
    'microsoftAuthenticatorAuthenticationMethod'    = 'Authenticator App'
    'phoneAuthenticationMethod'                     = 'Phone (SMS/Call)'
    'fido2AuthenticationMethod'                     = 'FIDO2 Key'
    'windowsHelloForBusinessAuthenticationMethod'   = 'Windows Hello'
    'softwareOathAuthenticationMethod'              = 'OATH Token'
    'temporaryAccessPassAuthenticationMethod'       = 'Temp Access Pass'
    'emailAuthenticationMethod'                     = 'Email OTP'
}

Identifying Users with Only Password Authentication

Process all users in a loop, collect method data, and classify each user. For large tenants, implement retry logic with exponential backoff when Graph returns HTTP 429 (too many requests).

$mfaReport = [System.Collections.Generic.List[PSCustomObject]]::new()

foreach ($user in $users) {
    $rawMethods    = Get-UserMfaMethods -UserId $user.Id
    $displayMethods= $rawMethods | ForEach-Object { $methodDisplayNames[$_] ?? $_ }
    $hasMfa        = $rawMethods | Where-Object { $_ -ne 'passwordAuthenticationMethod' -and $_ -ne 'retrievalError' }

    $mfaReport.Add([PSCustomObject]@{
        DisplayName      = $user.DisplayName
        UPN              = $user.UserPrincipalName
        MfaEnabled       = [bool]$hasMfa
        MethodCount      = $rawMethods.Count
        Methods          = $displayMethods -join '; '
        MfaRisk          = if (-not $hasMfa) { 'No MFA' } else { 'MFA Registered' }
    })

    # Basic rate-limit courtesy — Graph throttles at scale
    Start-Sleep -Milliseconds 50
}

$noMfa = $mfaReport | Where-Object { $_.MfaRisk -eq 'No MFA' }
Write-Host "Users without MFA: $($noMfa.Count) of $($mfaReport.Count)"

Exporting MFA Status Report to CSV

Export the full report and a filtered no-MFA list. The full report is useful for compliance evidence; the no-MFA list goes directly to the team responsible for remediation.

$mfaReport | Export-Csv -Path 'C:\Reports\MFA-Status-All.csv' -NoTypeInformation
$noMfa     | Export-Csv -Path 'C:\Reports\MFA-Status-NoMFA.csv' -NoTypeInformation

Write-Host "Reports written:"
Write-Host "  All users : C:\Reports\MFA-Status-All.csv"
Write-Host "  No MFA    : C:\Reports\MFA-Status-NoMFA.csv"

Common Errors

  • Authentication method endpoint requires the beta API profile for some method types. Certain method types — notably temporaryAccessPassAuthenticationMethod and some FIDO2 properties — are only fully exposed through the Graph beta endpoint. Switch the SDK to beta profile with Select-MgProfile -Name beta before the retrieval loop if you need full method data.
  • Rate limiting at scale — implement retry with backoff for large tenants. Graph throttles authentication method queries aggressively when called for thousands of users in rapid succession. Wrap your Get-MgUserAuthenticationMethod call in a retry loop that catches HTTP 429 responses, reads the Retry-After header, and sleeps accordingly before retrying.

Related Cmdlets / See Also

Wrapping Up

The Graph SDK’s authentication methods API provides accurate, per-user MFA registration data that the deprecated MSOnline module never delivered. Paginate through all users, classify method types, flag password-only accounts, and export the results — and you have a compliance-ready MFA audit in a single script run.

Send-Item -To