Source: https://lazyadmin.nl/powershell/msgraph-mfa-status/
Keeping track of your user’s MFA Status is important to keep your tenant protected. For now, we can still use the Msol module for this in PowerShell, but Microsoft is planning to retire this module. So I have recreated my successful MFA Status script with the Microsoft Graph module.
The Microsoft Graph module isn’t fully completed yet. It doesn’t only lack documentation, but we also can’t retrieve all information yet from it. For example, we can’t retrieve (or set) the default MFA method with Graph at the moment.
So this new MFA Status script can do pretty much the same things as the old script:
- List the MFA Status of all users
- List configured MFA types for each user
- Get all the users that don’t have MFA enabled
- Check the MFA status of a single user
- Checks if a user is admin or not
- Get only the licensed and enabled users
But with Graph, we are also able to retrieve a little bit more information than with the old module. So the following information is now also retrieved:
- Authenticator device name
- Check if Hello for business is registered
- Registered email address for Self Services Password Reset (SSPR)
What we currently can’t retrieve is the default MFA method, and if MFA is enforced for the users (as in that the user needs to configure MFA the next time after login).
As always, you will find the complete script at the end of the article.
Get MFA Status with Microsoft Graph and PowerShell
Microsoft Graph still has a lot of features only available in their beta release. So the first thing the script does is connect to Graph with the required scopes and switch over to the beta profile. We can then retrieve all users with the Get-MgUser cmdlet.

