Graph APIs and Workload Modules Battle for Supremacy
Some astute observers have asked why Microsoft has two apparently parallel efforts to support PowerShell within Microsoft 365. On the one hand, development groups like Teams, Exchange Online, and SharePoint Online main app-specific modules. On the other, the Microsoft Graph PowerShell SDK aims to enable PowerShell for all Graph APIs across all supported workloads.
By supported workload, I mean one that supports a Graph API. Some applications, like Teams, use the Graph exclusively. Others, like Exchange Online and SharePoint Online, support a mixture of APIs. For instance, an Outlook Graph API is available, but there’s no Graph API available to automate Exchange administrative operations, so a vacuum exists in the Graph API when it comes to performing tasks like mailbox management.
The dichotomy is caused by multiple factors. Exchange and SharePoint run on-premises and in the cloud, so they need to support PowerShell on both platforms. Teams is a cloud-only application, so it’s natural for it to focus on the Graph. Having the choice about which module to use could be regarded as nice, but it’s also confusing, especially for people who aren’t used to dealing with the options for Microsoft 365 PowerShell.
Comparing the Teams Workload Module and the SDK
As an example of what I mean, let’s look at two implementations of code to create a report of all the Teams channels created in a tenant. I chose the Teams module because it’s based on the Graph, so the code and the results should be very similar.
Conceptually, the steps to create the report are simple:
- Get the set of Teams.
- For each team, get the set of channels.
- For each channel, report what you find.
Here’s the code for a script to do the job written for the Teams PowerShell module:
Connect-MicrosoftTeams $TeamsChannelData = [System.Collections.Generic.List[Object]]::new() [array]$Teams = Get-Team ForEach ($Team in $Teams) { Write-Host "Processing team" $Team.DisplayName $Channels = Get-TeamChannel -GroupId $Team.GroupId ForEach ($Channel in $Channels) { $ReportLine = [PSCustomObject] @{ Team = $Team.DisplayName Channel = $Channel.DisplayName Type = $Channel.MembershipType Id = $Channel.Id } $TeamsChannelData.Add($ReportLine) } } $AvgChannels = [math]::round(($TeamsChannelData.Count/$Teams.Count),2) Write-Host ("{0} Teams found with {1} channels, an average of {2} channels per team" -f $Teams.Count, $TeamsChannelData.Count, $AvgChannels)
There’s no special sauce here. The code generates a report of the channels in each team and the type of channel (regular, shared, or private). Figure 1 shows the output (for the Graph version).
![Reporting Teams channels](https://s40823.pcdn.co/wp-content/uploads/2022/07/Teams-Channels-PowerShell-1024x484.jpg)
Now let’s look at the same code adapted to use cmdlets in the Microsoft Graph PowerShell SDK. The same steps happen:
Connect-MgGraph $TeamsChannelData = [System.Collections.Generic.List[Object]]::new() [array]$Teams = Get-MgGroup -Filter "resourceProvisioningOptions/Any(x:x eq 'Team')" -All ForEach ($Team in $Teams) { Write-Host "Processing team" $Team.DisplayName $Channels = Get-MgTeamChannel -TeamId $Team.Id ForEach ($Channel in $Channels) { $ReportLine = [PSCustomObject] @{ Team = $Team.DisplayName Channel = $Channel.DisplayName Type = $Channel.MembershipType Id = $Channel.Id Created = $Channel.CreatedDateTime SPOUrl = $Channel.AdditionalProperties['filesFolderWebUrl'] } $TeamsChannelData.Add($ReportLine) } } $AvgChannels = [math]::round(($TeamsChannelData.Count/$Teams.Count),2) Write-Host ("{0} Teams found with {1} channels, an average of {2} channels per team" -f $Teams.Count, $TeamsChannelData.Count, $AvgChannels)
The differences you might notice with the code written for the Teams module are:
- The Get-MgGroup cmdlet applies a filter to find team-enabled Microsoft 365 Groups. In the other script, the Get-Team cmdlet only deals with teams, so it doesn’t need a filter. Because they don’t need to filter objects, workload modules are often easier to deal with when it comes to finding information to process.
- The channel information output to the report includes some data that isn’t available to the Get-TeamChannel cmdlet. I’ve chosen to output the creation date for each channel and the URI for the channel folder in the SharePoint Online team site. What we learn from this is that the Graph APIs often expose more information about objects than might be available through a cmdlet in a workload module. Developers choose what information to expose, and in this case, might have decided that no one would ever want to know the creation date of a channel or its SharePoint URI.
Module Performance
From the evidence above, it’s obvious that writing a script using the Microsoft Teams module or the Graph SDK will deliver similar results with similar code. But is one approach better than the other when it comes to performance?
One way to test is to run the code several times and use PowerShell’s Measure-Command cmdlet to report how long the code takes to run. I’ve used this technique before to test how the Exchange Online REST cmdlets (like Get-ExoMailbox) perfomed after their introduction in 2019.
Here’s how I ran the code using the Microsoft Graph PowerShell SDK cmdlets 10 times:
Write-Host "Running Teams Test..." $TotalSeconds = 0 For ($i=0; $i -lt 10 ) { $i++ ; Write-Host "Processing run" $i $TeamsResult = Measure-Command { [array]$Teams = Get-MgGroup -Filter "resourceProvisioningOptions/Any(x:x eq 'Team')" -All ForEach ($Team in $Teams) { # Write-Host "Processing team" $Team.DisplayName $Channels = Get-MgTeamChannel -TeamId $Team.Id ForEach ($Channel in $Channels) { $ReportLine = [PSCustomObject] @{ Team = $Team.DisplayName Channel = $Channel.DisplayName Type = $Channel.MembershipType Id = $Channel.Id Created = $Channel.CreatedDateTime SPOUrl = $Channel.AdditionalProperties['filesFolderWebUrl'] } $TeamsChannelData.Add($ReportLine) } } # End of ForEach Channel $Total = $Total + $TeamsChannelData.Count } # End of ForEach team Write-Host ("Result: Processed channels from {0} teams in {1} seconds" -f $Teams.Count, $TeamsResult.TotalSeconds)} # End of 10 iterations
Testing the code with 82 teams containing 179 channels, I discovered:
- The runs of the script using the Graph SDK cmdlets took between 25 and 34 seconds (Figure 2). The script using the Teams cmdlets took twice as long.
- The Graph SDK cmdlets reported 179 channels instead of the 178 reported by the Teams cmdlets. The discrepancy is because Get-MgTeamChannel can report shared channels hosted in other tenants while Get-TeamChannel ignores these channels. Microsoft will probably close this gap in a future version of the Microsoft Teams PowerShell module.
![Testing the SDK cmdlets against Teams channels
Microsoft 365 PowerShell](https://s40823.pcdn.co/wp-content/uploads/2022/07/Teams-Channels-PowerShell-Test-1024x567.jpg)
I suspect that the speed advantage detected here will increase as the number of teams to process grows. Without going and creating a thousand or so teams, it’s hard to know, but I’m willing to stick my neck out and say that the Graph SDK cmdlets perform better in this scenario and are likely to do the same in other scenarios. Your mileage will vary, of course, and there’s no guarantee that the Graph SDK cmdlets will be faster in specific circumstances.
Questions to Ponder
The tests I ran are very basic but they’re enough to create some questions that you might consider before creating your next automation script for Microsoft 365. The two basic issues are:
- Can the Microsoft Graph PowerShell SDK be used? As noted above, there are parts of Microsoft 365 where the SDK offers no coverage, and a workload module is the only option.
- Does speed matter? It doesn’t in small tenants where the need is to process small sets of data. It becomes critical when dealing with thousands of objects like mailboxes, groups, or teams.
- Does a specific method allow access to data that’s not obtainable elsewhere?
After that, it comes down to issues like familiarity with cmdlets, the availability of existing code to use as the basis for a new script, whether you plan to run the script interactively or through a scheduled task, and so on. It’s perfectly OK to keep on using the workload modules if that’s your choice. At least, until Microsoft decides to deprecate a module like they’ve done with Azure AD.
Multiple Microsoft 365 PowerShell Options Not Unusual
Having multiple ways to accomplish tasks in PowerShell isn’t unusual. In the past, the Azure AD module attempted to take over from the Microsoft Online Services module, but never quite managed to do that in some areas (like MFA registration). Cmdlets from the SharePoint Online module are often combined with the PnP module to manage SharePoint Online and OneDrive for Business. And sometimes you need to use Exchange Online cmdlets to manage elements of Microsoft 365 Groups alongside cmdlets to manage Teams (and even Azure AD). Microsoft 365 can be a confusing melange of PowerShell, so from that perspective it’s good to see the Microsoft Graph PowerShell SDK making progress. And it has over the last year or so.
To get the Microsoft Graph PowerShell SDK to the point where it becomes the natural choice for developers wanting to write PowerShell scripts to process Microsoft 365 data, all we need to happen is:
- Coverage of all Microsoft 365 workloads, including Exchange Online.
- Better documentation (including good examples).
- Elimination of some functionality gaps that exist in the SDK.
- Reducing the friction that occurs at times (like the way Get-MgGroupMember returns membership information for a group as a set of identifiers instead of member names and their details).
On the surface, it doesn’t seem like a lot, but tribal tensions within Microsoft engineering groups and other software engineering priorities will probably mean that this state of nirvana won’t come about until well after I retire. Even then, the mass of scripts running in customer tenants and Microsoft itself will probably mean that the confused state of PowerShell in Microsoft 365 will persist for years afterward.
I can’t even get get-mgTeam to run without getting an error.
Get-MgTeam_List1: Requested API is not supported. Please check the path.
The Real Person!
The Real Person!
The cmdlet is Get-MgTeam. Are you trying to run Get-MgTeam -TeamId xxxxx?
In my opinion there is one other argument in favour for the SDK, and that is the portability of the commands (and scripts) over the different platforms (Windows, Linux and OSX)
The Real Person!
The Real Person!
That’s a reasonable point!
Is there any indication that MS is working on making Exchange Online admin functions available via Graph?
The Real Person!
The Real Person!
None that I can report…
That performance difference can really add up for customers with thousands of Microsoft Teams. The difference in counts for shared channels is also a problem since developers have to keep track of how it counts now and when the count might change in the future (and whether the change is announced or not).