Generating a report of users’ group membership (MemberOf inventory) V2

Time to update another one of my sample scripts to take advantage of the goodness the Exchange Online V3 PowerShell module provides. This time, we will be taking on the “memberOf inventory” script, or in other words the script that provides a list of all Exchange groups a given user is a member of, across all users within the organization or a subset of them. As this is an Exchange-based script, only group objects recognized by Exchange Online will be included: distribution groups, mail-enabled security groups and Microsoft 365 Groups. It does not include Azure AD (pure) security groups.

Similarly, the set of users for which the members query is executed is based on the information available within ExODS. In other words, only users that are recognized as valid Exchange recipients will be returned. Since we’re only covering membership to Exchange-related groups anyway, this is only natural, although there are some corner cases where a user, technically not recognized as a valid recipient by Exchange Online, can still appear within group membership. This is one of the improvements made in the “V2” version of the script – it will now include all Exchange Online user objects with an RecipientTypeDetails value of User. A set of switch parameters covers each individual recipient type, and you can toggle them as you see fit:

  • IncludeUserMailboxes – this is the default value, used when you run the script without providing any parameters.
  • IncludeSharedMailboxes – use this switch to include user objects corresponding to Shared mailboxes in the output.
  • IncludeRoomMailboxes – use this switch to include user objects corresponding to room, equipment and booking mailboxes.
  • IncludeMailUsers – use this switch to include mail user objects.
  • IncludeGuestUsers – use this switch to include Guest users (which are a special type of mail users).
  • IncludeMailContacts – although technically not a user object, sometimes it can be useful to also get inventory of which groups your Mail Contacts have been added to.
  • IncludeUsers – use this switch to include the aforementioned non-recipient User objects.
  • IncludeAll – use this switch to include all of the above.

As a side note, and with the hope to better illustrate the logic introduced above, here’s how to get the full set of users within your Exchange Online environment.

Get-User | group RecipientTypeDetails -NoElement | sort Count -Descending

Count Name
----- ----
16 UserMailbox
13 SharedMailbox
10 GuestMailUser
9 User
6 MailUser
5 SchedulingMailbox
4 RoomMailbox
2 DiscoveryMailbox
1 TeamMailbox
1 EquipmentMailbox

With the newly introduced parameters, we’re accounting for all of these user object types, apart from DiscoveryMailbox and TeamMailbox. Those last two are (mostly) a thing of the past now, and not something you would want to include in a report anyway.

As before, the script will use a server-side filter to return a list of all Recipient, for which the given user is a member. This is done via the Get-Recipient cmdlet and a filter based on the Members query, run against the object’s DistinguishedName value. In other words, for each user we run the following:

Get-Recipient -Filter "Members -eq '$dn'" 

where the $dn variable holds the (sanitized) value of the DistinguishedName property. With the V3 module and the InvokeCommand method, we get most of the benefits of the REST-based cmdlets, so performance-wise the script should fare much better. Even better performance would be possible by utilizing the Get-EXORecipient cmdlet instead, which allows us to limit the output to just select properties. However, the REST-based cmdlets continue to have issues with handling special characters, such as the “#” character found in the DistinguishedName value of every GuestMailUser object, so using the Get-Recipient cmdlet is an acceptable tradeoff.

The connectivity part of the script has also changed. It now explicitly checks for the presence of the REST-based cmdlets, as a way of detecting V2/V3 version of the module. Even though you can still obtain the same data via good old Remote PowerShell Session connectivity, it’s time to move forward. This also means that the new version of the script will not run against on-premises installs, unless you modify the Check-Connectivity function.

Few small changes have been made to the output as well. First, to accommodate for the non-recipient User  objects, which do not have a PrimarySMTPAddress, the output will use an “Identifier” column now. The value of the Identifier will depend on the object type – mailboxes will show the Primary SMTP address, mail users will show the External Email Address and non-recipient users will show the ExternalDirectoryObjectId instead. And to make things a bit clearer, we’ve also added the RecipientTypeDetails column to the output. Output will be exported to a CSV file in the working directory by default, and also stored in the global variable $varDGMemberOf for reuse.

And with that, you should be ready to try the new version. You can download the script from GitHub here:

To run the script against all regular users (users with mailboxes) and Guest users within your tenant, use the following syntax:

.\DG_MemberOf_inventoryV2.ps1 -IncludeGuestUsers -IncludeUserMailboxes

To include non-recipient users, use the –IncludeUsers switch:

.\DG_MemberOf_inventoryV2.ps1 -IncludeUsers

To include all supported user types, use the –IncludeAll switch:

.\DG_MemberOf_inventoryV2.ps1 -IncludeAll

Before closing, it’s worth mentioning one more time that this script only includes objects recognized by Exchange Online, thus it does not always provide the full picture. You might want to run a similar exercise against Azure AD, by leveraging the MemberOf/transitiveMemberOf endpoint. I’ll likely do a sample script on that in the coming days/weeks.

As always, do let me know if you run into any issues with the script or have comments on it.

3 thoughts on “Generating a report of users’ group membership (MemberOf inventory) V2

  1. Andrew McCalla says:

    Hey there. This is a super cool script. If I only wanted to run this against specific users, how would I accomplish that?

    1. Vasil Michev says:

      Replace the $MBList variable to only include the user in question, for example something like this:

      $MBList = Get-Recipient | Select-Object -Property $props

      You can add it on say line 102.


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.