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
Figure 1: Reporting Teams channels

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
Figure 2: Testing the SDK cmdlets against Teams channels

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.

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. Avatar photo
    jamie.mccarthy@me.com

    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.

    1. Avatar photo
      Tony Redmond

      The cmdlet is Get-MgTeam. Are you trying to run Get-MgTeam -TeamId xxxxx?

  2. Avatar photo
    Kees Sprangers

    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)

  3. Avatar photo
    Shawn B

    Is there any indication that MS is working on making Exchange Online admin functions available via Graph?

  4. Avatar photo
    Randy Rempel

    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).

Leave a Reply