Skip to content
  • There are no suggestions because the search field is empty.

Microsoft 365 MFA CHECK

A quick PowerShell Script to interrogate MS365/EntraID and show all sign-in enabled entities, and whether they have MFA setup.

The below script, will run, install any required modules, and then ask you to authenticate into Microsoft Office 365 (using the normal 365 authentication GUI), after which, assuming you have administrative access to MS365, it will identify all active sign-in enabled entities, and provide information as to whether they are MFA protected, or just have an authentication method for Self-Service-Password-Reset (SSPR).

NOTE: If you have SSPR setup and only email addresses listed, this will be taken as SSPR use and not MFA, and therefore, you will see that MFA is not enabled.

Your data will be shown within the PowerShell window, but also in an exploded window with some filtering options, such as the below.

Download the code below, save it as ms365-mfa-check.ps1, and then run it (subject to your security settings) via .\ms365-mfa-check.ps1 within that location when running an administrative PowerShell session.

As always, we recommend checking the scripts yourselves and ensuring you are happy with these before use. Cyber Tec Security accepts no responsibility and provides no warranty for the code included within the scripts provided below:

<#
=========================================================================================================
MFA Configured Report (Absolute registration evidence)
- Interactive GUI login (standard Microsoft sign-in window)
- Uses Microsoft Graph "userRegistrationDetails" report
- Shows:
    * MFAConfigured (IsMfaRegistered)
    * MFAMethodsRegistered (methods that can satisfy MFA)
    * SSPRMethodsRegistered (recovery/SSPR methods like email)
    * AllMethodsRegistered (raw combined list)
- Includes Members + Guests (as returned by the report)
- Notes:
    * This is a REPORTING dataset (can lag). For live truth for one user, use the helper at bottom.
T * Script is provided "as is" and whilst tested, Cyber Tec Security provides no warranty for the code
=========================================================================================================
#>

Clear-Host
$ErrorActionPreference = "Stop"

Write-Host "Starting MFA Configured Report..." -ForegroundColor Cyan
Write-Host ""

# --- Ensure NuGet provider ---
if (-not (Get-PackageProvider -Name NuGet -ErrorAction SilentlyContinue)) {
    Write-Host "Installing NuGet provider..." -ForegroundColor Yellow
    Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Scope CurrentUser -Force | Out-Null
}

# --- Ensure Microsoft Graph PowerShell is installed (only if missing) ---
$needInstall = -not (Get-Module -ListAvailable -Name Microsoft.Graph.Authentication)
if ($needInstall) {
    Write-Host "Microsoft Graph PowerShell not found. Installing Microsoft.Graph..." -ForegroundColor Yellow
    Install-Module Microsoft.Graph -Scope CurrentUser -AllowClobber -Force
}

# --- Import only what we need ---
Import-Module Microsoft.Graph.Authentication -ErrorAction Stop
Import-Module Microsoft.Graph.Reports -ErrorAction Stop

# --- Connect (GUI login) ---
Write-Host "Connecting to Microsoft Graph (GUI sign-in will appear)..." -ForegroundColor Cyan
Connect-MgGraph -Scopes "AuditLog.Read.All","User.Read.All" | Out-Null

# Use beta profile for best coverage of registration details in some tenants
try {
    Select-MgProfile -Name "beta"
} catch {
    # If profile switching isn't available, continue anyway
}

Write-Host ""
Write-Host "Pulling user registration details (this can take a little while in large tenants)..." -ForegroundColor Cyan

# --- Pull registration report ---
$details = Get-MgReportAuthenticationMethodUserRegistrationDetail -All

