Multi-Factor Authentication Should Be Enabled for Privileged Accounts
Update: This article describes a script using Graph APIs to generate a report showing the MFA status for accounts and highlights administrative accounts that aren’t MFA-enabled. Given the deprecation of the MSOL module, you should switch to the Graph version.
If, like me, you were impressed by the case laid out in the July 10 2019 blog entitled Your Pa$$word doesn’t matter by Alex Weinert (Microsoft), you might wonder how to take his advice to “turn on MFA” for accounts. The process can take some time and user education because you can’t really enable MFA for “average users” if you don’t prepare them to deal with the resulting challenges, roll out the Microsoft Authenticator app, and so on. And then there’s the ongoing need to find unprotected Azure AD accounts to coach their owners about the wonders of MFA.
Reporting Accounts with Administrative Roles
One immediate step you can take is to clamp down on accounts holding one or more Azure Active Directory administrative roles that are not MFA-enabled. Microsoft has an Azure Active Directory usage and insights report about authentication methods to inform tenants about the accounts that are/are not enabled for MFA and self-service password reset (Figure 1), but it doesn’t highlight accounts holding administrative roles.
Figure 1: Azure Active Directory Usage and Insights Report about MFA and SSPR
The script is imperfect and could do with improvement in terms of optimization and error handling, but it works. Here’s what it does.
Azure Active Directory defines directory roles which can be assigned to accounts to allow those accounts to perform specific tasks. In this case, we’re interested in some of the more highly-permissioned roles like Exchange Admin, so we use the Get-AzureADDirectoryRole cmdlet to grab the GUIDs identifying these roles and put them in variables. We then call the Get-AzureADDirectoryRoleMember cmdlet to populate another set of variables with details of the accounts that hold each role.
The script then calls the Get-MsolUser cmdlet to create a collection of Azure Active Directory licensed accounts (yes, there’s an odd mix of the Azure AD V1 and V2 cmdlets in the script; that’s because I can’t work out how to get MFA information using the V2 cmdlets). Using the MFA report code described here, each account is checked to see if it is MFA-enabled. We then create an array of accounts which are not MFA-enabled. These accounts are checked to see if they hold one of the administrative roles we’re interested in. If an account holds one or more of those roles, we capture its details.
# Extract users whose accounts don't have MFA
$MFAUsers = $MFAReport | ? {$_.MFAUsed -ne "Enforced"}
If (!($MFAUsers)) { Write-Host "No privileged accounts found without MFA protection" ; break}
Write-Host "Checking MFA status for accounts holding admin roles..."
$i = 0
$Report = [System.Collections.Generic.List[Object]]::new() # Create output file
# Check Admin Roles if MFA not enabled
ForEach ($User in $MFAUsers) {
$Roles = $Null
If ($UserAccountAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName "Account holds the User Account Admin role" -ForegroundColor Red
$Roles = "Account Admin" }
If ($TenantAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName "Account holds the Tenant Admin role" -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = "Tenant Admin" } Else { $Roles = $Roles + "; Tenant Admin" } }
If ($TeamsAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName "Account holds the Teams Admin role" -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = "Teams Admin" } Else { $Roles = $Roles + "; Teams Admin" } }
If ($ExchangeAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName "Account holds the Exchange Admin role" -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = "Exchange Admin" } Else { $Roles = $Roles + "; Exchange Admin" } }
If ($SharePointAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName "Account holds the SharePoint Admin role" -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = "SharePoint Admin" } Else { $Roles = $Roles + "; SharePoint Admin" } }
If ($Roles -ne $Null) {Write-Host "User" $User.DisplayName "is assigned the following roles:" $Roles -ForeGroundColor Yellow; $i++
$ReportLine = [PSCustomObject]@{
User = $User.DisplayName
UPN = $User.UserPrincipalName
Roles = $Roles
MFA = $User.MFAUsed }
$Report.Add($ReportLine) } #End if
}
Reporting Unprotected Azure AD Administrative Accounts
As the code runs, it generates information about accounts which are not MFA-enabled but hold administrative roles (Figure 2). Apart from anything else, this is a good way to see what accounts hold administrative roles and ask whether they need to hold those roles.
Figure 2: Viewing details of Azure AD accounts with administrative roles which are not MFA-protected
Finally, a CSV file is generated with details of accounts holding Azure AD administrative roles which are not MFA-enabled and exported to a CSV file. Figure 3 shows details of what the file contains as viewed through the Out-GridView cmdlet. It’s easy to pick out the accounts whose security needs to be improved.
Figure 3: Viewing details of unprotected accounts through Out-GridView
As always, we’re happy to hear about other approaches to the problem. Please post your ideas as a comment to this post.
Need more solutions to common Office 365 Admin problems? The Office 365 for IT Pros eBook is packed full of ideas…
10 Replies to “How to Find Unprotected Azure Active Directory Administrative Accounts”
Hi
I think you should limit the script to licensed accounts only.
Unlicensed admin accounts should get licensed and then have MFA enforced, excluding one or two break-glass accounts.
I’m trying to improve our Secure Score, which is showing that 2 out of 7 admin accounts don’t have MFA…but doesn’t then tell me which accounts are affected. This is what brought me to this page, however your script doesn’t cover the above roles, which secure score references.
There’s the command Get-AzureADMSRoleDefinition, which displays info on these roles, but I can’t figure out how to edit the rest of the script to include this, as I’m quite the PS novice! I had a go creating the script below, but although it now doesn’t give any errors, it also doesn’t find any admin accounts (and I know there is at least 1).
# ReportAdminAzureADAccountsNoMFA.PS1
# A script to find Azure AD accounts with privileged roles that aren’t protected by MFA
#
# https://github.com/12Knocksinna/Office365itpros/blob/master/ReportAdminAzureADAccountsNoMFA.PS1
#
# Uses both AzureAD and MSOnline modules
CLS
$ModulesLoaded = Get-Module | Select Name
If (!($ModulesLoaded -match “AzureAD”)) {Write-Host “Please connect to the Azure AD module and then restart the script” ; break}
If (!($ModulesLoaded -match “MSOnline”)) {Write-Host “Please connect to the Microsoft Online Services module and then restart the script”; break}
# Extract users whose accounts don’t have MFA
$MFAUsers = $MFAReport | ? {$_.MFAUsed -ne “Enforced”}
If (!($MFAUsers)) { Write-Host “No privileged accounts found without MFA protection” ; break}
Write-Host “Checking MFA status for accounts holding admin roles…”
$i = 0
$Report = [System.Collections.Generic.List[Object]]::new() # Create output file
# Check Admin Roles if MFA not enabled
ForEach ($User in $MFAUsers) {
$Roles = $Null
If ($AppAdmins.PrincipalId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Application Administrator role” -ForegroundColor Red
$Roles = “Application Administrator” }
If ($AuthAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Authentication Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Authentication Administrator” } Else { $Roles = $Roles + “; Authentication Administrator” } }
If ($BillAdminRole.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Billing Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Billing Administrator” } Else { $Roles = $Roles + “; Billing Administrator” } }
If ($CloudAppAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Cloud application Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Cloud application Administrator” } Else { $Roles = $Roles + “; Cloud application Administrator” } }
If ($CondAccAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Conditional Access Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Conditional Access Administrator” } Else { $Roles = $Roles + “; Conditional Access Administrator” } }
If ($ExchangeAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Exchange Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Exchange Administrator” } Else { $Roles = $Roles + “; Exchange Administrator” } }
If ($GlobalAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Global Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Global Administrator” } Else { $Roles = $Roles + “; Global Administrator” } }
If ($HelpdeskAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Helpdesk Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Helpdesk Administrator” } Else { $Roles = $Roles + “; Helpdesk Administrator” } }
If ($PasswordAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Password Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Password Administrator” } Else { $Roles = $Roles + “; Password Administrator” } }
If ($PrivAuthAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Privileged authentication Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Privileged authentication Administrator” } Else { $Roles = $Roles + “; Privileged authentication Administrator” } }
If ($PrivRoleAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Privileged role Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Privileged role Administrator” } Else { $Roles = $Roles + “; Privileged role Administrator” } }
If ($SecurityAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Security Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Security Administrator” } Else { $Roles = $Roles + “; Security Administrator” } }
If ($SharePointAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the SharePoint Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “SharePoint Administrator” } Else { $Roles = $Roles + “; SharePoint Administrator” } }
If ($UserAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the User Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “User Administrator” } Else { $Roles = $Roles + “; User Administrator” } }
If ($Roles -ne $Null) {Write-Host “User” $User.DisplayName “is assigned the following roles:” $Roles -ForeGroundColor Yellow; $i++
$ReportLine = [PSCustomObject]@{
User = $User.DisplayName
UPN = $User.UserPrincipalName
Roles = $Roles
MFA = $User.MFAUsed }
$Report.Add($ReportLine) } #End if
}
Write-Host “All done.” $i “privileged accounts found which aren’t protected by MFA – see C:\temp\MFAReport.CSV for details”
$Report | Out-GridView
$Report | Export-CSV -NoTypeInformation C:\temp\MFAReport.CSV
# Do not use our scripts in production until you are satisfied that the code meets the need of your organization. Never run any code downloaded from the Internet without
# first validating the code in a non-production environment.
If you know an account that has an admin role and isn’t protected by MFA, you could run the code just for that account to see what it generates. I often do this when I’m creating a loop and want to be sure that each item in the loop will be processed successfully.
I’m coming at this from a different angle. Not so much looking at the MFA enablement/enforcement on the account level but looking at conditional access policies to see what accounts have MFA enforced and under what conditions. I know we can just look at the policies, but as you grow more and more policies, and your conditions/exclusions get complex it would be nice to be able to spit out a report which shows that you have certain accounts excluded, which you might expect (failsafe/break glass), but some others which you might not expect. For example, you add a user to an exclude group temporarily or for testing and forget to remove it.
{"id":null,"mode":"button","open_style":"in_modal","currency_code":"EUR","currency_symbol":"\u20ac","currency_type":"decimal","blank_flag_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":100,"top_media_type":"featured_image","featured_image_url":"https:\/\/office365itpros.com\/wp-content\/uploads\/2022\/11\/cover-141x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Virtual Tip Jar","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"Office 365 for IT Pros","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"Office 365 for IT Pros","form_subtitle":null,"currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for supporting the work of Office 365 for IT Pros!","payment_confirmation_title":"Office 365 for IT Pros","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"Enter the name on your card."},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email address"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}
Hi
I think you should limit the script to licensed accounts only.
Unlicensed admin accounts should get licensed and then have MFA enforced, excluding one or two break-glass accounts.
The script does filter for licensed member accounts… But because it’s PowerShell, you can do what you like to amend it.
Thank you Tony. Great script.
I had to modify the beginning to set different guids for my tenant as below.
# Define GUIDs for the Privileged Roles (from Get-AzureADDirectoryRole)
$UserAccountAdmin = Get-AzureADDirectoryRole | Where-Object {$_.displayName -eq ‘User Account Administrator’} | Select ObjectId
$TenantAdmin = Get-AzureADDirectoryRole | Where-Object {$_.displayName -eq ‘Company Administrator’} | Select ObjectId
$TeamsAdmin = Get-AzureADDirectoryRole | Where-Object {$_.displayName -eq ‘Lync Service Administrator’} | Select ObjectId
$ExchangeAdmin = Get-AzureADDirectoryRole | Where-Object {$_.displayName -eq ‘Exchange Service Administrator’} | Select ObjectId
$SharePointAdmin = Get-AzureADDirectoryRole | Where-Object {$_.displayName -eq ‘Sharepoint Service Administrator’} | Select ObjectId
# Find out the set of accounts that hold these admin roles in the tenant
$UserAccountAdmins = Get-AzureADDirectoryRoleMember -ObjectId $UserAccountAdmin.ObjectID | Select ObjectId, UserPrincipalName
$TenantAdmins = Get-AzureADDirectoryRoleMember -ObjectId $TenantAdmin.ObjectID | Select ObjectId, UserPrincipalName
$TeamsAdmins = Get-AzureADDirectoryRoleMember -ObjectId $TeamsAdmin.ObjectID | Select ObjectId, UserPrincipalName
$ExchangeAdmins = Get-AzureADDirectoryRoleMember -ObjectId $ExchangeAdmin.ObjectID | Select ObjectId, UserPrincipalName
$SharePointAdmins = Get-AzureADDirectoryRoleMember -ObjectId $SharePointAdmin.ObjectID | Select ObjectId, UserPrincipalName
Thanks Paul… Your suggestions are good ones because the GUIDs could change from tenant to tenant.
Has anyone managed to get this working when checking against the following admin roles?
Application administrator
Authentication administrator
Billing administrator
Cloud application administrator
Conditional Access administrator
Exchange administrator
Global administrator
Helpdesk administrator
Password administrator
Privileged authentication administrator
Privileged role administrator
Security administrator
SharePoint administrator
User administrator
I’m trying to improve our Secure Score, which is showing that 2 out of 7 admin accounts don’t have MFA…but doesn’t then tell me which accounts are affected. This is what brought me to this page, however your script doesn’t cover the above roles, which secure score references.
There’s the command Get-AzureADMSRoleDefinition, which displays info on these roles, but I can’t figure out how to edit the rest of the script to include this, as I’m quite the PS novice! I had a go creating the script below, but although it now doesn’t give any errors, it also doesn’t find any admin accounts (and I know there is at least 1).
# ReportAdminAzureADAccountsNoMFA.PS1
# A script to find Azure AD accounts with privileged roles that aren’t protected by MFA
#
# https://github.com/12Knocksinna/Office365itpros/blob/master/ReportAdminAzureADAccountsNoMFA.PS1
#
# Uses both AzureAD and MSOnline modules
CLS
$ModulesLoaded = Get-Module | Select Name
If (!($ModulesLoaded -match “AzureAD”)) {Write-Host “Please connect to the Azure AD module and then restart the script” ; break}
If (!($ModulesLoaded -match “MSOnline”)) {Write-Host “Please connect to the Microsoft Online Services module and then restart the script”; break}
# Retrieve GUIDs for the Privileged Roles (from Get-AzureADDirectoryRole)
Write-Host “Finding Azure Active Directory administrative roles…”
$AppAdminRole = Get-AzureADMSRoleDefinition | Where-Object {$_.DisplayName -eq ‘Application Administrator’} | Select Id
$AuthAdminRole = Get-AzureADMSRoleDefinition | Where-Object {$_.DisplayName -eq ‘Authentication Administrator’} | Select Id
$BillAdminRole = Get-AzureADMSRoleDefinition | Where-Object {$_.DisplayName -eq ‘Billing Administrator’} | Select Id
$CloudAppAdminRole = Get-AzureADMSRoleDefinition | Where-Object {$_.DisplayName -eq ‘Cloud application Administrator’} | Select Id
$CondAccAdminRole = Get-AzureADMSRoleDefinition | Where-Object {$_.DisplayName -eq ‘Conditional Access Administrator’} | Select Id
$ExchangeAdminRole = Get-AzureADMSRoleDefinition | Where-Object {$_.DisplayName -eq ‘Exchange Administrator’} | Select Id
$GlobalAdminRole = Get-AzureADMSRoleDefinition | Where-Object {$_.DisplayName -eq ‘Global Administrator’} | Select Id
$HelpdeskAdminRole = Get-AzureADMSRoleDefinition | Where-Object {$_.DisplayName -eq ‘Helpdesk Administrator’} | Select Id
$PasswordAdminRole = Get-AzureADMSRoleDefinition | Where-Object {$_.DisplayName -eq ‘Password Administrator’} | Select Id
$PrivAuthAdminRole = Get-AzureADMSRoleDefinition | Where-Object {$_.DisplayName -eq ‘Privileged authentication Administrator’} | Select Id
$PrivRoleAdminRole = Get-AzureADMSRoleDefinition | Where-Object {$_.DisplayName -eq ‘Privileged role Administrator’} | Select Id
$SecurityAdminRole = Get-AzureADMSRoleDefinition | Where-Object {$_.DisplayName -eq ‘Security Administrator’} | Select Id
$SharePointAdminRole = Get-AzureADMSRoleDefinition | Where-Object {$_.DisplayName -eq ‘Sharepoint Administrator’} | Select Id
$UserAdminRole = Get-AzureADMSRoleDefinition | Where-Object {$_.DisplayName -eq ‘User Administrator’} | Select Id
# Find out the set of accounts that hold these admin roles in the tenant
$AppAdmins = Get-AzureADMSRoleAssignment -Filter “roleDefinitionId eq ‘$($AppAdminRole.Id)'” | Select PrincipalId
$AuthAdmins = Get-AzureADMSRoleAssignment -Filter “roleDefinitionId eq ‘$($AuthAdminRole.Id)'” | Select PrincipalId
$BillAdmins = Get-AzureADMSRoleAssignment -Filter “roleDefinitionId eq ‘$($BillAdminRole.Id)'” | Select PrincipalId
$CloudAppAdmins = Get-AzureADMSRoleAssignment -Filter “roleDefinitionId eq ‘$($CloudAppAdminRole.Id)'” | Select PrincipalId
$CondAccAdmins = Get-AzureADMSRoleAssignment -Filter “roleDefinitionId eq ‘$($CondAccAdminRole.Id)'” | Select PrincipalId
$ExchangeAdmins = Get-AzureADMSRoleAssignment -Filter “roleDefinitionId eq ‘$($ExchangeAdminRole.Id)'” | Select PrincipalId
$GlobalAdmins = Get-AzureADMSRoleAssignment -Filter “roleDefinitionId eq ‘$($GlobalAdminRole.Id)'” | Select PrincipalId
$HelpdeskAdmins = Get-AzureADMSRoleAssignment -Filter “roleDefinitionId eq ‘$($HelpdeskAdminRole.Id)'” | Select PrincipalId
$PasswordAdmins = Get-AzureADMSRoleAssignment -Filter “roleDefinitionId eq ‘$($PasswordAdminRole.Id)'” | Select PrincipalId
$PrivAuthAdmins = Get-AzureADMSRoleAssignment -Filter “roleDefinitionId eq ‘$($PrivAuthAdminRole.Id)'” | Select PrincipalId
$PrivRoleAdmins = Get-AzureADMSRoleAssignment -Filter “roleDefinitionId eq ‘$($PrivRoleAdminRole.Id)'” | Select PrincipalId
$SecurityAdmins = Get-AzureADMSRoleAssignment -Filter “roleDefinitionId eq ‘$($SecurityAdminRole.Id)'” | Select PrincipalId
$SharePointAdmins = Get-AzureADMSRoleAssignment -Filter “roleDefinitionId eq ‘$($SharePointAdminRole.Id)'” | Select PrincipalId
$UserAdmins = Get-AzureADMSRoleAssignment -Filter “roleDefinitionId eq ‘$($UserAdminRole.Id)'” | Select PrincipalId
$MFAReport = [System.Collections.Generic.List[Object]]::new() # Create output file
Write-Host “Finding Azure AD user accounts and checking their MFA status…”
$Users = (Get-MsolUser -All | ? {$_.UserType -eq “Member” -and $_.Islicensed -eq $True} | Sort DisplayName)
ForEach ($User in $Users) {
$MFAMethods = $User.StrongAuthenticationMethods.MethodType
$MFAEnforced = $User.StrongAuthenticationRequirements.State
$DefaultMFAMethod = ($User.StrongAuthenticationMethods | ? {$_.IsDefault -eq “True”}).MethodType
If (($MFAEnforced -eq “Enforced”) -or ($MFAEnforced -eq “Enabled”)) {
Switch ($DefaultMFAMethod) {
“OneWaySMS” { $MethodUsed = “One-way SMS” }
“TwoWayVoiceMobile” { $MethodUsed = “Phone call verification” }
“PhoneAppOTP” { $MethodUsed = “Hardware token or authenticator app” }
“PhoneAppNotification” { $MethodUsed = “Authenticator app” }
} #End Switch
}
Else {
$MFAEnforced= “Not Enabled”
$MethodUsed = “MFA Not Used” }
$MFAReportLine = [PSCustomObject] @{
UserPrincipalName = $User.UserPrincipalName
DisplayName = $User.DisplayName
MFAUsed = $MFAEnforced
MFAMethod = $MethodUsed
ObjectId = $User.ObjectId }
$MFAReport.Add($MFAReportLine)
} # End For
# Extract users whose accounts don’t have MFA
$MFAUsers = $MFAReport | ? {$_.MFAUsed -ne “Enforced”}
If (!($MFAUsers)) { Write-Host “No privileged accounts found without MFA protection” ; break}
Write-Host “Checking MFA status for accounts holding admin roles…”
$i = 0
$Report = [System.Collections.Generic.List[Object]]::new() # Create output file
# Check Admin Roles if MFA not enabled
ForEach ($User in $MFAUsers) {
$Roles = $Null
If ($AppAdmins.PrincipalId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Application Administrator role” -ForegroundColor Red
$Roles = “Application Administrator” }
If ($AuthAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Authentication Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Authentication Administrator” } Else { $Roles = $Roles + “; Authentication Administrator” } }
If ($BillAdminRole.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Billing Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Billing Administrator” } Else { $Roles = $Roles + “; Billing Administrator” } }
If ($CloudAppAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Cloud application Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Cloud application Administrator” } Else { $Roles = $Roles + “; Cloud application Administrator” } }
If ($CondAccAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Conditional Access Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Conditional Access Administrator” } Else { $Roles = $Roles + “; Conditional Access Administrator” } }
If ($ExchangeAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Exchange Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Exchange Administrator” } Else { $Roles = $Roles + “; Exchange Administrator” } }
If ($GlobalAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Global Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Global Administrator” } Else { $Roles = $Roles + “; Global Administrator” } }
If ($HelpdeskAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Helpdesk Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Helpdesk Administrator” } Else { $Roles = $Roles + “; Helpdesk Administrator” } }
If ($PasswordAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Password Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Password Administrator” } Else { $Roles = $Roles + “; Password Administrator” } }
If ($PrivAuthAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Privileged authentication Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Privileged authentication Administrator” } Else { $Roles = $Roles + “; Privileged authentication Administrator” } }
If ($PrivRoleAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Privileged role Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Privileged role Administrator” } Else { $Roles = $Roles + “; Privileged role Administrator” } }
If ($SecurityAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the Security Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “Security Administrator” } Else { $Roles = $Roles + “; Security Administrator” } }
If ($SharePointAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the SharePoint Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “SharePoint Administrator” } Else { $Roles = $Roles + “; SharePoint Administrator” } }
If ($UserAdmins.ObjectId -Contains $User.ObjectId) {
Write-Host $User.DisplayName “Account holds the User Administrator role” -ForegroundColor Red
If ($Roles -eq $Null) { $Roles = “User Administrator” } Else { $Roles = $Roles + “; User Administrator” } }
If ($Roles -ne $Null) {Write-Host “User” $User.DisplayName “is assigned the following roles:” $Roles -ForeGroundColor Yellow; $i++
$ReportLine = [PSCustomObject]@{
User = $User.DisplayName
UPN = $User.UserPrincipalName
Roles = $Roles
MFA = $User.MFAUsed }
$Report.Add($ReportLine) } #End if
}
Write-Host “All done.” $i “privileged accounts found which aren’t protected by MFA – see C:\temp\MFAReport.CSV for details”
$Report | Out-GridView
$Report | Export-CSV -NoTypeInformation C:\temp\MFAReport.CSV
# An example script used to illustrate a concept. More information about the topic can be found in the Office 365 for IT Pros eBook https://gum.co/O365IT/
# and/or a relevant article on https://office365itpros.com or https://www.petri.com. See our post about the Office 365 for IT Pros repository # https://office365itpros.com/office-365-github-repository/ for information about the scripts we write.
# Do not use our scripts in production until you are satisfied that the code meets the need of your organization. Never run any code downloaded from the Internet without
# first validating the code in a non-production environment.
Maybe the script at https://office365itpros.com/2022/07/06/azure-ad-admin-roles-no-mfa/ might be a better start. It uses the Graph SDK instead of the older MSOL and Azure AD modules, which are in the process of being deprecated by Microsoft.
If you know an account that has an admin role and isn’t protected by MFA, you could run the code just for that account to see what it generates. I often do this when I’m creating a loop and want to be sure that each item in the loop will be processed successfully.
I’m coming at this from a different angle. Not so much looking at the MFA enablement/enforcement on the account level but looking at conditional access policies to see what accounts have MFA enforced and under what conditions. I know we can just look at the policies, but as you grow more and more policies, and your conditions/exclusions get complex it would be nice to be able to spit out a report which shows that you have certain accounts excluded, which you might expect (failsafe/break glass), but some others which you might not expect. For example, you add a user to an exclude group temporarily or for testing and forget to remove it.