A Choice of Methods Exist to Find Groups with Guest Members

Much as I liked the Graph APIs, sometimes they aren’t the right choice for a job. Take the example of a reader who wanted to find all the Microsoft 365 groups in the tenant with guest members. They wanted to use the Graph APIs to filter groups based on membership but found that this isn’t possible. Instead, the kind of code needed to do the job would:

  • Find all guest accounts.
  • For each guest account, find the groups they belong to.
  • Report the groups with guests.

This sounds straightforward. The only issue is the time needed to interrogate Azure AD to find the set of groups for each guest account, followed by a call to Get-MgGroup to retrieve the display name because Get-MgUserMemberGroup returns only a set of group identifiers instead of full group details. In any case, here’s what I came up with:

[array]$GuestAccounts = Get-MgUser -All -Filter "UserType eq 'Guest'" | Select -ExpandProperty Id
[array]$AllGroups = $Null
ForEach ($Guest in $GuestAccounts) {
   [array]$Groups = Get-MgUserMemberGroup -UserId $Guest -SecurityEnabledOnly:$false 
   $AllGroups += $Groups }
$GraphGroups = $AllGroups | Sort -Unique
ForEach ($Group in $GraphGroups) { 
    $G = Get-MgGroup -GroupId $Group 
    If ($G.GroupTypes -like "*Unified*") {
       Write-Host ("Microsoft 365 Group {0} found" -f $G.DisplayName) }
}

Slow but Working Code

The code works, but it’s slow. In my tenant with 170 guest accounts and 217 Microsoft 365 Groups, the code took just over 10 seconds to find the 12 groups with guest members. That’s OK for a one-off script, but we can do better in terms of performance and functionality.

The immediate problem is the lack of filtering available for the Microsoft Graph PowerShell SDK cmdlets to find groups with guests. This forces the need to get all guests and then find the groups they belong to. Another complexity is that Get-MgUserMemberGroup returns all types of groups that a guest can belong to. In most cases, these are Microsoft 365 groups, but you can add a guest account to a distribution list, so a further check is necessary to ensure that we only process Microsoft 365 groups.

Using Get-UnifiedGroup to Find Groups with Guests

However, a suitable filter is available for the Get-UnifiedGroup cmdlet. This isn’t a Graph cmdlet, but it proves that often multiple ways exist to solve problems in Microsoft 365. In fact, the solution is very easy with Get-UnifiedGroup because Exchange Online tracks the numbers of guests in each group in a filterable property, which means that we can use a server-side filter to find the set of groups with guest members:

[array]$ExoGroups = Get-UnifiedGroup -Filter {GroupExternalMemberCount -gt 0}

The command ran in under two seconds, so that’s a nice improvement.

Detecting Groups That Shouldn’t Have Guest Members

We now know what Microsoft 365 groups have guest members. That’s interesting information, but we can use the data to check if any guests are in groups that have sensitivity labels which block guest access. If an administrator assigns a sensitivity label that blocks guest access to a group, the sensitivity label prevents the addition of new guest members but has no effect on existing guests. These members continue to enjoy full access to the group’s resources. The fact that these guests remain creates an inconsistency that deserves a review to decide if the label is incorrect or the guests should be removed.

To check the set of groups that we identified, we need to know which sensitivity labels have container management settings (to control Teams, Groups, and Sites) that prohibit guest members. To do this:

  • Run the Set-Label cmdlet to find all labels.
  • Find the set with container management settings.
  • Filter for the labels that block guest access.

This code does the job and creates two hash tables: one with the set of container management labels, the other with the labels that allow guest access.

Connect-IPPSSession
[array]$Labels = Get-Label
$ContainerLabels = @{}
$LabelsAllowingGuests = @{}
ForEach ($Label in $Labels) { # Find out which labels apply blocks to guest users
       $LabelGuestAccess = $True
       $LabelActions = $Label.LabelActions | ConvertFrom-Json      
       ForEach ($LabelAction in $LabelActions) {
          If (($LabelAction.Type -eq "protectgroup") -and ($Label.ContentType -Like "*Site*")) {
             $Settings = $LabelAction.Settings
             $ContainerLabels.Add([String]$Label.ImmutableId, $Label.DisplayName)
             ForEach ($Setting in $Settings) {
                If ($Setting.Key -eq "allowaccesstoguestusers" -and $Setting.Value -eq "true") {
                  $LabelsAllowingGuests.Add([String]$Label.ImmutableId, $Label.DisplayName)}}}
}}
Write-Host “The following labels allow guest access to Groups, Teams, and Sites”
$LabelsAllowingGuests | Format-Table Value

With the hash tables populated, we can check the sensitivity label assigned to each group to ensure that it’s consistent. If a group has a label that doesn’t allow guests, we flag the group for an administrator to decide if guests should stay or be removed. If the decision is to allow guests in a group’s membership, it would be best to assign a sensitivity label that permits guest access.

ForEach ($Group in $ExoGroups) {
   $CheckLabel = $LabelsAllowingGuests.Item($Group.SensitivityLabel.Guid)
   # Write-Host "Group" $Group.DisplayName "Label" $LabelDisplayName
   If (!($CheckLabel)) { 
        $BadLabel = $ContainerLabels.Item($Group.SensitivityLabel.Guid)
        Write-Host ("The label assigned to the {0} group doesn't allow guests ({1})" -f $Group.DisplayName, $BadLabel ) }
}

The label assigned to the Corporate Business Development group doesn't allow guests (Limited Access)
The label assigned to the Sales Department team group doesn't allow guests (Limited Access)

Speed and Effectiveness

And that’s it. We found groups with guest members and checked the groups for an appropriate sensitivity label. Using a server-side filter to find the groups means that performance is much better with Get-UnifiedGroup than is currently possible with the Graph cmdlets.

As a side note, over the past few years, Microsoft has built out the set of controls available for container management. A recent example is control over site external sharing capability. Even if you don’t want to use sensitivity labels for protecting Office documents (and PDF files), using labels for container management is a great way to apply consistent settings over teams, groups, and sites.

About the Author

Tony Redmond

Tony Redmond has written thousands of articles about Microsoft technology since 1996. He is the lead author for the Office 365 for IT Pros eBook, the only book covering Office 365 that is updated monthly to keep pace with change in the cloud. Apart from contributing to Practical365.com, Tony also writes at Office365itpros.com to support the development of the eBook. He has been a Microsoft MVP since 2004.

Comments

  1. Jakke

    Hello Toni,

    I have current situation, after changing the sensitivity label via SharePoint Admin center the label got modified on all platforms accordantly. The only label that did not get updated is the label I get with the get-unifiedgroup command. All Admin centers did show the correct label (including EAC and Graph Explorer) except for that Exchange command, what makes it unreliable for me 🙁 do you see the same behavior? BTW I did open a case with Microsoft on it.

    1. Avatar photo
      Tony Redmond

      Always change a sensitivity label for a group in AAD. Synchronizing label updates from SharePoint to the other workloads has some known issues, including slowness.

Leave a Reply