# --- Helpers ---
function Convert-ToStringArray {
    param(
        [Parameter(Mandatory=$false)]
        $Value
    )

    if ($null -eq $Value) { return @() }

    # Sometimes it arrives as an array, sometimes as a single string, occasionally as something else
    if ($Value -is [System.Array]) { return @($Value) }

    if ($Value -is [string]) {
        $s = $Value.Trim()
        if ([string]::IsNullOrWhiteSpace($s)) { return @() }

        # If it looks comma-separated, split it; otherwise treat as single token
        if ($s -like "*,*") {
            return @($s.Split(",") | ForEach-Object { $_.Trim() } | Where-Object { $_ })
        } else {
            return @($s)
        }
    }

    # Fallback: stringify and attempt split
    $asString = ($Value | Out-String).Trim()
    if ([string]::IsNullOrWhiteSpace($asString)) { return @() }
    if ($asString -like "*,*") {
        return @($asString.Split(",") | ForEach-Object { $_.Trim() } | Where-Object { $_ })
    }
    return @($asString)
}

# SSPR/recovery-ish methods we want to separate (these do NOT mean MFA configured)
# Note: the report commonly uses "email" (SSPR). Keep the list conservative.
$ssprLike = @(
    "email",
    "alternateEmail",
    "alternateMobilePhone"
)

# --- Build report ---
$report = $details | ForEach-Object {

    $methods = Convert-ToStringArray -Value $_.MethodsRegistered

    $ssprMethods = @($methods | Where-Object { $_ -in $ssprLike })
    $mfaMethods  = @($methods | Where-Object { $_ -notin $ssprLike })

    [pscustomobject]@{
        DisplayName           = $_.UserDisplayName
        UserPrincipalName     = $_.UserPrincipalName
        UserType              = $_.UserType

        # Graph report flag: user has registered MFA methods (best high-level indicator)
        MFAConfigured         = [bool]$_.IsMfaRegistered

        # Split for clarity
        MFAMethodsRegistered  = ($mfaMethods -join ", ")
        SSPRMethodsRegistered = ($ssprMethods -join ", ")

        # Raw combined list (useful for troubleshooting)
        AllMethodsRegistered  = ($methods -join ", ")

        # Extra context
        IsMfaCapable          = [bool]$_.IsMfaCapable
        IsSsprRegistered      = [bool]$_.IsSsprRegistered
        IsPasswordlessCapable = [bool]$_.IsPasswordlessCapable
        LastUpdated           = $_.LastUpdatedDateTime
    }
} | Sort-Object UserType, DisplayName

Write-Host ""
Write-Host "===== MFA CONFIGURED REPORT (Registration Evidence) =====" -ForegroundColor Green

# Console view
$report |
    Select-Object DisplayName,
          UserPrincipalName,
          UserType,
          MFAConfigured,
          MFAMethodsRegistered,
          SSPRMethodsRegistered, 
          AllMethodsRegistered,
          IsMfaCapable,
          IsSsprRegistered,
          LastUpdated |
    Format-Table -AutoSize

# Optional: Grid view
try {
    $report |
        Select-Object DisplayName,
              UserPrincipalName,
              UserType,
              MFAConfigured,
              MFAMethodsRegistered,
                      SSPRMethodsRegistered,
                      AllMethodsRegistered,
              IsMfaCapable,
              IsSsprRegistered,
              LastUpdated |
        Out-GridView -Title "MFA Configured Report (Registered Methods)"
} catch {}

# Optional: Export
# $report | Export-Csv ".\MFA_Configured_Report.csv" -NoTypeInformation -Encoding UTF8

Write-Host ""
Write-Host "Done." -ForegroundColor Cyan

# --- Optional: Live (non-report) check for ONE user (uncomment to use) ---
# This is definitive and updates immediately, but is slower at scale.
# Requires extra scope: UserAuthenticationMethod.Read.All
#
# Disconnect-MgGraph
# Connect-MgGraph -Scopes "User.Read.All","UserAuthenticationMethod.Read.All" | Out-Null
# $userUpn = "user@domain.com"
# Get-MgUserAuthenticationMethod -UserId $userUpn -All |
#   Select-Object @{n="MethodType";e={$_.AdditionalProperties.'@odata.type'}}, Id |
#   Format-Table -AutoSize