After we have collected all the users we can use the Get-MgUserAuthenticationMethod cmdlet to get all the MFA details.
Requirements
You will need to have the Microsoft Graph module installed. The script will check if the module is installed, if not you will be given the option to install it.
Getting all users and their MFA Status
The script comes with a couple of parameters that we can use to fine-tune the export results. But by default, it will get all licensed users, list the admins, and save the CSV Export at the same location as the script. The script will open the CSV file when completed.
So to get all users we can simply run the script:
# Get all licensed users: Get-MgMFAStatus.ps1
Get only users without MFA
When you have a large tenant you probably only want to see the users who don’t have MFA enabled. To do this you can add use the switch -withoutMFAOnly
:
Get-MgMFAStatus.ps1 -withOutMFAOnly
Check MFA Status of Admin only
The script will list all admins by default, but you can also check the MFA Status from admins only with the -adminsOnly
switch:
Get-MgMFAStatus.ps1 -adminsOnly
Check the status of a specific user or a selection of users
It’s also possible to check the MFA status of a specific user. We can specify the UserPrincipal name of the user using the -UserPrincipalName parameter:
Get-MgMFAStatus -UserPrincipalName '[email protected]'
The parameter accepts a string array, so you can comma separate the users that you want to retrieve:
Get-MgMFAStatus -UserPrincipalName '[email protected]','[email protected]'
Another option is to use the filter of the Get-MgUser cmdlet and then pipe the Get-MgMFAStatus script:
Get-MgUser -Filter "country eq 'Netherlands'" | ForEach-Object { Get-MgMFAStatus -UserPrincipalName $_.UserPrincipalName }
The Complete Script
The complete script can be downloaded from my Github repository, which I recommend using so you have always the latest version.
Tip
Quickly get the MFA Status of your users by adding a reference to the script in your PowerShell Profile. Read all about it in this article.
<# .Synopsis Get the MFA status for all users or a single user with Microsoft Graph .DESCRIPTION This script will get the Azure MFA Status for your users. You can query all the users, admins only or a single user. It will return the MFA Status, MFA type and registered devices. Note: Default MFA device is currently not supported https://docs.microsoft.com/en-us/graph/api/resources/authenticationmethods-overview?view=graph-rest-beta Hardwaretoken is not yet supported .NOTES Name: Get-MgMFAStatus Author: R. Mens - LazyAdmin.nl Version: 1.1 DateCreated: Jun 2022 Purpose/Change: Add Directory.Read.All scope .LINK https://lazyadmin.nl .EXAMPLE Get-MgMFAStatus Get the MFA Status of all enabled and licensed users and check if there are an admin or not .EXAMPLE Get-MgMFAStatus -UserPrincipalName '[email protected]','[email protected]' Get the MFA Status for the users John Doe and Jane Doe .EXAMPLE Get-MgMFAStatus -withOutMFAOnly Get only the licensed and enabled users that don't have MFA enabled .EXAMPLE Get-MgMFAStatus -adminsOnly Get the MFA Status of the admins only .EXAMPLE Get-MgUser -Filter "country eq 'Netherlands'" | ForEach-Object { Get-MgMFAStatus -UserPrincipalName $_.UserPrincipalName } Get the MFA status for all users in the Country The Netherlands. You can use a similar approach to run this for a department only. .EXAMPLE Get-MgMFAStatus -withOutMFAOnly| Export-CSV c:\temp\userwithoutmfa.csv -noTypeInformation Get all users without MFA and export them to a CSV file #> [CmdletBinding(DefaultParameterSetName="Default")] param( [Parameter( Mandatory = $false, ParameterSetName = "UserPrincipalName", HelpMessage = "Enter a single UserPrincipalName or a comma separted list of UserPrincipalNames", Position = 0 )] [string[]]$UserPrincipalName, [Parameter( Mandatory = $false, ValueFromPipeline = $false, ParameterSetName = "AdminsOnly" )] # Get only the users that are an admin [switch]$adminsOnly = $false, [Parameter( Mandatory = $false, ValueFromPipeline = $false, ParameterSetName = "Licensed" )] # Check only the MFA status of users that have license [switch]$IsLicensed = $true, [Parameter( Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "withOutMFAOnly" )] # Get only the users that don't have MFA enabled [switch]$withOutMFAOnly = $false, [Parameter( Mandatory = $false, ValueFromPipeline = $false )] # Check if a user is an admin. Set to $false to skip the check [switch]$listAdmins = $true, [Parameter( Mandatory = $false, HelpMessage = "Enter path to save the CSV file" )] [string]$path = ".\MFAStatus-$((Get-Date -format "MMM-dd-yyyy").ToString()).csv" ) Function ConnectTo-MgGraph { # Check if MS Graph module is installed if (-not(Get-InstalledModule Microsoft.Graph)) { Write-Host "Microsoft Graph module not found" -ForegroundColor Black -BackgroundColor Yellow $install = Read-Host "Do you want to install the Microsoft Graph Module?" if ($install -match "[yY]") { Install-Module Microsoft.Graph -Repository PSGallery -Scope CurrentUser -AllowClobber -Force }else{ Write-Host "Microsoft Graph module is required." -ForegroundColor Black -BackgroundColor Yellow exit } } # Connect to Graph Write-Host "Connecting to Microsoft Graph" -ForegroundColor Cyan Connect-MgGraph -Scopes "User.Read.All, UserAuthenticationMethod.Read.All, Directory.Read.All" # Select the beta profile Select-MgProfile Beta } Function Get-Admins{ <# .SYNOPSIS Get all user with an Admin role #> process{ $admins = Get-MgDirectoryRole | Select-Object DisplayName, Id | %{$role = $_.displayName; Get-MgDirectoryRoleMember -DirectoryRoleId $_.id | where {$_.AdditionalProperties."@odata.type" -eq "#microsoft.graph.user"} | % {Get-MgUser -userid $_.id | Where-Object {($_.AssignedLicenses).count -gt 0}} } | Select @{Name="Role"; Expression = {$role}}, DisplayName, UserPrincipalName, Mail, ObjectId | Sort-Object -Property Mail -Unique return $admins } } Function Get-Users { <# .SYNOPSIS Get users from the requested DN #> process{ # Set the properties to retrieve $select = @( 'id', 'DisplayName', 'userprincipalname', 'mail' ) $properties = $select + "AssignedLicenses" # Get enabled, disabled or both users switch ($enabled) { "true" {$filter = "AccountEnabled eq true and UserType eq 'member'"} "false" {$filter = "AccountEnabled eq false and UserType eq 'member'"} "both" {$filter = "UserType eq 'member'"} } # Check if UserPrincipalName(s) are given if ($UserPrincipalName) { Write-host "Get users by name" -ForegroundColor Cyan $users = @() foreach ($user in $UserPrincipalName) { try { $users += Get-MgUser -UserId $user -Property $properties | select $select -ErrorAction Stop } catch { [PSCustomObject]@{ DisplayName = " - Not found" UserPrincipalName = $User isAdmin = $null MFAEnabled = $null } } } }elseif($adminsOnly) { Write-host "Get admins only" -ForegroundColor Cyan $users = @() foreach ($admin in $admins) { $users += Get-MgUser -UserId $admin.UserPrincipalName -Property $properties | select $select } }else { if ($IsLicensed) { # Get only licensed users $users = Get-MgUser -Filter $filter -Property $properties -all | Where-Object {($_.AssignedLicenses).count -gt 0} | select $select }else{ $users = Get-MgUser -Filter $filter -Property $properties -all | select $select } } return $users } } Function Get-MFAMethods { <# .SYNOPSIS Get the MFA status of the user #> param( [Parameter(Mandatory = $true)] $userId ) process{ # Get MFA details for each user [array]$mfaData = Get-MgUserAuthenticationMethod -UserId $userId # Create MFA details object $mfaMethods = [PSCustomObject][Ordered]@{ status = "-" authApp = "-" phoneAuth = "-" fido = "-" helloForBusiness = "-" emailAuth = "-" tempPass = "-" passwordLess = "-" softwareAuth = "-" authDevice = "-" authPhoneNr = "-" SSPREmail = "-" } ForEach ($method in $mfaData) { Switch ($method.AdditionalProperties["@odata.type"]) { "#microsoft.graph.microsoftAuthenticatorAuthenticationMethod" { # Microsoft Authenticator App $mfaMethods.authApp = $true $mfaMethods.authDevice = $method.AdditionalProperties["displayName"] $mfaMethods.status = "enabled" } "#microsoft.graph.phoneAuthenticationMethod" { # Phone authentication $mfaMethods.phoneAuth = $true $mfaMethods.authPhoneNr = $method.AdditionalProperties["phoneType", "phoneNumber"] -join ' ' $mfaMethods.status = "enabled" } "#microsoft.graph.fido2AuthenticationMethod" { # FIDO2 key $mfaMethods.fido = $true $fifoDetails = $method.AdditionalProperties["model"] $mfaMethods.status = "enabled" } "#microsoft.graph.passwordAuthenticationMethod" { # Password # When only the password is set, then MFA is disabled. if ($mfaMethods.status -ne "enabled") {$mfaMethods.status = "disabled"} } "#microsoft.graph.windowsHelloForBusinessAuthenticationMethod" { # Windows Hello $mfaMethods.helloForBusiness = $true $helloForBusinessDetails = $method.AdditionalProperties["displayName"] $mfaMethods.status = "enabled" } "#microsoft.graph.emailAuthenticationMethod" { # Email Authentication $mfaMethods.emailAuth = $true $mfaMethods.SSPREmail = $method.AdditionalProperties["emailAddress"] $mfaMethods.status = "enabled" } "microsoft.graph.temporaryAccessPassAuthenticationMethod" { # Temporary Access pass $mfaMethods.tempPass = $true $tempPassDetails = $method.AdditionalProperties["lifetimeInMinutes"] $mfaMethods.status = "enabled" } "#microsoft.graph.passwordlessMicrosoftAuthenticatorAuthenticationMethod" { # Passwordless $mfaMethods.passwordLess = $true $passwordLessDetails = $method.AdditionalProperties["displayName"] $mfaMethods.status = "enabled" } "#microsoft.graph.softwareOathAuthenticationMethod" { # ThirdPartyAuthenticator $mfaMethods.softwareAuth = $true $mfaMethods.status = "enabled" } } } Return $mfaMethods } } Function Get-MFAStatusUsers { <# .SYNOPSIS Get all AD users #> process { Write-Host "Collecting users" -ForegroundColor Cyan # Collect users $users = Get-Users Write-Host "Processing" $users.count "users" -ForegroundColor Cyan # Collect and loop through all users $users | ForEach { $mfaMethods = Get-MFAMethods -userId $_.id if ($withOutMFAOnly) { if ($mfaMethods.status -eq "disabled") { [PSCustomObject]@{ "Name" = $_.DisplayName Emailaddress = $_.mail UserPrincipalName = $_.UserPrincipalName isAdmin = if ($listAdmins -and ($admins.UserPrincipalName -match $_.UserPrincipalName)) {$true} else {"-"} MFAEnabled = $false "Phone number" = $mfaMethods.authPhoneNr "Email for SSPR" = $mfaMethods.SSPREmail } } }else{ [pscustomobject]@{ "Name" = $_.DisplayName Emailaddress = $_.mail UserPrincipalName = $_.UserPrincipalName isAdmin = if ($listAdmins -and ($admins.UserPrincipalName -match $_.UserPrincipalName)) {$true} else {"-"} "MFA Status" = $mfaMethods.status # "MFA Default type" = "" - Not yet supported by MgGraph "Phone Authentication" = $mfaMethods.phoneAuth "Authenticator App" = $mfaMethods.authApp "Passwordless" = $mfaMethods.passwordLess "Hello for Business" = $mfaMethods.helloForBusiness "FIDO2 Security Key" = $mfaMethods.fido "Temporary Access Pass" = $mfaMethods.tempPass "Authenticator device" = $mfaMethods.authDevice "Phone number" = $mfaMethods.authPhoneNr "Email for SSPR" = $mfaMethods.SSPREmail } } } } } # Connect to Graph ConnectTo-MgGraph # Get Admins # Get all users with admin role $admins = $null if (($listAdmins) -or ($adminsOnly)) { $admins = Get-Admins } # Get MFA Status Get-MFAStatusUsers | Sort-Object Name | Export-CSV -Path $path -NoTypeInformation if ((Get-Item $path).Length -gt 0) { Write-Host "Report finished and saved in $path" -ForegroundColor Green # Open the CSV file Invoke-Item $path }else{ Write-Host "Failed to create report" -ForegroundColor Red }
Wrapping Up
Having MFA enabled really helps with protecting your tenant. This PowerShell script allows you to easily check the MFA status of your users.
Make sure you also check this article with 20 other security tips for Office 365. You can find the Msol version of this script here.
If you found this script useful, then please share it. If you have any questions then just drop a comment below.
You may also like one of the following PowerShell report scripts: