Forum Discussion
NidalT
May 27, 2020Copper Contributor
Report on MFA Status with Conditional Access
Is there any effective way to get a report of the actual MFA state of your users? I mean, the individual MFA state as well as MFA enabled via Conditional Access. It's easy to report on the individu...
SimonBrown
Sep 25, 2022Copper Contributor
I've been using this script for a while but only this week realised that the reporting was incorrect.
I've just edited the script to be more accurate in a generic way. Hopefully this helps others.
Where the script originally 'checked' for Conditional Access, I replaced this
$MFAStatus='Enabled via Conditional Access'
With this
$mfaPolicies = Get-AzureADMSConditionalAccessPolicy | Where {$_.GrantControls.BuiltInControls -contains "Mfa"}
$aadUser = Get-AzureADUser -ObjectId $Upn
$userObjectId = $aadUser.ObjectId
$userMembership = ($aadUser | Get-AzureADUserMembership).ObjectId
if (!$userMembership) {
$userMembership = ""
}
if ($mfaPolicies | Where {
$_.Conditions.Users.IncludeUsers -eq "All" -or `
$_.Conditions.Users.IncludeUsers -contains $aadUser.ObjectId -or `
(Compare-Object -ReferenceObject $_.Conditions.Users.IncludeGroups -DifferenceObject $userMembership -IncludeEqual -ErrorAction SilentlyContinue).SideIndicator -contains "==" -or `
(Compare-Object -ReferenceObject $_.Conditions.Users.IncludeRoles -DifferenceObject $userMembership -IncludeEqual -ErrorAction SilentlyContinue).SideIndicator -contains "==" -and `
$_.Conditions.Users.ExcludeUsers -notcontains $aadUser.ObjectId -or `
(Compare-Object -ReferenceObject $_.Conditions.Users.ExcludeGroups -DifferenceObject $userMembership -IncludeEqual -ErrorAction SilentlyContinue).SideIndicator -contains "==" -or `
(Compare-Object -ReferenceObject $_.Conditions.Users.ExcludeRoles -DifferenceObject $userMembership -IncludeEqual -ErrorAction SilentlyContinue).SideIndicator -contains "==" -and `
$_.State -eq "enabled"
}) {
$MFAStatus="Enabled via Conditional Access"
} else {
$MFAStatus="Disabled"
}
All it really does is get the current users ObjectID, get the ObjectID of all the roles and groups to which the user is assigned and then checks the conditional access policies which are configured for MFA to see if the user belongs to them, is not excluded from them, and the policy is enabled.
I had to use the AzureAD module to do this so I also have to authenticate with that module so I had to update the part of the script which connects to MSOnline to also connect to Azure AD by replacing this
if(($UserName -ne "") -and ($Password -ne ""))
{
$SecuredPassword = ConvertTo-SecureString -AsPlainText $Password -Force
$Credential = New-Object System.Management.Automation.PSCredential $UserName,$SecuredPassword
Connect-MsolService -Credential $credential
}
else
{
Connect-MsolService | Out-Null
}
With this
if(($UserName -ne "") -and ($Password -ne ""))
{
$SecuredPassword = ConvertTo-SecureString -AsPlainText $Password -Force
$Credential = New-Object System.Management.Automation.PSCredential $UserName,$SecuredPassword
Connect-MsolService -Credential $credential
Connect-AzureAD -Credential $credential
}
else
{
Write-Host "You will be prompted to sign-in twice, once to Microsoft 365 and then for Azure AD!" -ForegroundColor Yellow
Connect-MsolService | Out-Null
Connect-AzureAD | Out-Null
}
The issue seems to be that the script writer assumed that not explicitly enabling/enforcing MFA for a user meant that it was "Enabled via Conditional Access" and hard coded that value. This should have been "Controlled via Conditional Access", but it never meant a policy was actually applied.
UPADTE: These changes do cause the script to throw errors when a user is not a member of any roles/groups. They are nothing to worry about and the script is still accurate according to my testing. I'll try and figure out how to suppress them without making it really complicated.
UPDATE: Added a quick if statement to check if `$userMembership` is null, if it is then it's created as a blank variable which doesn't result in an error for the `compare-object`.
NidalT
Sep 25, 2022Copper Contributor
This is gold!
I didn't think of doing it that way. It makes much more sense!
Thank you!
I'll review my script and update it with your part.
I didn't think of doing it that way. It makes much more sense!
Thank you!
I'll review my script and update it with your part.
- rizavanJul 24, 2024Copper ContributorSimonBrown many thanks for your quick response.
- SimonBrownJul 24, 2024Copper Contributor
Sorry DonDDragon, I didn't see the notification for your message
Unfortunately I have not kept my script up to date to use the new MS Graph modules.
The steps above should help you adapt the latest version of the AdminDroid script.When I get some time I will try and fork the current version of the AdminDroid script, update it and then link it here.
I can't remember the exact status of the old modules, they may still work. In which case, the latest version of my code is:
Param ( [Parameter(Mandatory = $false)] [switch]$DisabledOnly, [switch]$EnabledOnly, [switch]$EnforcedOnly, [switch]$ConditionalAccessOnly, [switch]$AdminOnly, [switch]$LicensedUserOnly, [Nullable[boolean]]$SignInAllowed = $null, [string]$UserName, [string]$Password ) #Check for MSOnline module $Modules=Get-Module -Name MSOnline -ListAvailable if($Modules.count -eq 0) { Write-Host Please install MSOnline module using below command: `nInstall-Module MSOnline -ForegroundColor yellow Exit } #Storing credential in script for scheduling purpose/ Passing credential as parameter if(($UserName -ne "") -and ($Password -ne "")) { $SecuredPassword = ConvertTo-SecureString -AsPlainText $Password -Force $Credential = New-Object System.Management.Automation.PSCredential $UserName,$SecuredPassword Connect-MsolService -Credential $credential Connect-AzureAD -Credential $credential } else { Write-Host "You will be prompted to sign-in twice, once to Microsoft 365 and then for Azure AD!" -ForegroundColor Yellow Connect-MsolService | Out-Null Connect-AzureAD | Out-Null } $Result="" $Results=@() $UserCount=0 $PrintedUser=0 #Output file declaration $ExportCSV=".\MFADisabledUserReport_$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).csv" $ExportCSVReport=".\MFAEnabledUserReport_$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).csv" #Loop through each user Get-MsolUser -All | foreach{ $UserCount++ $DisplayName=$_.DisplayName $Upn=$_.UserPrincipalName $MFAStatus=$_.StrongAuthenticationRequirements.State $MethodTypes=$_.StrongAuthenticationMethods $Title=$_.Title $UsageLocation=$_.UsageLocation $RolesAssigned="" Write-Progress -Activity "`n Processed user count: $UserCount "`n" Currently Processing: $DisplayName" if($_.BlockCredential -eq "True") { $SignInStatus="False" $SignInStat="Denied" } else { $SignInStatus="True" $SignInStat="Allowed" } #Filter result based on SignIn status if(($SignInAllowed -ne $null) -and ([string]$SignInAllowed -ne [string]$SignInStatus)) { return } #Filter result based on License status if(($LicensedUserOnly.IsPresent) -and ($_.IsLicensed -eq $False)) { return } if($_.IsLicensed -eq $true) { $LicenseStat="Licensed" } else { $LicenseStat="Unlicensed" } #Check for user's Admin role $Roles=(Get-MsolUserRole -UserPrincipalName $upn).Name if($Roles.count -eq 0) { $RolesAssigned="No roles" $IsAdmin="False" } else { $IsAdmin="True" foreach($Role in $Roles) { $RolesAssigned=$RolesAssigned+$Role if($Roles.indexof($role) -lt (($Roles.count)-1)) { $RolesAssigned=$RolesAssigned+"," } } } #Filter result based on Admin users if(($AdminOnly.IsPresent) -and ([string]$IsAdmin -eq "False")) { return } #Check for MFA enabled user if(($MethodTypes -ne $Null) -or ($MFAStatus -ne $Null) -and (-Not ($DisabledOnly.IsPresent) )) { #Check for Conditional Access if($MFAStatus -eq $null) { $mfaPolicies = Get-AzureADMSConditionalAccessPolicy | Where {$_.GrantControls.BuiltInControls -contains "Mfa"} $aadUser = Get-AzureADUser -ObjectId $Upn $userMembership = ($aadUser | Get-AzureADUserMembership).ObjectId if (!$userMembership) { $userMembership = "" } if ($mfaPolicies | Where { $_.Conditions.Users.IncludeUsers -eq "All" -or ` $_.Conditions.Users.IncludeUsers -contains $aadUser.ObjectId -or ` (Compare-Object -ReferenceObject $_.Conditions.Users.IncludeGroups -DifferenceObject $userMembership -IncludeEqual -ErrorAction SilentlyContinue).SideIndicator -contains "==" -or ` (Compare-Object -ReferenceObject $_.Conditions.Users.IncludeRoles -DifferenceObject $userMembership -IncludeEqual -ErrorAction SilentlyContinue).SideIndicator -contains "==" -and ` $_.Conditions.Users.ExcludeUsers -notcontains $aadUser.ObjectId -or ` (Compare-Object -ReferenceObject $_.Conditions.Users.ExcludeGroups -DifferenceObject $userMembership -IncludeEqual -ErrorAction SilentlyContinue).SideIndicator -contains "==" -or ` (Compare-Object -ReferenceObject $_.Conditions.Users.ExcludeRoles -DifferenceObject $userMembership -IncludeEqual -ErrorAction SilentlyContinue).SideIndicator -contains "==" -and ` $_.State -eq "enabled" }) { $MFAStatus="Enabled via Conditional Access" } else { $MFAStatus="Disabled" } } #Filter result based on EnforcedOnly filter if((([string]$MFAStatus -eq "Enabled") -or ([string]$MFAStatus -eq "Enabled via Conditional Access")) -and ($EnforcedOnly.IsPresent)) { return } #Filter result based on EnabledOnly filter if(([string]$MFAStatus -eq "Enforced") -and ($EnabledOnly.IsPresent)) { return } #Filter result based on MFA enabled via Other source if((($MFAStatus -eq "Enabled") -or ($MFAStatus -eq "Enforced")) -and ($ConditionalAccessOnly.IsPresent)) { return } $Methods="" $MethodTypes="" $MethodTypes=$_.StrongAuthenticationMethods.MethodType $DefaultMFAMethod=($_.StrongAuthenticationMethods | where{$_.IsDefault -eq "True"}).MethodType $MFAPhone=$_.StrongAuthenticationUserDetails.PhoneNumber $MFAEmail=$_.StrongAuthenticationUserDetails.Email if($MFAPhone -eq $Null) { $MFAPhone="-"} if($MFAEmail -eq $Null) { $MFAEmail="-"} if($MethodTypes -ne $Null) { $ActivationStatus="Yes" foreach($MethodType in $MethodTypes) { if($Methods -ne "") { $Methods=$Methods+"," } $Methods=$Methods+$MethodType } } else { $ActivationStatus="No" $Methods="-" $DefaultMFAMethod="-" $MFAPhone="-" $MFAEmail="-" } #Print to output file $PrintedUser++ $Result=@{'DisplayName'=$DisplayName;'UserPrincipalName'=$upn;'Title'=$Title;'UsageLocation'=$UsageLocation;'MFAStatus'=$MFAStatus;'ActivationStatus'=$ActivationStatus;'DefaultMFAMethod'=$DefaultMFAMethod;'AllMFAMethods'=$Methods;'MFAPhone'=$MFAPhone;'MFAEmail'=$MFAEmail;'LicenseStatus'=$LicenseStat;'IsAdmin'=$IsAdmin;'AdminRoles'=$RolesAssigned;'SignInStatus'=$SigninStat} $Results= New-Object PSObject -Property $Result $Results | Select-Object DisplayName,UserPrincipalName,Title,UsageLocation,MFAStatus,ActivationStatus,DefaultMFAMethod,AllMFAMethods,MFAPhone,MFAEmail,LicenseStatus,IsAdmin,AdminRoles,SignInStatus | Export-Csv -Path $ExportCSVReport -Notype -Append } #Check for MFA disabled user elseif(($DisabledOnly.IsPresent) -and ($MFAStatus -eq $Null) -and ($_.StrongAuthenticationMethods.MethodType -eq $Null)) { $MFAStatus="Disabled" $Department=$_.Department if($Department -eq $Null) { $Department="-"} $PrintedUser++ $Result=@{'DisplayName'=$DisplayName;'UserPrincipalName'=$upn;'Department'=$Department;'MFAStatus'=$MFAStatus;'LicenseStatus'=$LicenseStat;'IsAdmin'=$IsAdmin;'AdminRoles'=$RolesAssigned; 'SignInStatus'=$SigninStat} $Results= New-Object PSObject -Property $Result $Results | Select-Object DisplayName,UserPrincipalName,Department,Title,UsageLocation,MFAStatus,LicenseStatus,IsAdmin,AdminRoles,SignInStatus | Export-Csv -Path $ExportCSV -Notype -Append } } #Open output file after execution Write-Host `nScript executed successfully if((Test-Path -Path $ExportCSV) -eq "True") { Write-Host "MFA Disabled user report available in: $ExportCSV" $Prompt = New-Object -ComObject wscript.shell $UserInput = $Prompt.popup("Do you want to open output file?",` 0,"Open Output File",4) If ($UserInput -eq 6) { Invoke-Item "$ExportCSV" } Write-Host Exported report has $PrintedUser users } elseif((Test-Path -Path $ExportCSVReport) -eq "True") { Write-Host "MFA Enabled user report available in: $ExportCSVReport" $Prompt = New-Object -ComObject wscript.shell $UserInput = $Prompt.popup("Do you want to open output file?",` 0,"Open Output File",4) If ($UserInput -eq 6) { Invoke-Item "$ExportCSVReport" } Write-Host Exported report has $PrintedUser users } Else { Write-Host No user found that matches your criteria. } #Clean up session Get-PSSession | Remove-PSSession
- rizavanJul 24, 2024Copper Contributor
DonDDragon @SimonBrown please can anybody share the final script. Many thanks
- DonDDragonFeb 02, 2023Copper Contributor
any chance I can download that script ty SimonBrown
- SimonBrownSep 25, 2022Copper ContributorNo worries.
It feels like it should have been much more straight forward. But once I got my head around what values were available and where, it wasn't too bad.