Identify MFA Deployment Source for Entra users

Removed MS Online PowerShell module dependency to retrieve per-user MFA and used Graph API instead.
This commit is contained in:
AdminDroid 2025-06-27 15:53:57 +05:30
parent 1c64812b27
commit b3662cb30b

View File

@ -1,7 +1,7 @@
<# <#
============================================================================================= =============================================================================================
Name: Identify MFA Deployment Sources in Microsoft 365 Using PowerShell Name: Identify MFA Deployment Sources in Microsoft 365 Using PowerShell
Version: 1.0 Version: 2.0
website: o365reports.com website: o365reports.com
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
@ -11,7 +11,7 @@ Script Highlights:
2. Helps you understand user MFA registration status (registered or not) to plan your MFA rollout campaigns efficiently. 2. Helps you understand user MFA registration status (registered or not) to plan your MFA rollout campaigns efficiently.
3. It specifically identifies MFA sources for external users as well. 3. It specifically identifies MFA sources for external users as well.
4. The script checks which Conditional Access policies demand MFA and tells you if users have registered for that MFA method as required by those policies. 4. The script checks which Conditional Access policies demand MFA and tells you if users have registered for that MFA method as required by those policies.
5. Automatically install the missing required modules (Microsoft Graph Beta, MSOnline) with your confirmation. 5. Automatically install the missing required module Microsoft Graph Beta with your confirmation.
6. The script can be executed with an MFA-enabled account too.  6. The script can be executed with an MFA-enabled account too. 
7. Exports report results as a CSV file.  7. Exports report results as a CSV file. 
8. The script is scheduler-friendly, making it easy to automate. 8. The script is scheduler-friendly, making it easy to automate.
@ -20,50 +20,39 @@ Script Highlights:
Change Log: Change Log:
~~~~~~~~~~ ~~~~~~~~~~
V1.0 (July 3, 2024) - File created V1.0 (Jul 03, 2024) - File created
V1.1 (Nov 04, 2024) - Scope updation to resolve permission issue V1.1 (Nov 04, 2024) - Scope updation to resolve permission issue
v2.0 (Jun 27, 2025) - Removed MSOnline PowerShell module to retrieve per-user MFA status and used Graph API.
For detailed Script execution: : https://o365reports.com/2024/06/26/identity-mfa-deployment-source-in-microsoft-365-using-powershell/ For detailed Script execution: : https://o365reports.com/2024/06/26/identity-mfa-deployment-source-in-microsoft-365-using-powershell/
============================================================================================ ============================================================================================
#> #>
param param
( (
[string]$TenantId, [string]$TenantId,
[string]$AppId, [string]$AppId,
[string]$CertificateThumbprint, [string]$CertificateThumbprint
[string]$UserName,
[string]$Password
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
#Check for Module Availability # Check if Microsoft Graph Beta module is installed
function Check-ModuleAvailability $MsGraphModule = Get-Module Microsoft.Graph.Beta -ListAvailable
{ if($MsGraphModule -eq $null)
param ( {
[string]$ModuleName, Write-host "Important: Microsoft Graph Beta module is unavailable. It is mandatory to have this module installed in the system to run the script successfully."
[string]$ModuleDisplayName $confirm = Read-Host Are you sure you want to install Microsoft Graph Beta module? [Y] Yes [N] No
) if($confirm -match "[yY]") {
$module = Get-Module $ModuleName -ListAvailable Write-host "Installing Microsoft Graph Beta module..."
if ($module -eq $null) Install-Module Microsoft.Graph.Beta -Scope CurrentUser -AllowClobber
{ Write-host "Microsoft Graph Beta module is installed in the machine successfully" -ForegroundColor Magenta
Write-Host "Important: $ModuleDisplayName module is unavailable. It is mandatory to have this module installed in the system to run the script successfully." }
$confirm = Read-Host "Are you sure you want to install $ModuleDisplayName module? [Y] Yes [N] No" else {
if ($confirm -match "[yY]") Write-host "Exiting. `nNote: Microsoft Graph Beta module must be available in your system to run the script" -ForegroundColor Red
{ Exit
Write-Host "Installing $ModuleDisplayName module..." }
Install-Module $ModuleName -Scope CurrentUser -AllowClobber
Write-Host "$ModuleDisplayName module is installed in the machine successfully" -ForegroundColor Magenta
}
else
{
Write-Host "Exiting. `nNote: $ModuleDisplayName module must be available in your system to run the script" -ForegroundColor Red
Exit
}
}
} }
function Process-ExternalUsers function Process-ExternalUsers
@ -152,80 +141,34 @@ function Get-UserIdsByRole {
return $UserIds return $UserIds
} }
# Check for Module Availabilit
Check-ModuleAvailability -ModuleName "MsOnline" -ModuleDisplayName "MsOnline"
Check-ModuleAvailability -ModuleName "Microsoft.Graph.Beta" -ModuleDisplayName "Microsoft Graph Beta"
#Disconnect from the Microsoft Graph If already connected #Disconnect from the Microsoft Graph If already connected
if (Get-MgContext) { if (Get-MgContext) {
Write-Host Disconnecting from the previous sesssion.... -ForegroundColor Yellow Write-Host Disconnecting from the previous sesssion.... -ForegroundColor Yellow
Disconnect-MgGraph | Out-Null Disconnect-MgGraph | Out-Null
} }
Write-Host "`nConnecting to Microsoft Graph..."
#Credentials check and Certificate check if(($TenantId -ne "") -and ($ClientId -ne "") -and ($CertificateThumbprint -ne ""))
if(($UserName -ne "") -and ($Password -ne "")) {
{ Connect-MgGraph -TenantId $TenantId -AppId $ClientId -CertificateThumbprint $CertificateThumbprint -ErrorAction SilentlyContinue -ErrorVariable ConnectionError | Out-Null
$SecuredPassword = ConvertTo-SecureString -AsPlainText $Password -Force if($ConnectionError -ne $null) {
$Credential = New-Object System.Management.Automation.PSCredential $UserName,$SecuredPassword Write-Host $ConnectionError -Foregroundcolor Red
$CredentialPassed = $true Exit
} }
Write-Host "Connected to Microsoft Graph PowerShell using $((Get-MgContext).AppName) application."
if((($AppId -ne "") -and ($CertificateThumbPrint -ne "")) -and ($TenantId -ne "")) }
else
{ {
$CBA=$true Connect-MgGraph -Scopes 'User.Read.All','Policy.Read.all','Team.ReadBasic.All','Directory.Read.All','AuditLog.Read.All' -NoWelcome -ErrorAction SilentlyContinue -Errorvariable ConnectionError | Out-Null
if($ConnectionError -ne $null) {
Write-Host "$ConnectionError" -Foregroundcolor Red
Exit
}
Write-Host "Connected to Microsoft Graph PowerShell using account: $((Get-MgContext).Account)"
} }
#version check Write-Host "`nRetrieving Entra Users..."
# Connecting to Microsoft Graph $MgUsers = Get-MgBetaUser -All | Sort-Object DisplayName
Write-Host "Connecting to Microsoft Graph..."
try
{
if($CBA -eq $true)
{
Connect-MgGraph -ApplicationId $AppId -TenantId $TenantId -CertificateThumbPrint $CertificateThumbprint
Write-Host Connected to Microsoft Graph PowerShell using (Get-MgContext).AppName application -ForegroundColor Yellow
}
else
{
Connect-MgGraph -Scopes 'User.Read.All','Policy.Read.all','Policy.ReadWrite.SecurityDefaults','Team.ReadBasic.All','Directory.Read.All','AuditLog.Read.All' -NoWelcome
Write-Host Connected to Microsoft Graph PowerShell using (Get-MgContext).Account account -ForegroundColor Yellow
}
}
catch [Exception]
{
Write-Host "Error: An error occurred while connecting to Microsoft Graph: $($_.Exception.Message)" -ForegroundColor Red
Exit
}
#Connecting to Msonline
Write-Host "Connecting to MSOnline......"
try
{
if($CredentialPassed -eq $true)
{
Connect-MsolService -Credential $Credential
}
elseif($CBA -eq $true)
{
Write-Host "MSonline module doesn't support certificate based authentication. Please enter the credential in the prompt"
Connect-MsolService
}
else
{
Connect-MsolService
}
Write-Host Connected to MSOnline -ForegroundColor Yellow
}
catch
{
Write-Host "Error: An error occurred while connecting to MsOnline: $($_.Exception.Message)" -ForegroundColor Red
Exit
}
#Get Users From Msonline
$Users = Get-MsolUser -All | Select-Object ObjectId , StrongAuthenticationRequirements
$MgBetaUsers = Get-MgBetaUser -All | Sort-Object DisplayName
#Check Security Default is enabled or not #Check Security Default is enabled or not
$SecurityDefault = (Get-MgBetaPolicyIdentitySecurityDefaultEnforcementPolicy).IsEnabled $SecurityDefault = (Get-MgBetaPolicyIdentitySecurityDefaultEnforcementPolicy).IsEnabled
$DirectoryRole = Get-MgBetaDirectoryRole -All $DirectoryRole = Get-MgBetaDirectoryRole -All
@ -236,7 +179,7 @@ $ProcessedUserCount =0
#check for Security default if disabled start to process the Conditional access policies #check for Security default if disabled start to process the Conditional access policies
$PolicySetting = 'True' $PolicySetting = 'True'
$TotalUser = $Users.count $TotalUser = $MgUsers.count
if($SecurityDefault) if($SecurityDefault)
{ {
$PolicySetting = 'False' $PolicySetting = 'False'
@ -258,8 +201,7 @@ else
#Get the External users if it was specified in the policy #Get the External users if it was specified in the policy
if($Policies.Conditions.Users.IncludeGuestsOrExternalUsers -ne $null -or $Policies.Conditions.Users.ExcludeGuestsOrExternalUsers -ne $null) if($Policies.Conditions.Users.IncludeGuestsOrExternalUsers -ne $null -or $Policies.Conditions.Users.ExcludeGuestsOrExternalUsers -ne $null)
{ {
Write-Host "Getting Information about Exernal Users" $ExternalUsers = $MgUsers | where-object {$_.ExternalUserState -ne $null}
$ExternalUsers = $MgBetaUsers | where-object {$_.ExternalUserState -ne $null}
$UsersinTenant = @{} $UsersinTenant = @{}
foreach($GuestUser in $ExternalUsers) foreach($GuestUser in $ExternalUsers)
{ {
@ -291,7 +233,7 @@ else
} }
$B2BGuest = $ExternalUsers | where-object {$_.UserType -eq 'Guest'} $B2BGuest = $ExternalUsers | where-object {$_.UserType -eq 'Guest'}
$B2BMember = $ExternalUsers | where-object {$_.UserType -ne 'Member'} $B2BMember = $ExternalUsers | where-object {$_.UserType -ne 'Member'}
$LocalGuest = $MgBetaUsers | where-object { $_.ExternalUserState -eq $null -and $_.UserType -eq 'Guest'} $LocalGuest = $MgUsers | where-object { $_.ExternalUserState -eq $null -and $_.UserType -eq 'Guest'}
#B2B Direct connect #B2B Direct connect
$Groups = Get-MgBetaTeam -All $Groups = Get-MgBetaTeam -All
@ -327,7 +269,6 @@ else
"temporaryAccessPass" = @("TemporaryAccessPassOneTime", "TemporaryAccessPassMultiuse") "temporaryAccessPass" = @("TemporaryAccessPassOneTime", "TemporaryAccessPassMultiuse")
} }
Write-Host "Processing the policies..."
# Loop through each policy # Loop through each policy
foreach ( $Policy in $Policies) foreach ( $Policy in $Policies)
{ {
@ -346,7 +287,7 @@ else
} }
elseif($Policy.Conditions.Users.IncludeUsers -eq 'All') elseif($Policy.Conditions.Users.IncludeUsers -eq 'All')
{ {
$MgBetaUsers.Id $MgUsers.Id
$Check = $false $Check = $false
} }
if($Check) if($Check)
@ -395,81 +336,71 @@ else
$IncludedUsers = $IncludeId | Select-Object -Unique $IncludedUsers = $IncludeId | Select-Object -Unique
} }
} }
$ProcessedUserCount = 0 $ProcessedUserCount = 0
Write-Host "Processing the Users"
$FilePath = ".\MFA_Deployment_Sources_Report_$((Get-Date -format 'yyyy-MMM-dd-ddd hh-mm tt').ToString()).csv" $FilePath = ".\MFA_Deployment_Sources_Report_$((Get-Date -format 'yyyy-MMM-dd-ddd hh-mm tt').ToString()).csv"
#Now starts the Process of Checking various conditions for the users and Export to the .csv file in the D local storage #Now starts the Process of Checking various conditions for the users
foreach ($User in $MgBetaUsers) foreach ($User in $MgUsers)
{ {
$name = @() $name = @()
$ProcessedUserCount++ $ProcessedUserCount++
$percent = ($ProcessedUserCount/$TotalUser)*100 $percent = ($ProcessedUserCount/$TotalUser)*100
Write-Progress -Activity "`n Processed user count: $ProcessedUserCount `n" -Status "Currently processing User: $($User.DisplayName)" -PercentComplete $percent Write-Progress -Activity "`n Processed user count: $ProcessedUserCount `n" -Status "Currently processing User: $($User.DisplayName)" -PercentComplete $percent
$Peruser = $Users | Where-Object {$_.ObjectID -contains $User.Id} $UserId = $User.Id
# Get user authentication details $Peruser = (Invoke-MgGraphRequest -Method GET -Uri "/beta/users/$UserId/authentication/requirements").perUserMfaState
$UserAuthDetails = $UserAuthenticationDetail | Where-Object { $_.UserPrincipalName -eq $User.UserPrincipalName } # Get user authentication details
$MethodsRegistered = if ($UserAuthDetails.MethodsRegistered -ne "") { $UserAuthDetails.MethodsRegistered -join ',' } else { 'None' } $UserAuthDetails = $UserAuthenticationDetail | Where-Object { $_.UserPrincipalName -eq $User.UserPrincipalName }
$Name += foreach($Pol in $Policies.DisplayName){if($UsersInPolicy[$pol] -contains $user.Id){$Pol}} $MethodsRegistered = if ($UserAuthDetails.MethodsRegistered -ne "") { $UserAuthDetails.MethodsRegistered -join ',' } else { 'None' }
$PolicyName = $Name -join',' $Name += foreach($Pol in $Policies.DisplayName){if($UsersInPolicy[$pol] -contains $user.Id){$Pol}}
$MFAEnforce = @{ $PolicyName = $Name -join','
'User Display Name' = $User.DisplayName $MFAEnforce = @{
'User Principal Name' = $User.UserPrincipalName 'User Display Name' = $User.DisplayName
'MFA Enforced Via' = if($PerUser.StrongAuthenticationRequirements.State -eq 'Enforced' -and $PolicySetting -eq 'True' -and $IncludedUsers -contains $User.Id){'Per User MFA , Conditional Access Policy'} 'User Principal Name' = $User.UserPrincipalName
elseif ( $PerUser.StrongAuthenticationRequirements.State -eq 'Enforced' -and $SecurityDefault -eq $true) { 'Per User MFA , Security Default' } 'MFA Enforced Via' = if($PerUser -eq 'Enforced' -and $PolicySetting -eq 'True' -and $IncludedUsers -contains $User.Id){'Per User MFA , Conditional Access Policy'}
elseif ($PerUser.StrongAuthenticationRequirements.State -eq 'Enforced') { 'Per User MFA' } elseif ( $PerUser -eq 'Enforced' -and $SecurityDefault -eq $true) { 'Per User MFA , Security Default' }
elseif ($SecurityDefault -eq $true) { 'Security Default' } elseif ($PerUser -eq 'Enforced') { 'Per User MFA' }
elseif ($PolicySetting -eq 'True' -and $IncludedUsers -contains $User.Id){'Conditional Access Policy'}elseif ($Peruser.BlockCredential -eq $true) {'SignIn Blocked'}else {'Disabled'} elseif ($SecurityDefault -eq $true) { 'Security Default' }
'Is Registered MFA Supported in CA' = if($IncludedUsers -contains $User.Id){if($UserAuthDetails.IsMFARegistered -contains 'True'){if($PolicySetting -eq 'True' -and $NotRegistered -notcontains $User.Id) {$true}elseif($Registered -contains $User.Id){$true}else{'False'}}else{'False'}}else{''} elseif ($PolicySetting -eq 'True' -and $IncludedUsers -contains $User.Id){'Conditional Access Policy'}
'CA MFA Status' = if($IncludedUsers -contains $User.Id){'Enabled'}else{'Disabled'} elseif ($User.AccountEnabled -eq $false) {'SignIn Blocked'}
'Assigned CA Policy' = if($IncludedUsers -contains $User.Id){$PolicyName}else{''} else {'Disabled'}
'Per User MFA Status' = if ($PerUser.StrongAuthenticationRequirements.State) { $PerUser.StrongAuthenticationRequirements.State } else { 'Disabled' } 'Is Registered MFA Supported in CA' = if($IncludedUsers -contains $User.Id){if($UserAuthDetails.IsMFARegistered -contains 'True'){if($PolicySetting -eq 'True' -and $NotRegistered -notcontains $User.Id) {'True'}elseif($Registered -contains $User.Id){'True'}else{'False'}}else{'False'}}else{''}
'Security Default Status' = if ($SecurityDefault -eq $false){'Disabled'} else{'Enabled'} 'CA MFA Status' = if($IncludedUsers -contains $User.Id){'Enabled'}else{'Disabled'}
'MFA Registered' = $UserAuthDetails.IsMFARegistered -contains 'True' 'Assigned CA Policy' = if($IncludedUsers -contains $User.Id){$PolicyName}else{''}
'Methods Registered' = if($MethodsRegistered){$MethodsRegistered}else{'None'} 'Per User MFA Status' = if ($PerUser) { $PerUser } else { 'Disabled' }
} 'Security Default Status' = if ($SecurityDefault -eq $false){'Disabled'} else{'Enabled'}
$MFAEnforced = New-Object PSObject -Property $MFAEnforce 'MFA Registered' = $UserAuthDetails.IsMFARegistered -contains 'True'
try 'Methods Registered' = if($MethodsRegistered){$MethodsRegistered}else{'None'}
{ }
$MFAEnforced | Select-Object 'User Display Name','User Principal Name','MFA Registered','Methods Registered','MFA Enforced Via','Per User MFA Status','Security Default Status','CA MFA Status','Assigned CA Policy','Is Registered MFA Supported in CA' | Export-Csv -Path $FilePath -NoTypeInformation -Append
}
catch
{
Write-Host "Error occurred While Exporting: $_" -ForegroundColor Red
}
}
<# $MFAEnforced = New-Object PSObject -Property $MFAEnforce
$Prompt = New-Object -ComObject wscript.shell try
$UserInput = $Prompt.popup("Do you want to open output file?",` 0,"Open Output File",4) {
If ($UserInput -eq 6) $MFAEnforced | Select-Object 'User Display Name','User Principal Name','MFA Registered','Methods Registered','MFA Enforced Via','Per User MFA Status','Security Default Status','CA MFA Status','Assigned CA Policy','Is Registered MFA Supported in CA' | Export-Csv -Path $FilePath -NoTypeInformation -Append
{ }
Invoke-Item "$FilePath" catch
{
Write-Host "Error occurred While Exporting: $_" -ForegroundColor Red
}
} }
Write-Host "Report was generated Successfully"
Write-Host "Total number of Users processed: $ProcessedUserCount"
#Disconnect from the Microsoft Graph #Disconnect from the Microsoft Graph
Disconnect-MgGraph | Out-Null Disconnect-MgGraph | Out-Null
Write-Host `n~~ Script prepared by AdminDroid Community ~~`n -ForegroundColor Green
Write-Host "~~ Check out " -NoNewline -ForegroundColor Green; Write-Host "admindroid.com" -ForegroundColor Yellow -NoNewline; Write-Host " to get access to 1800+ Microsoft 365 reports. ~~" -ForegroundColor Green `n`n
#>
#Open output file after execution #Open output file after execution
Write-Host `nScript executed successfully
if((Test-Path -Path $FilePath) -eq "True") if((Test-Path -Path $FilePath) -eq "True")
{ {
Write-Host `n~~ Script prepared by AdminDroid Community ~~`n -ForegroundColor Green Write-Host `n~~ Script prepared by AdminDroid Community ~~`n -ForegroundColor Green
Write-Host "~~ Check out " -NoNewline -ForegroundColor Green; Write-Host "admindroid.com" -ForegroundColor Yellow -NoNewline; Write-Host " to get access to 1800+ Microsoft 365 reports. ~~" -ForegroundColor Green `n`n Write-Host "~~ Check out " -NoNewline -ForegroundColor Green; Write-Host "admindroid.com" -ForegroundColor Yellow -NoNewline; Write-Host " to get access to 1800+ Microsoft 365 reports. ~~" -ForegroundColor Green `n
Write-Host "Exported report has $ProcessedUserCount user(s)" -ForegroundColor cyan Write-Host "Exported report has $ProcessedUserCount user(s)"
$Prompt = New-Object -ComObject wscript.shell $Prompt = New-Object -ComObject wscript.shell
$UserInput = $Prompt.popup("Do you want to open output file?",` 0,"Open Output File",4) $UserInput = $Prompt.popup("Do you want to open output file?",` 0,"Open Output File",4)
if ($UserInput -eq 6) if ($UserInput -eq 6)
{ {
Invoke-Item "$FilePath" Invoke-Item "$FilePath"
} }
Write-Host "Detailed report available in: " -NoNewline -ForegroundColor Yellow Write-Host `n"Detailed report available in: " -NoNewline -ForegroundColor Yellow
Write-Host $FilePath Write-Host $FilePath
} }
else else
{ {