Exchange Server and its cloud sibling are well-known for their robust administrative controls, powered by the RBAC model. Designed with great attention to detail, the RBAC model allows you to granularly control what each admin or user can do, down to the individual cmdlet or even parameter level. While this is extremely useful, because of the flexibility of the model, sometimes it can be a bit hard to keep track of just what permissions have been granted in the organization.

To address this issue, you can use PowerShell to prepare detailed reports of the roles assigned. One such example script is provided by Paul here at Practical 365, and many admins have already made a habit of running it periodically against their environments. The script, however, relies on the AD PowerShell cmdlets and as such cannot be run against Exchange Online, so in this article, we will introduce an updated/alternative approach. We will also discuss some potential improvement areas, and of course, provide you with the actual solution.

A quick refresher on Exchange RBAC

As the name suggests, Exchange’s Role-based Access Control (RBAC) permission model has management roles as its building blocks. A role represents a set of tasks or cmdlets, granted to a role assignee. The role assignee can be a user, a security group or a role group (or a role assignment policy, which we don’t cover here). The link between the role and its assignees is called a management role assignment. Role assignments, in turn, can be regular or delegating. The former is used when you grant someone access to the management tools and features. On the other hand, delegating role assignments don’t grant access to the management tools, but are used to grant someone the ability to assign a given role to others. Lastly, we have the concept of management scopes, which can be used to limit the effect of the role to a subset of the objects in your organization.

While this all might sound very confusing at first, once you understand the basic principles behind the RBAC model, you will certainly appreciate its robustness and customizability. Even more so if you come to compare it with other workloads such as SharePoint or Skype for Business. In Exchange Online, the RBAC model is a bit more limited compared to on-premises versions, but it’s still well ahead of what we get in other parts of the service, including Azure AD.

How to find out who has permissions

Just because some admin functionality exists, it doesn’t mean it’s in use. The same goes for any Exchange management roles. Role assignments effectively represent the “link” between a role and the security principals who have been granted permissions to access the cmdlets, scripts or tools available as part of the role. If no role assignment exists for a given role, then no one in the organization has access to the management tasks it enables. Generally speaking, you can omit such roles from your report. But if you want to be thorough, or are just curious, here’s how to list any (admin) roles that have no corresponding role assignments:

Get-ManagementRole | ? { !(Get-ManagementRoleAssignment -Role $_.Guid.Guid) -and !$_.IsEndUserRole }

In the above, we are listing all roles, then for each role checking whether any role assignments exist. We are also excluding any “end user” roles, such as the ones that govern different OWA features. The resulting list will return just the “unassigned” roles if any. Again, as no one is currently assigned to said roles, they are not “in use” and you can simply ignore them. The bottom line here is that if you want to get an overview of what permissions are granted in the company at present, the Get-ManagementRoleAssignment cmdlet is your natural starting point.

We now know how to list assignments for a given role. However, this doesn’t completely answer the question of who has been granted access, as roles can be delegated to security groups, or bundled in role groups and role assignment policies. If we want to list the individual users, we have to further “expand” the corresponding object, which is effectively what Paul’s script does for Role Groups. We are trying to address a broader scenario, however, so we need to also account for assignments of type User, SecurityGroup and PartnerLinkedRoleGroup. In turn, where needed we should aim to expand group membership and list individual users.

In the on-premises world, group expansion is easily performed via the AD tools. In Office 365, however, things aren’t that straightforward, and in general, we have to use solutions such as the one described in this article. Luckily, in the case of role assignments, the good folks at Microsoft have given us an easier method, namely the -GetEffectiveUsers switch parameter. What the switch does is to recursively process each nested group and return a list of the individual user entries. Here’s an example:

The first cmdlet lists just the direct assignees to the Journaling role. Three of those entries, namely the “Compliance management” and “Organization management” role groups and the “MESGtop” security group have additional members and even some nested groups. The full list of members is returned when we rerun the cmdlet with the –GetEffectiveUsers switch, one entry per user. Which is practically all we need to generate the report.

Another approach would be to use the Get-ManagementRoleAssignment cmdlet against specific security principal entry. The cmdlet is intelligent enough to not only list direct assignments, but also any assignments corresponding to security or role groups that have the given security principal as a member, including nested groups. However, since the number of such objects in most companies is larger than the number of role assignments, this method might be slower. Just in case, here’s an example:

