How to List Hidden Group Memberships with the Graph

Administrative Interfaces Can List Hidden Group Memberships, but Graph-Based Apps Need Extra Permission

A user of the Microsoft 365 Groups and Teams activity report script (which I should do some work on to upgrade some really old code) pointed out that they weren’t getting details of groups with hidden membership. I’ve written about groups with hidden membership before and observed that administrative interfaces like the Microsoft 365 admin center or Entra admin center have access to hidden membership (Figure 1) where user-facing clients like Outlook block access to hidden group memberships.

The Entra admin center reveals the hidden membership of a Microsoft 365 group .

Hidden group memberships.
Figure 1: The Entra admin center reveals the hidden membership of a Microsoft 365 group

PowerShell modules built for administrative use also count is administrative interfaces, so cmdlets like Get-UnifiedGroupLinks from the Exchange Online management module report hidden memberships as happily as they list open memberships. Modules like Exchange Online assume that anyone running their cmdlets is an administrator, so they extend the same access to data that the administrator enjoys through an admin portal.

Listing Hidden Group Memberships is Different with the Graph

The Graph API is different. Working on a least permission model, the Graph makes no assumptions about permissions when a session starts and the only access to data available during the session is via granted permissions. The permissions can be delegated (access to data available to the signed-in user) or application (available to tenant data). Delegated permissions are used for interactive sessions. Application permissions are used by apps (which can run in interactive sessions).

When the problem was first reported, I did a quick check but couldn’t find anything wrong. But I screwed up by running commands in an interactive session signed in with an account that held the Exchange administrator role. Interactive sessions use delegated permissions, but they also respect any of the administrative roles assigned to the account, so while the Get-MgGroupMember cmdlet would normally stop me seeing the members of a group with hidden membership, the Exchange administrator role removed the block and made the membership visible.

This experience proves once again that any testing for a Graph access scenario should be done with application permissions. In other words:

  • Create an app (registration).
  • Assign the app the lowest possible level of permissions you think are needed to get the job done.
  • Test.
  • Refine as necessary using different permissions until the test is successful.

Fetching Hidden Group Membership

In this scenario, I started off with an app with consent to use the Group.Read.All and User.Read.All permissions. The former is needed to read group details; the latter to retrieve member information (user accounts). I then disconnected my current interactive Microsoft Graph PowerShell SDK session and signed in with the app, using the thumbprint of an X.509 certificate uploaded to the app to authenticate. Running Get-MgContext confirmed the available permissions (scopes):

Connect-MgGraph -Tenantid $TenantId -AppId $AppId -CertificateThumbprint $Thumbprint
(Get-MgContext).Scopes | Format-List
User.Read.All
Group.Read.All

Now attempt to read the membership of a group with hidden membership. The Get-MgGroupMember cmdlet returns nothing, but we know why because the visibility property of the group is set to HiddenMembership. A group might have no members, but if its visibility property is set to HiddenMembership, there might be data to retrieve,

Get-MgGroupMember -GroupId $GroupId

Get-MgGroup -GroupId $GroupId | Select-Object Visibility

Visibility
----------
HiddenMembership

The Visibility property is most often used to note whether a group has private or public membership. Unfortunately, it’s not a filterable property for the Graph, so to find the groups with hidden membership, you do something like this:

[array]$Groups = Get-MgGroup -All -PageSize 500
$Groups | Where-Object {$_.Visibility -eq 'HiddenMembership' } | Format-Table DisplayName, Id, Visibility

To find details of the hidden membership, grant consent for the app to use the Member.Read.Hidden permission. Disconnect and reconnect using the app and make sure that Member.Read.Hidden is available. Now run Get-MgGroupMember again:

[array]$Members = Get-MgGroupMember -GroupId $GroupId

Id                                   DeletedDateTime
--                                   ---------------
eff4cd58-1bb8-4899-94de-795f656b4a18
cad05ccf-a359-4ac7-89e0-1e33bf37579e
08dda855-5dc3-4fdc-8458-cbc494a5a774
75ba0efb-aed5-4c0b-a5de-be5b65187c08
4daa5f06-55eb-4d79-9a24-1be369919fec
59e09287-ac1b-4ff7-80a3-08d0d1eed939

Or to see the display names of the members:

$Members.additionalProperties.displayName
Tony Redmond
James Ryan
Sean Landy
Terry Hegarty
Otto Flick
Hans Geering (Project Management)

If an app has a higher-level permission, such as Directory.Read.All, it can also read hidden membership. The same permission governs access to hidden memberships of Entra ID groups and administrative units.

The Takeaway about Graph Permissions

The takeaway here is not to assume anything about Graph permissions. The ability of the Microsoft Graph Command Line Tools app (used for Microsoft Graph PowerShell SDK interactive sessions) to accrue delegated permissions over time is both a benefit and a problem. When you can, use app-only mode to validate exactly what permissions are required to run your code.


Need some assistance to write and manage PowerShell scripts for Microsoft 365? Get a copy of the Automating Microsoft 365 with PowerShell eBook, available standalone or as part of the Office 365 for IT Pros eBook bundle.

2 Replies to “How to List Hidden Group Memberships with the Graph”

  1. Didn’t know that the characters from the BBC-Sitcom “allo allo” (like Otto Flick or Hans Geering) are working for Tony Redmond’s enterprise 😉

Leave a Reply

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