How to Find Unprotected Azure Active Directory Administrative Accounts

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.

Azure Active Directory Usage and Insights Report about MFA and SSPR

Find unprotected Azure AD accounts
Figure 1: Azure Active Directory Usage and Insights Report about MFA and SSPR

We discussed how to create a report of Azure AD accounts and their MFA status in a previous post and we can build on the techniques explored there to construct a PowerShell script to report accounts holding an administrative role that need to be protected with MFA. You can grab a copy of the script from GitHub.

What the Script Does

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.

Write-Host "Finding Azure Active Directory administrative roles..."
$UserAccountAdmin = Get-AzureADDirectoryRole | Where-Object {$_.DisplayName -eq ‘User Account Administrator’} | Select ObjectId
$TenantAdmin = Get-AzureADDirectoryRole | Where-Object {$_.DisplayName -eq ‘Global Administrator’} | Select ObjectId
$TeamsAdmin = Get-AzureADDirectoryRole | Where-Object {$_.DisplayName -eq ‘Teams 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

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.

Viewing details of Azure AD accounts with administrative roles which are not MFA-protected
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.

Viewing details of unprotected accounts through Out-GridView
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…

9 Replies to “How to Find Unprotected Azure Active Directory Administrative Accounts”

  1. 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.

    1. The script does filter for licensed member accounts… But because it’s PowerShell, you can do what you like to amend it.

  2. 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

    1. Thanks Paul… Your suggestions are good ones because the GUIDs could change from tenant to tenant.

  3. 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.

    1. 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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.