Preparing the output

Now that we know how to list each individual user and each role they have been assigned, all we need to do is prepare the output. One approach is to just dump all this information into a CSV file, then use Excel to filter/sort it as needed. However, as you can see from the above screenshot, some of the data presented is quite ambiguous as objects are listed by their display name. We can improve the quality of the output by finding the corresponding object and return a unique identifier. Unfortunately, this might not always be possible, but for most scenarios, we should be able to identify the correct object.

The getUPN and getGroup helper function will try to “convert” a given DisplayName to the corresponding object and return a unique identifier for it. Since there can be multiple entries matching a given display name, the functions will call the Get-ManagementRoleAssignment and use the –Role and –RoleAssignee parameters to find out which entry corresponds to the actual role assignment. If they cannot find a match or multiple matches remain even after these additional checks, the display name will be returned as is.

Apart from providing additional information about each assignee, we can also use the native PowerShell capabilities to group the output by user. This might or might not be more convenient, but as mentioned above you can also use tools such as Excel to perform any grouping, sorting and filtering operations. The output returned from the script is sampled below:

For each user, group, or role group object, the object type, and unique identifier are returned (UPN or PrimarySMTPAddress where available, GUID otherwise). Then, a list of the individual Roles assigned to the given security principal is listed, and an indicator of the type of assignment. For objects with multiple roles assigned, we can have multiple values for the assignment type. Because we are grouping results, only unique role entries will be returned, if that’s not what you expect, edit the code on line 161.

Additional parameters

The script features a few additional parameters that govern the output. The first parameter is –IncludeDelegatingAssignments, which signals the script to also return details on any non-default Delegating Role Assignments. By non-default we mean delegating role assignments outside of those assigned to the “Organization Management” role group. If you don’t agree with the logic used here, feel free to adjust the filter on line 79.

The –IncludeRoleGroups parameter will force the script to return entries for “parent” role group assignments in the output. Since we are using the –GetEffectiveUsers parameter, any individual user that’s a direct member of the role group or member of any nested group within it is returned, so you might not want to see the “parent” entry as well. This is the default behavior, but if you do want to see such entries, you can specify the –IncludeRoleGroups parameter when invoking the script. No entries will be returned in the output for any nested groups, just the user objects within them! This is a limitation of using the –GetEffectiveUsers switch, but we plan to present an alternative solution in a future article.

Lastly, the –IncludeUnassignedRoleGroups switch governs whether to return entries for role groups that don’t have any role assignments. As discussed above, any cmdlets included in such role groups are effectively in “no use”, but you might want to include the role groups for completeness. This parameter can only be used when –IncludeRoleGroups is set to $true.

Here’s how the console output will look like when all these parameters are used. In the highlighted entries, you can see an object with a duplicate display name, but thanks to the additional checks performed by the script, a unique identifier is returned. You can also see users and groups with direct role assignments, including delegating ones; role groups with no assignments; “parent” groups.

The output will also be stored in the global variable $varRoleAssignments so it can be easily reused. Don’t forget that the script will also save the “raw” output to a CSV file!

Download the script from the TechNet Gallery or GitHub, and don’t forget to send us your comments.

About the Author

Vasil Michev

Vasil Michev is an Office Servers and Services MVP, specializing in Office 365. He's currently employed as a Technical Product Manager, and in his free time he can be found helping others in the Office 365 community.


  1. imran

    Could you please test it in your environment and let me know if it works for you ?

    1. imran

      Forget this i have achieved with using parameters , but my next question is, the output of the Assignee is with ID instead of username, how can i get this with username ?

      1. Avatar photo
        Tony Redmond

        what kind of ID? A GUID? Can you do Get-Recipient -Id $Value to see what $Value (the identifier) resolves to?

  2. imran

    this code doesn’t return any output. What could be the reason ?
    Thanks in advance.

    1. Avatar photo
      Tony Redmond

      No idea. Have you debugged it? The code is PowerShell after all, so you can step through it to discover where problems might lie.

    2. Avatar photo
      Tony Redmond

      One basic problem I see is that the script in GitHub attempts to connect to Exchange Online via basic auth. Have you tried changing the script so that it uses a simple Connect-ExchangeOnline instead of running the Check-Connectivity function?

Leave a Reply