Auditing MFA Registration Across Microsoft 365 with Microsoft Graph PowerShell
If you’re still running Get-MsolUser to check MFA status, that module is deprecated and no longer receiving updates, Microsoft has fully moved this reporting to the Microsoft Graph PowerShell SDK. Here is the current, correct method for exporting per-user MFA status across your Microsoft 365 tenant.
Why the old method stopped being the right answer
The MSOnline module’s Get-MsolUser exposed a simple StrongAuthenticationMethods property that many older scripts parsed for MFA status. That module is deprecated, and its underlying Azure AD Graph API is being retired, meaning scripts built on it will progressively break rather than just becoming “old but still working.” The current approach uses Get-MgUserAuthenticationMethod from the Microsoft Graph PowerShell SDK instead.
Setting up the required permissions
Install-Module Microsoft.Graph.Users -Scope CurrentUser
Install-Module Microsoft.Graph.Identity.SignIns -Scope CurrentUser
Connect-MgGraph -Scopes "UserAuthenticationMethod.Read.All", "AuditLog.Read.All", "User.Read.All"
The scopes matter here specifically: UserAuthenticationMethod.Read.All lets you read what authentication methods are registered, and AuditLog.Read.All is needed if you also want per-user sign-in and registration detail rather than just a yes/no on whether MFA is registered.
Pulling the report
$users = Get-MgUser -All -Property Id, DisplayName, UserPrincipalName
$report = foreach ($user in $users) {
$methods = Get-MgUserAuthenticationMethod -UserId $user.Id
$methodTypes = $methods | ForEach-Object { $_.AdditionalProperties["@odata.type"] -replace "#microsoft.graph.", "" }
[PSCustomObject]@{
DisplayName = $user.DisplayName
UserPrincipalName = $user.UserPrincipalName
MFARegistered = ($methodTypes | Where-Object { $_ -ne "passwordAuthenticationMethod" }).Count -gt 0
RegisteredMethods = ($methodTypes -join ", ")
}
}
$report | Export-Csv "MFAStatusReport.csv" -NoTypeInformation -Encoding UTF8
Every user has a passwordAuthenticationMethod entry by default, that’s just their password, not an MFA factor, which is why it’s explicitly excluded when determining whether real MFA is registered. The remaining method types (phoneAuthenticationMethod, microsoftAuthenticatorAuthenticationMethod, fido2AuthenticationMethod, and others) each represent an actual second factor.
Cross-referencing against your Conditional Access / Security Defaults enforcement
A user having a registered MFA method and a user actually being required to use it are two different questions. This report answers the first. To confirm enforcement, check which Conditional Access policies target each user (Get-MgIdentityConditionalAccessPolicy) or whether Security Defaults is enabled tenant-wide (Get-MgPolicyIdentitySecurityDefaultEnforcementPolicy), since a user can have MFA registered but still not be in scope of any policy that actually requires it.
Frequently asked questions
My old script used Get-MsolUser‘s StrongAuthenticationMethods property. Is there a direct one-line replacement?
No, and treating it as a drop-in replacement is the most common mistake in this migration. StrongAuthenticationMethods reflected the older per-user MFA model, while Get-MgUserAuthenticationMethod reflects the current, broader authentication methods model that also covers passwordless and FIDO2 methods. Rebuild the logic against the new data shape rather than trying to map properties one-to-one.
Does this report also work for accounts only secured by Conditional Access-enforced MFA, with no methods manually registered by the user yet?
If a user has never completed MFA registration, Get-MgUserAuthenticationMethod will correctly show no real methods registered, meaning they’d fail an MFA challenge if one were required, which is exactly the gap this report is meant to surface before it becomes an access problem for that user.
