Reporting on user’s Administrative unit membership in Azure AD

As Microsoft is slowly ramping up the Azure AD RBAC story, I expect usage of Administrative units to gain more traction, which in turn means organizations will need an easy way to report on AU usage. Sure, you can readily get the list of AUs to which a given user has been added to from the Azure AD blade, or similarly, get a list of all members of a particular AU. However, doing this for a thousand users via the UI is a no go, so we need a programmatic solution.

In fact, we have few such solutions. Let’s start with good old Exchange Online PowerShell – you can use the user object’s AdministrativeUnits property to obtain the list of AUs, i.e.:

Get-User vasil | select -ExpandProperty AdministrativeUnits

From here’s it’s fairly easy to automate this across all users:

Get-User -ResultSize Unlimited | select UserPrincipalName,AdministrativeUnits

where you can expose additional properties as needed and you can even use the Get-AdministrativeUnit cmdlet to replace the GUIDs with human-readable values. However, the cmdlet offers only a limited set of properties on the AU object, most importantly it doesn’t give us information about membership type.

Another set of cmdlets we can use come from the MSOnline module, but they also return insufficient details. The set of AU cmdlets within the AzureAD/AzureADPreview module goes a step further, but still has some limitations. Finally, we end up with the Graph API, and it’s bastard child, the Microsoft Graph API SDK for PowerShell, both of which offer the most extensive support of endpoints/methods for the scenario at hand. As with most things Graph, you will need to have an app with the required permissions (Directory.Read.All would do) before you start poking around.

Instead of going over each endpoint we can use to gather information on AUs, let’s just focus on the fastest/easiest method to do the task at hand. We need a list of users, and for each user we need a list of AUs he’s a member of. So, we need a query against the /users endpoint to give us the basic user information, and some way to fetch AU membership. This is where the /memberOf endpoint comes in, providing us with a quick way to list all directory objects for which a given user is considered a “member”. These include all group types recognizable by Azure AD, admin roles, and most importantly for our scenario, Administrative units.

Here’s where we can take another shortcut – instead of getting the full list of objects returned from the /memberOf endpoint, we can use the so-called OData cast to limit the output to just AU objects. To do so, we simply need to append the microsoft.graph.administrativeUnit string to our request. In other words, the query we will run looks something like this:


From here on, it’s just a matter of gathering the relevant details and preparing the output. The script will fetch a list of all user objects within the organization, then loop over each of them and run the query above. In an ideal world, we would’ve been able to fetch all this information via a single Graph API query, possibly via the use of the $expand operator. However, there are certain limitations (and bugs) with the current implementation of $expand, so my advice would be to stay clear of it. If nothing else, the limit of maximum of 2o entries returned should warn you off.

Now, we can technically obtain the same set of data even faster, by querying the list of AU objects and enumerating their membership, instead of doing things on a per-user basis. As with most of my script samples however, you should treat the code as a proof of concept, suitable to specific scenarios only. Yes, there are faster ways to generate the same output when you are interested in a tenant-wide inventory. The method used by the script however is much more suitable when you want to obtain the AU membership of a single user, or a group of users.

Without further ado, you can get the script from my GitHub repo. Make sure to replace the “connectivity” bits (lines 10-40) with your preferred method to obtain an access token. At the very least, replace the variables in lines 14-16, which are needed to obtain a token via the client credentials flow, the default one used by the script. Apart from that, you need to decide whether you want to run the script against all users in the tenant (default behavior when you specify no parameter), or against a subset of the users, in which case you need to provide a list of users for the –UserList parameter. The later accepts a list of UPNs or GUIDs, or you can even import a CSV file:

#provide a list of users via their identifier
.\AU_memberOf_inventory.ps1 -UserList "vasil","" ,"b0a68760-1234-1234-1234-8d1ae78f15dc"

#import the list of users from a CSV file
.\AU_memberOf_inventory.ps1 -UserList (Import-Csv .\testtttt.csv).UserPrincipalName

In the example above, the first value (“vasil”) will fail to be resolved against a matching user object and thus will be skipped. Make sure to provide a set of valid UPNs or GUIDs. If importing from a CSV file, make sure to pass an array of string values, not the full imported object. Lastly, here’s a sample of the script’s output:

AUmemberOfI specifically opted to use the “each AU membership on a new line” format, as opposed to the more “condensed” “one line per user” format, as it quickly allows you to filter the resulting CSV file not just on the user(s), but it also allows you to quickly list all members of a given AU, or even pinpoint users that are not a member of any AU. In any case, feel free to adjust the output as you see fit.

As usual, let me know if you run into any issues or have questions about the chosen method to generate AU membership. Remember that this is just a quick proof of concept script which lacks the touch and polish needed for a full blown production-ready solution! Especially when it comes to handling authentication. Proper error handling is another example of where the script should be improved. And, once Microsoft releases support for “nesting” AUs, you might consider replacing the /memberOf query with a /transitiveMemberOf one 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

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