Scripts/Get-MFAStatus.ps1
function Get-MFA { <# .SYNOPSIS Retrieves the MFA status for all users. Script inspired by: https://activedirectorypro.com/mfa-status-powershell/ .DESCRIPTION Retrieves the MFA status for all users. .PARAMETER OutputDir OutputDir is the parameter specifying the output directory. Default: Output\MFA .PARAMETER Encoding Encoding is the parameter specifying the encoding of the CSV output file. Default: UTF8 .PARAMETER UserIds UserIds is the UserIds parameter filtering the log entries by the account of the user who performed the actions. .PARAMETER LogLevel Specifies the level of logging: None: No logging Minimal: Critical errors only Standard: Normal operational logging Debug: Verbose logging for debugging purposes Default: Standard .PARAMETER IncludePhoneNumbers When this switch is set, the script will collect and include phone numbers used for MFA in the output. Default: False .EXAMPLE Get-MFA Retrieves the MFA status for all users. .EXAMPLE Get-MFA Retrieves the MFA status for all users. .EXAMPLE Get-MFA -Encoding utf32 Retrieves the MFA status for all users and exports the output to a CSV file with UTF-32 encoding. .EXAMPLE Get-MFA -OutputDir C:\Windows\Temp Retrieves the MFA status for all users and saves the output to the C:\Windows\Temp folder. .EXAMPLE Get-MFA -IncludePhoneNumbers Retrieves the MFA status for all users including their phone numbers used for MFA. #> [CmdletBinding()] param( [string]$OutputDir = "Output\MFA", [string]$Encoding = "UTF8", [string[]]$UserIds, [ValidateSet('None', 'Minimal', 'Standard', 'Debug')] [string]$LogLevel = 'Standard', [switch]$IncludePhoneNumbers ) Set-LogLevel -Level ([LogLevel]::$LogLevel) $isDebugEnabled = $script:LogLevel -eq [LogLevel]::Debug if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] PowerShell Version: $($PSVersionTable.PSVersion)" -Level Debug Write-LogFile -Message "[DEBUG] Input parameters:" -Level Debug Write-LogFile -Message "[DEBUG] OutputDir: '$OutputDir'" -Level Debug Write-LogFile -Message "[DEBUG] Encoding: '$Encoding'" -Level Debug Write-LogFile -Message "[DEBUG] UserIds: '$($UserIds -join ', ')'" -Level Debug Write-LogFile -Message "[DEBUG] UserIds count: $($UserIds.Count)" -Level Debug Write-LogFile -Message "[DEBUG] LogLevel: '$LogLevel'" -Level Debug $graphModules = Get-Module -Name Microsoft.Graph* -ErrorAction SilentlyContinue if ($graphModules) { Write-LogFile -Message "[DEBUG] Microsoft Graph Modules loaded:" -Level Debug foreach ($module in $graphModules) { Write-LogFile -Message "[DEBUG] - $($module.Name) v$($module.Version)" -Level Debug } } else { Write-LogFile -Message "[DEBUG] No Microsoft Graph modules loaded" -Level Debug } } Write-LogFile -Message "=== Starting MFA Status Collection ===" -Color "Cyan" -Level Standard $requiredScopes = @("UserAuthenticationMethod.Read.All","User.Read.All") $graphAuth = Get-GraphAuthType -RequiredScopes $RequiredScopes if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Graph authentication details:" -Level Debug Write-LogFile -Message "[DEBUG] Required scopes: $($requiredScopes -join ', ')" -Level Debug Write-LogFile -Message "[DEBUG] Authentication type: $($graphAuth.AuthType)" -Level Debug Write-LogFile -Message "[DEBUG] Current scopes: $($graphAuth.Scopes -join ', ')" -Level Debug if ($graphAuth.MissingScopes.Count -gt 0) { Write-LogFile -Message "[DEBUG] Missing scopes: $($graphAuth.MissingScopes -join ', ')" -Level Debug } else { Write-LogFile -Message "[DEBUG] Missing scopes: None" -Level Debug } } $summary = @{ TotalUsers = 0 MFAEnabled = 0 MFADisabled = 0 MethodCounts = @{ Email = 0 Fido2 = 0 App = 0 Phone = 0 SoftwareOath = 0 HelloBusiness = 0 TemporaryAccessPass = 0 CertificateBasedAuth = 0 } StartTime = Get-Date ProcessingTime = $null } if (!(test-path $OutputDir)) { New-Item -ItemType Directory -Force -Path $OutputDir > $null } else { if (!(Test-Path -Path $OutputDir)) { Write-Error "[Error] Custom directory invalid: $OutputDir exiting script" -ErrorAction Stop Write-LogFile -Message "[Error] Custom directory invalid: $OutputDir" -Level Minimal } } Write-LogFile -Message "[INFO] Identifying authentication methods..." -Level Standard $results = @() $allUsers = @() $userPhoneMethodsCache = @{} $phoneNumberMap = @{} $phoneResults = @() if ($UserIds) { if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Processing scenario: Specific users" -Level Debug Write-LogFile -Message "[DEBUG] Users to process: $($UserIds -join ', ')" -Level Debug } Write-LogFile -Message "[INFO] Processing specific users..." -Level Standard foreach ($userId in $UserIds) { $userUri = "https://graph.microsoft.com/v1.0/users/$userId" try { $user = Invoke-MgGraphRequest -Uri $userUri -Method Get -OutputType PSObject $allUsers += $user if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Successfully retrieved user: $($user.userPrincipalName)" -Level Debug } } catch { Write-LogFile -Message "[WARNING] User with ID $userId not found" -Color "Yellow" -Level Minimal if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Error details for user $userId : $($_.Exception.Message)" -Level Debug } } } } else { Write-LogFile -Message "[INFO] Processing all users..." -Level Standard $nextLink = "https://graph.microsoft.com/v1.0/users" do { $response = Invoke-MgGraphRequest -Uri $nextLink -Method Get -OutputType PSObject $allUsers += $response.value $nextLink = $response.'@odata.nextLink' } while ($nextLink) } $summary.TotalUsers = $allUsers.Count Write-LogFile -Message "[INFO] Found $($summary.TotalUsers) users to process" -Level Standard if ($IncludePhoneNumbers) { Write-LogFile -Message "[INFO] Collecting phone numbers for MFA..." -Level Standard foreach ($user in $allUsers) { $userPrinc = $user.userPrincipalName try { $phoneMethods = Get-MgUserAuthenticationPhoneMethod -UserId $userPrinc -ErrorAction SilentlyContinue $userPhoneMethodsCache[$userPrinc] = $phoneMethods if ($phoneMethods -and $phoneMethods.Count -gt 0) { $summary.PhoneNumberUsers++ foreach ($phoneMethod in $phoneMethods) { if (-not [string]::IsNullOrEmpty($phoneMethod.PhoneNumber)) { if (-not $phoneNumberMap.ContainsKey($phoneMethod.PhoneNumber)) { $phoneNumberMap[$phoneMethod.PhoneNumber] = @() } $phoneNumberMap[$phoneMethod.PhoneNumber] += $userPrinc } } } } catch { Write-LogFile -Message "[ERROR] Error retrieving phone methods for user $userPrinc : $_" -Level Minimal -Color "Red" $userPhoneMethodsCache[$userPrinc] = @() } } } foreach ($user in $allUsers) { $userPrinc = $user.userPrincipalName if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Processing user: $userPrinc" -Level Debug } $myObject = [PSCustomObject]@{ user = $userPrinc MFAstatus = "Disabled" email = $false fido2 = $false app = $false password = $false phone = $false softwareoath = $false hellobusiness = $false temporaryAccessPass = $false certificateBasedAuthConfiguration = $false } try { $contentUri = "https://graph.microsoft.com/v1.0/users/$($user.id)/authentication/methods" $MFAData = Invoke-MgGraphRequest -Uri $contentUri -Method Get -OutputType PSObject if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Fetching auth methods from: $contentUri" -Level Debug } if ($MFAData -and $MFAData.value) { if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Found $($MFAData.value.Count) authentication methods" -Level Debug } ForEach ($method in $MFAData.value) { $odataType = $method.'@odata.type' if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Processing method: $odataType" -Level Debug } Switch ($odataType) { "#microsoft.graph.emailAuthenticationMethod" { $myObject.email = $true $myObject.MFAstatus = "Enabled" $summary.MethodCounts.Email++ } "#microsoft.graph.fido2AuthenticationMethod" { $myObject.fido2 = $true $myObject.MFAstatus = "Enabled" $summary.MethodCounts.Fido2++ } "#microsoft.graph.microsoftAuthenticatorAuthenticationMethod" { $myObject.app = $true $myObject.MFAstatus = "Enabled" $summary.MethodCounts.App++ } "#microsoft.graph.passwordAuthenticationMethod" { $myObject.password = $true if ($myObject.MFAstatus -ne "Enabled") { $myObject.MFAstatus = "Disabled" } } "#microsoft.graph.phoneAuthenticationMethod" { $myObject.phone = $true $myObject.MFAstatus = "Enabled" $summary.MethodCounts.Phone++ } "#microsoft.graph.softwareOathAuthenticationMethod" { $myObject.softwareoath = $true $myObject.MFAstatus = "Enabled" $summary.MethodCounts.SoftwareOath++ } "#microsoft.graph.windowsHelloForBusinessAuthenticationMethod" { $myObject.hellobusiness = $true $myObject.MFAstatus = "Enabled" $summary.MethodCounts.HelloBusiness++ } "#microsoft.graph.temporaryAccessPassAuthenticationMethod" { $myObject.temporaryAccessPass = $true $myObject.MFAstatus = "Enabled" $summary.MethodCounts.TemporaryAccessPass++ } "#microsoft.graph.certificateBasedAuthConfiguration" { $myObject.certificateBasedAuthConfiguration = $true $myObject.MFAstatus = "Enabled" $summary.MethodCounts.CertificateBasedAuth++ } Default { Write-LogFile -Message "[WARNING] Unknown method type: $odataType for user $userPrinc" -Level Standard -Color "Yellow" } } } } else { Write-LogFile -Message "[WARNING] No MFA data found for user $userPrinc" -Level Standard -Color "Yellow" if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] MFAData is null or empty" -Level Debug Write-LogFile -Message "[DEBUG] MFAData: $($MFAData | ConvertTo-Json -Compress)" -Level Debug } } if ($IncludePhoneNumbers) { try { $phoneMethods = Get-MgUserAuthenticationPhoneMethod -UserId $userPrinc -ErrorAction SilentlyContinue if ($phoneMethods) { foreach ($phoneMethod in $phoneMethods) { $phoneObject = [PSCustomObject]@{ UserPrincipalName = $userPrinc UserId = $user.id PhoneNumber = $phoneMethod.PhoneNumber PhoneType = $phoneMethod.PhoneType SmsSignInState = $phoneMethod.SmsSignInState } $phoneResults += $phoneObject } } } catch { Write-LogFile -Message "[ERROR] Error retrieving phone methods for user $userPrinc : $_" -Level Minimal -Color "Red" } } if ($IncludePhoneNumbers) { try { $phoneMethods = Get-MgUserAuthenticationPhoneMethod -UserId $userPrinc -ErrorAction SilentlyContinue if ($phoneMethods) { foreach ($phoneMethod in $phoneMethods) { $phoneObject = [PSCustomObject]@{ UserPrincipalName = $userPrinc UserId = $user.id PhoneNumber = $phoneMethod.PhoneNumber PhoneType = $phoneMethod.PhoneType SmsSignInState = $phoneMethod.SmsSignInState } $phoneResults += $phoneObject } } } catch { Write-LogFile -Message "[ERROR] Error retrieving phone methods for user $userPrinc : $_" -Level Minimal -Color "Red" } } } catch { Write-LogFile -Message "[ERROR] Error processing user $userPrinc : $_" -Level Minimal -Color "Red" if ($isDebugEnabled) { Write-LogFile -Message "[DEBUG] Error details:" -Level Debug Write-LogFile -Message "[DEBUG] Exception type: $($_.Exception.GetType().Name)" -Level Debug Write-LogFile -Message "[DEBUG] Error message: $($_.Exception.Message)" -Level Debug Write-LogFile -Message "[DEBUG] Stack trace: $($_.ScriptStackTrace)" -Level Debug } } if ($myObject.MFAstatus -eq "Enabled") { $summary.MFAEnabled++ } else { $summary.MFADisabled++ } $results += $myObject } $summary.ProcessingTime = (Get-Date) - $summary.StartTime $date = Get-Date -Format "yyyyMMddHHmm" $authMethodsPath = "$OutputDir\$($date)-MFA-AuthenticationMethods.csv" $results | Export-Csv -Path $authMethodsPath -NoTypeInformation -Encoding $Encoding Write-LogFile -Message "[INFO] Retrieving user registration details..." -Level Standard $results = @() $registrationResults = "$OutputDir\$($date)-MFA-UserRegistrationDetails.csv" $nextLink = "https://graph.microsoft.com/v1.0/reports/authenticationMethods/userRegistrationDetails" do { $response = Invoke-MgGraphRequest -Uri $nextLink -Method Get -OutputType PSObject $userDetails = $response.value $nextLink = $response.'@odata.nextLink' ForEach ($detail in $userDetails) { if (!$UserIds -or $UserIds -contains $detail.userPrincipalName) { $myObject = [PSCustomObject]@{} $detail.PSObject.Properties | ForEach-Object { $value = if ($_.Value -is [System.Array]) { ($_.Value -join ', ') } else { $_.Value } $myObject | Add-Member -Type NoteProperty -Name $_.Name -Value $value } $results += $myObject } if ($IncludePhoneNumbers) { $userPrinc = $detail.userPrincipalName if ($userPhoneMethodsCache.ContainsKey($userPrinc) -and $userPhoneMethodsCache[$userPrinc]) { $phoneNumbers = @() $phoneTypes = @() $smsStates = @() foreach ($phoneMethod in $userPhoneMethodsCache[$userPrinc]) { $phoneNumbers += $phoneMethod.PhoneNumber $phoneTypes += $phoneMethod.PhoneType $smsStates += $phoneMethod.SmsSignInState } $myObject | Add-Member -Type NoteProperty -Name "MfaPhoneNumbers" -Value ($phoneNumbers -join "; ") $myObject | Add-Member -Type NoteProperty -Name "MfaPhoneTypes" -Value ($phoneTypes -join "; ") $myObject | Add-Member -Type NoteProperty -Name "MfaSmsSignInStates" -Value ($smsStates -join "; ") } else { $myObject | Add-Member -Type NoteProperty -Name "MfaPhoneNumbers" -Value "" $myObject | Add-Member -Type NoteProperty -Name "MfaPhoneTypes" -Value "" $myObject | Add-Member -Type NoteProperty -Name "MfaSmsSignInStates" -Value "" } } } } while ($nextLink) $results | Export-Csv -Path $registrationResults -NoTypeInformation -Encoding $Encoding Write-LogFile -Message "`n=== MFA Status Analysis Summary ===" -Color "Cyan" -Level Standard Write-LogFile -Message "`nMFA Status:" -Level Standard Write-LogFile -Message " Total Users: $($summary.TotalUsers)" -Level Standard Write-LogFile -Message " MFA Enabled: $($summary.MFAEnabled) users ($([math]::Round($summary.MFAEnabled/$summary.TotalUsers*100,1))%)" -Level Standard Write-LogFile -Message " MFA Disabled: $($summary.MFADisabled) users ($([math]::Round($summary.MFADisabled/$summary.TotalUsers*100,1))%)" -Level Standard if ($IncludePhoneNumbers) { Write-LogFile -Message " Users with Phone MFA: $($summary.PhoneNumberUsers) users" -Level Standard } Write-LogFile -Message "`nAuthentication Methods:" -Level Standard Write-LogFile -Message " - Email: $($summary.MethodCounts.Email)" -Level Standard Write-LogFile -Message " - Fido2: $($summary.MethodCounts.Fido2)" -Level Standard Write-LogFile -Message " - Microsoft Authenticator App: $($summary.MethodCounts.App)" -Level Standard Write-LogFile -Message " - Phone: $($summary.MethodCounts.Phone)" -Level Standard Write-LogFile -Message " - Software OAuth: $($summary.MethodCounts.SoftwareOath)" -Level Standard Write-LogFile -Message " - Hello Business: $($summary.MethodCounts.HelloBusiness)" -Level Standard Write-LogFile -Message " - Temporary Access Pass: $($summary.MethodCounts.TemporaryAccessPass)" -Level Standard Write-LogFile -Message " - Certificate Based Auth: $($summary.MethodCounts.CertificateBasedAuth)" -Level Standard Write-LogFile -Message "`nOutput Files:" -Level Standard Write-LogFile -Message " Authentication Methods: $authMethodsPath" -Level Standard Write-LogFile -Message " Registration Details: $registrationResults " -Level Standard Write-LogFile -Message "Processing Time: $($summary.ProcessingTime.ToString('mm\:ss'))" -Color "Green" -Level Standard Write-LogFile -Message "===================================" -Color "Cyan" -Level Standard } |