Public/get-mfaaudit.ps1

# get-mfaaudit.ps1
# Lists ALL users and their MFA registration status.
# Flags accounts with no MFA registered — useful for compliance checks
# and cleaning up before enforcing Conditional Access policies.
# Requires: Graph (User.Read.All, UserAuthenticationMethod.Read.All)

if (-not (Get-MgContext)) {
    Connect-MgGraph -Scopes "User.Read.All", "UserAuthenticationMethod.Read.All" -ContextScope Process
}

Write-Host "Fetching users..."
$users = Get-MgUser -All -Property "DisplayName,UserPrincipalName,AccountEnabled" |
    Where-Object { $_.UserPrincipalName -notmatch "#EXT#" }

Write-Host "Checking MFA methods for $($users.Count) users (this takes a while)..."

$results = foreach ($user in $users) {
    $methods = Get-MgUserAuthenticationMethod -UserId $user.Id |
        Where-Object { $_.OdataType -ne "#microsoft.graph.passwordAuthenticationMethod" }

    $methodNames = ($methods | ForEach-Object {
        $_.OdataType -replace '#microsoft.graph.', '' -replace 'AuthenticationMethod', ''
    }) -join ", "

    [PSCustomObject]@{
        "Display Name" = $user.DisplayName
        "UPN"          = $user.UserPrincipalName
        "Enabled"      = $user.AccountEnabled
        "MFA Methods"  = if ($methodNames) { $methodNames } else { "NONE" }
        "MFA Status"   = if ($methods.Count -gt 0) { "Registered" } else { "Not Registered" }
    }
}

$noMfa = $results | Where-Object { $_."MFA Status" -eq "Not Registered" }
Write-Host "`n$($results.Count) total users | $($noMfa.Count) without MFA`n"

$results | Sort-Object "MFA Status", "Display Name" | Format-Table -AutoSize

$path = "$env:USERPROFILE\Desktop\MFAAudit_$(Get-Date -Format 'yyyyMMdd').csv"
$results | Export-Csv -Path $path -NoTypeInformation
Write-Host "Exported to $path"