A while back, I published a short article and script to illustrate the process of obtaining a list of all Azure AD role assignments. The examples therein use the old MSOnline and Azure AD PowerShell modules, which are now on a deprecation path. Thus, it’s time to update the code to leverage the “latest and greatest”. Quotes are there for a reason…
The updated script comes in two flavors. The first one is based on direct web requests against the Graph API endpoints and uses application permissions, thus is suitable for automation scenarios. Do make sure to replace the authentication variables, which you can find on lines 11-13. Better yet, replace the whole authentication block (lines 7-36) with your preferred “connect to Graph” function. Also make sure that sufficient permissions are granted to the service principal under which you will be running the script. Those include the Directory.Read.All scope for fetching regular role assignments and performing directory-wide queries, and the RoleManagement.Read.Directory for PIM roles.
The second flavor is based on the cmdlets included as part of the Microsoft Graph SDK for PowerShell. As authentication is handled via the Connect-MGGraph cmdlet, the script is half the size of the first one. And it would’ve been even smaller were it not for few annoying bugs Microsoft is yet to address.
In all fairness, switching to the Graph does offer some improvements, such as being able to use a single call to list all role assignments. This is made possible thanks to the /roleManagement/directory/roleAssignmentsendpoint endpoint (or calling the Get-MgRoleManagementDirectoryRoleAssignment cmdlet). Previously, we had to iterate over each admin role and list its members, which is not exactly optimal, and given the fact that the list of built-in roles has now grown to over 90, it does add up. On the negative side, we have a bunch of GUIDs in the output, most of which we will want to translate to human-readable values, as they designate the user, group or service principal to which a given role has been assigned, as well as the actual role. One way to go about this is to use the $expand operator (or the –ExpandProperty parameter if using the SDK) to request the full object.
While this is the quickest method, the lack of support for the $select operator inside an $expand query means we will be fetching a lot more data than what we need for the report. In addition, there seems to be an issue with the definition of the expandable properties for this specific endpoint, as trying to use the handy $expand=* value will result in an error (“Could not find a property named ‘appScope’ on type ‘Microsoft.DirectoryServices.RoleAssignment'”). In effect, to fetch both the expanded principal object and the expanded roleDefinition object, we need to run two separate queries and merge the results. Hopefully Microsoft will address this issue in the future (the /roleManagement/directory/roleEligibilitySchedules we will use to fetch PIM eligible role assignments does support $expand=* query).
Another option is to collect all the principalIDs and issue a POST request against the /directoryObjects/getByIds endpoint (or the corresponding Get-MgDirectoryObjectById cmdlet), which does have a proper support for $select. A single query can be used to “translate” up to 1000 principal values, which should be sufficient for most scenarios. With the information gathered from the query, we can construct a hash-table and use it to lookup the property values we want to expose in our report. Lastly, you can also query each principalID individually, but that’s the messiest option available.
Apart from role assignments obtained via the /roleManagement/directory/roleAssignments call, the script can also include any PIM eligible role assignments. To fetch those, invoke the script with the –IncludePIMEligibleAssignments switch. It will then call the /v1.0/roleManagement/directory/roleEligibilitySchedules endpoint, or similarly, use the Get-MgRoleManagementDirectoryRoleEligibilitySchedule cmdlet. Some minor adjustments are needed to ensure the output between the two is uniform, which includes the aforementioned issue with expanding the navigation properties. But hey, it wouldn’t be a Microsoft product if everything worked out of the box 🙂
And with that, we’re ready to build the output. Thanks to the $expand operator and the workarounds used above, we should be able to present sufficient information about each role assignment, while minimizing the number of calls made. The output is automatically exported to a CSV in the script folder, and includes the following fields:
- Principal – an identifier for the user, group or service principal to which the role has been assigned. Depending on the object type, an UPN, appID or GUID value will be presented.
- PrincipalDisplayName – the display name for the principal.
- PrincipalType – the object type of the principal.
- AssignedRole – the display name of the role assigned.
- AssignedRoleScope – the assignment scope, either the whole directory (“/”) or a specific administrative unit.
- AssignmentType – the type of assignment (“Permanent” or “Eligible”).
- IsBuiltIn – indicates whether the role is a default one, or custom-created one.
- RoleTemplate – the GUID for the role template.
Now, it’s very important to understand that this script only covers Azure AD admin roles, either default or custom ones, and optionally eligible PIM-assignable roles. Apart from these, there are numerous workload-specific roles that can be granted across Office 365, such as the Exchange Online Roles and assignments, Roles in the Security and Compliance Center, site collection permissions in SharePoint Online, and so on. Just because a given user doesn’t appear in the admin role report, it doesn’t mean that he cannot have other permissions granted!
In addition, one should make sure to cover any applications (service principals) that have been granted permissions to execute operations against your tenant. Such permissions can range from being able to read directory data to full access to user’s messages and files, so it’s very important to keep track on them. We published an article that can get you started with a sample script a while back.