In the past, I’ve written about the importance of standardizing the Azure AD account creation process so that account properties end up being fully populated with properties like department, manager, and so on. Although it’s relatively easy to make sure that account properties are accurate when created, over time, it’s almost inevitable that inaccuracies will creep in unless steps are taken to validate and correct errors in Azure AD user account manager links. PowerShell is very useful in this respect, as scripts to identify account data issues are easy to write.

Manager-employee relationships are possibly the hardest updates to manage. Often changes in reporting lines happen without any knowledge of the IT department. These are HR matters, and unless HR informs IT about the need to update Azure AD, the directory remains untouched and gradually descends into an inaccurate state.

The reason why directory accuracy matters is that Microsoft 365 highlights organization charts in several places such as Teams (Figure 1), the Microsoft 365 user profile card, and the organization explorer in Outlook for Windows (requires a Viva Suite license).

Organization chart based on Azure AD manager-employee links shown in Teams
Figure 1: Organization chart based on Azure AD manager-employee links shown in Teams

Obviously, if Azure AD holds inaccurate information about reporting relationships, the charts generated by applications will be misleading. That’s a problem which should be fixed.

Large organizations tend to have sophisticated HR systems that might include a feed to Azure AD to handle changes like reporting relationships, departments, telephone numbers, physical locations, and so on. But let’s pretend that our organization doesn’t possess such a feed. Instead, we need to come up with a way to replicate what might happen if an HR feed was available.

Defining a Possible Solution

Sketching out the kind of solution we might develop, I came up with the following:

  • HR provides regular updates for information about managers and their departments, including the level 2 manager for each department. For this exercise, we’ll use a CSV file to hold this data.
  • A PowerShell script reads the HR information and checks each user account to update the manager based on the department assigned to the account. If the user is the department manager, the script updates the account with the level 2 manager. Because the need exists to move away from the old Azure AD and MSOL modules, we’ll use cmdlets from the Microsoft Graph PowerShell SDK.

Simple. The script could run interactively, but in a production environment, it’ll be more likely to run the script on a scheduled basis using something like an Azure Automation runbook.

A commercial directory maintenance solution like LiveTiles Directory (which used to be  known as Hyperfish) will do the job more elegantly and comprehensively, but it’s more fun to code up a homemade solution. You can download the script I wrote from GitHub. As always, the scripts we write are to illustrate principles rather than being fully-functional solutions. Please use the script as you like.

Scripting the Solution

The example script goes through several phases to find user accounts, figure out the right department manager to assign, and eventually update the accounts. Here’s an overview of what happens.

First, the script reads the information about department managers from HR. Because the data is in a CSV file, the Import-CSV cmdlet is all that’s needed. However, the script might have to process thousands of employees, so fast access to the data is needed. For this reason, the script loads the department name and manager (user principal name) into a hash table. If the script detects any errors in the management data, it flags an error and stops.

Next, the script finds the set of Azure AD user accounts to process, Azure AD user accounts aren’t always associated with real people who might have a manager, so to exclude accounts used for things like room and shared mailboxes and guest accounts, the script uses an advanced Graph query against Azure AD with the Get-MgUser cmdlet to find accounts with at least one assigned license. Filtering the output of Get-MgUser like this is a very useful command that you’ll probably end up using in many scripts:

Write-Host "Finding licensed Azure AD accounts..."
[array]$Users = Get-MgUser -All -Filter "assignedLicenses/`$count ne 0 and userType eq 'Member'" -ConsistencyLevel eventual -CountVariable UsersFound | Sort-Object DisplayName
If (!($Users)) { Write-Host "Couldn't find any user accounts with assigned licenses!" ; break }

After fetching the set of user accounts, the script splits accounts into those with a department property filled and those without. Obviously, the script can only process accounts with departments because it uses that property to find the manager. At the same time, realizing that life is imperfect, the script reads in a default manager to use when it can’t match a department:

[array]$UsersNoDepartment = $Users | Where-Object {$Null -eq $_.department}
[array]$UsersWithDepartment = $Users | Where-Object {$Null -ne $_.department}
# Get default manager to assign people to when we can't find a department manager
$DefaultManager = $ManagerData | Where-Object {$_.Department -eq "Default" } | Select-Object -ExpandProperty Manager

The next part of the script focuses on figuring out what manager to assign to a user account. If the lookup against the hash table using the department value for the user account succeeds, we know what manager to assign. Some conditional processing handles the situation where a user is the department manager. This code exists because Microsoft 365 allows a user to be their own manager, something that doesn’t happen too often in real life. Another check exists for the CEO account, who doesn’t have a manager because they’re at the top of the organization.

After determining the manager to assign to a user account, the script checks if the manager is already present for the account. To do this, the script runs the Get-MgUserManager cmdlet and compares the value of the user principal name for the manager against the user principal name for the current department manager. If the two match, the script won’t attempt to update the account.

$UserManager = (Get-MgUserManager -UserId $User.Id -ErrorAction SilentlyContinue).additionalProperties.userPrincipalName
 If ($DepartmentManager -eq $UserManager) {
    # Same manager, so don't update
    $DepartmentManager = $Null }

The final step is where the script calls the Set-MgUserManagerByRef cmdlet to update the account. The cmdlet operates by reference, meaning that the input references another object (the department manager). The cmdlet accepts the target object identified by its object identifier or user principal name (as used in this example). Here’s what the code does:

Write-Host ("Updating user account {0} with manager {1}" -f $User.displayName, $DepartmentManager)
$Id = ("https://graph.microsoft.com/v1.0/users/{0}" -f $DepartmentManager) 
Set-MgUserManagerByRef -UserId $User.Id -AdditionalProperties @{ "@odata.id" = $Id }

As an output, the script generates a report about the management updates it processes. The script exports the output as a CSV file and displays details via the Out-GridView cmdlet (Figure 2).

Manager assignments made by the script

Azure AD User Account manager
Figure 2: Manager assignments made by the script

The script also notes if it finds any accounts that don’t have a department property.

A Little More Complex Than Anticipated

When I started to write the example script, I thought that the code would end up in something like 40 lines. The final script is about double that (without any error handling), which again proves my incompetence when estimating tasks. Knowing how to handle management assignments with PowerShell is a good thing. Apart from anything else, this exercise demonstrates the use of the Get-MgUserManager and Set-MgUserManagerByRef cmdlets which you might need to upgrade scripts that use the eventually-to-be-deprecated AzureAD and MSOL modules.

Microsoft Platform Migration Planning and Consolidation

Minimize the risk, time, cost, and complexity associated with your next Exchange Online Domain Transfer

About the Author

Tony Redmond

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He is the lead author for the Office 365 for IT Pros eBook, the only book covering Office 365 that is updated monthly to keep pace with change in the cloud. Apart from contributing to Practical365.com, Tony also writes at Office365itpros.com to support the development of the eBook. He has been a Microsoft MVP since 2004.

Comments

  1. Arvind Suthar

    Will this work if the users are provisioned through Azure AD Connect?

    1. Avatar photo
      Tony Redmond

      User accounts provisioned through AAD Connect are ‘mastered’ on-premises. Therefore, any changes made to their reporting relationships must be made in Active Directory and synchronized back to Entra ID.

Leave a Reply