Table of Contents
Improving the Speed of reporting Teams SharePoint URLs by Replacing the Get-UnifiedGroup Cmdlet
Last week, following a response to a reader question, I updated an article describing how to create a report of Teams and the URLs for the SharePoint Online sites used to store shared files. The only real improvement I made to the script was to use the Get-ExoRecipient cmdlet to resolve the members of the ManagedBy property to output display names instead of mailbox names. This change is necessary since Exchange Online moved to using the External Directory Object ID (EDOID) as the mailbox name to ensure uniqueness. Not everyone can recognize a mailbox GUID and know what mailbox it refers to.
The script uses the Get-UnifiedGroup cmdlet to find team-enabled groups. After reviewing the code, I wondered if it was possible to speed up processing by replacing the Exchange Online cmdlets with Microsoft Graph PowerShell SDK cmdlets or API requests. It’s always been true that the Get-UnifiedGroup cmdlet is relatively slow. This situation is explainable because the cmdlet fetches a lot of data about a Microsoft 365 group from multiple workloads. Microsoft has improved the performance of Get-UnifiedGroup over the years, but it’s still not the most rapid cmdlet you’ll ever use.
Converting to Graph SDK Cmdlets
Converting the script to use Microsoft Graph PowerShell SDK cmdlets isn’t very difficult. Here’s the code:
# Check that we are connected to Exchange Online $ModulesLoaded = Get-Module | Select-Object -ExpandProperty Name If (!($ModulesLoaded -match "ExchangeOnlineManagement")) {Write-Host "Please connect to the Exchange Online Management module and then restart the script"; break} Connect-MgGraph -NoWelcome -Scopes Group.Read.All, Sites.Read.All Write-Host "Finding Teams..." [array]$Teams = Get-MgGroup -Filter "resourceProvisioningOptions/any(x:x eq 'Team')" -All If (!($Teams)) { Write-Host "Can't find any Teams for some reason..." } Else { Write-Host ("Processing {0} Teams..." -f $Teams.count) $TeamsList = [System.Collections.Generic.List[Object]]::new() ForEach ($Team in $Teams) { $SPOSiteURL = (Get-UnifiedGroup -Identity $Team.Id).SharePointSiteURL [array]$Channels = Get-MgTeamChannel -TeamId $Team.Id [array]$Owners = (Get-MgGroupOwner -GroupId $Team.Id).AdditionalProperties.displayName $DisplayNames = $Owners -join ", " $TeamLine = [PSCustomObject][Ordered]@{ Team = $Team.DisplayName SPOSite = $SPOSiteURL Owners = $DisplayNames } $TeamsList.Add($TeamLine) } $TeamsList | Out-GridView $TeamsList | Export-CSV -NoTypeInformation c:\temp\TeamsSPOList.CSV }
Figure 1 shows the result.

You’ll notice that I still use the Get-UnifiedGroup cmdlet to fetch the Teams SharePoint URL. It’s possible to retrieve this information using the Graph with code like:
$Uri = ("https://graph.microsoft.com/v1.0/groups/{0}/drive/root/webUrl" -f $Team.Id) $SPOData = Invoke-MgGraphRequest -Uri $Uri -Method Get [string]$SPODocLib = $SPOData.Value $SPOSiteUrl = $SPODocLib.SubString(0, $SPODocLib.LastIndexOf("/"))
Or:
$Uri = ("https://graph.microsoft.com/v1.0/groups/{0}/sites/root" -f $Team.Id) $SPOData = Invoke-MgGraphRequest -URI $Uri -Method Get $SPOSiteUrl = $SPOData.WebURL
The Problem with Permissions when Fetching Teams SharePoint URLs
In both cases, the code works. However, the code fails for some teams due to the restriction placed on interactive use of the Graph SDK. When you connect an interactive session to the Graph, you’re restricted to using delegate permissions. The only data that the Graph SDK cmdlets can access is whatever the signed-in user can access. This is very different to the permissions model used by modules like the Exchange Online management module, which allow access to data based on RBAC controls, meaning that a tenant administrator can access everything.
The restriction disappears when running the SDK cmdlets using a registered app or an Azure Automation runbook. Now the cmdlets can use application permissions, so they can access any data permitted by the Graph permissions assigned to the service principal of the app.
Using either version of the code shown above works perfectly and returns the SharePoint site URL, but only for sites accessible to the signed-in user. Attempts to access any other site returns a 403 forbidden error.
I even tried using the Teams Graph cmdlets:
[array]$Channels = Get-MgTeamChannel -TeamId $Team.Id $Files = (Get-MgTeamChannelFileFolder -TeamId $Team.Id -ChannelId $Channels[0].Id).WebURL $SPOSiteUrl = $Files.SubString(0,$Files.IndexOf("sites/")) + "sites/" + $Team.MailNickName
Again, this approach works for teams that the signed-in user is a member of, but not for other teams.
Going Back to Pure Exchange Cmdlets to Report Teams SharePoint URLs
The problem with permissions meant that I had to use a hybrid of Graph SDK cmdlets to get everything except the SharePoint site URL. And while this approach works, it’s slower than the original implementation using only Exchange Online cmdlets. In several runs against 88 teams the hybrid version took an average of 42 seconds to finish. The Exchange version required an average of 31 seconds.
The learning here is that Graph SDK cmdlets aren’t always the best choice for speed, no matter what you read on the internet. It’s always worth testing to find which approach is the most functional and fastest. Sometimes both boxes are ticked, and that’s a result.
Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.
Speaking of performance, have you noticed any recent degradation in the performance of the REST-based ExchangeOnlineManagement cmdlets (e.g. Get-EXOMailbox)? It previously took roughly 5-7 minutes to pull info for our ~50,000 mailboxes with “Get-EXOMailox -ResultSize Unlimited”. Since approximately September 12th, that same call takes 10 times as long (making it very nearly equivalent to the regular ol’ Get-Mailbox cmdlet from a timing perspective). I had assumed it was related to service alert EX675238 which referenced increased latency for REST API calls. However, that server incident was marked as resolved and the performance of Get-EXOMailbox remains unchanged. Is it just me?
I haven’t noticed any issues. I recommend that you file a service report with Microsoft. It’s the only way to get on engineering’s radar.