Last Update: Sep 04, 2024 | Published: Sep 03, 2019
After a shaky start, the Teams PowerShell module is now in a reasonable state. Valid gripes still exist that the Teams module is slow, and you must resort to the horrible Skype for Business Online module to work with Teams policies, but hope exists that Microsoft will improve performance and create an integrated module in the future.
But no matter how the Teams PowerShell module improves, its usefulness is still limited by the properties Microsoft chooses to expose through this interface. For instance, even if sometimes Microsoft backtracks on useful changes, Exchange Online, exposes a lot of information about mailboxes and Office 365 Groups. This makes it much easier for administrators to automate common operational processes for mailboxes and groups.
However, Exchange was the first major Microsoft server to adopt PowerShell way back in Exchange 2007. When Teams came along, PowerShell wasn’t its primary choice for an interface to enable automation. Instead, Teams focused on the Microsoft Graph API. The Teams PowerShell module is built on top of the Graph API, a fact that explains some oddities in filtering and other behavior.
In any case, because Microsoft doesn’t expose all the properties of teams through PowerShell, sometimes you’re forced to use the Graph to get at information (the Graph Explorer helps you understand the information available for Teams). This is fine if you’re a programmer who’s used to dealing with RESTful APIs, but maybe not so good if you’re an administrator who writes some PowerShell scripts from time to time.
Fortunately, you can access the Graph API through PowerShell. And once you get your head around the concepts involved, it’s reasonably straightforward to write scripts to access and use the information exposed through the Graph.
There are many blog posts available that explain how to connect to the Graph with PowerShell (here’s a good example and here’s another approach). The basic idea is that:
So much for theory. As my working example, I’ve chosen to interrogate Teams to discover the set of channels that are mail-enabled and report the email addresses assigned to these channels. Any team member can enable a channel by requesting an email address. When this happens, Office 365 creates a special hidden mailbox for the channel and links the mailbox to Teams with a connector. Mail sent to the channel shows up as a new conversation and is also captured in the SharePoint document library belonging to the team.
The Teams PowerShell module includes a Get-TeamChannel cmdlet to return the set of channels in a team. However, it doesn’t return properties to show if a channel is mail-enabled or what its email address is, but the Graph knows. Here’s the code I used, broken into:
Cls # Define the values applicable for the application used to connect to the Graph. These values # are unique to an app within a tenant. $AppId = "dx16b32c-0edb-48be-9385-30a9cfd96155" $TenantId = "b662333f-14fc-43a2-9a7a-d2e27f4f3478" $AppSecret = 's_rkvIn1oZ1cNceUBvJ2or1lrrIsb*:=' # Construct URI and body needed for authentication $uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" $body = @{ client_id = $AppId scope = "https://graph.microsoft.com/.default" client_secret = $AppSecret grant_type = "client_credentials" } # Get OAuth 2.0 Token $tokenRequest = Invoke-WebRequest -Method Post -Uri $uri -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing # Unpack Access Token $token = ($tokenRequest.Content | ConvertFrom-Json).access_token # Base URL $uri = "https://graph.microsoft.com/beta/" $headers = @{Authorization = "Bearer $token"} $ctype = "application/json" # Create list of Teams in the tenant Write-Host "Fetching list of Teams in the tenant" $Teams = Invoke-WebRequest -Method GET -Uri "$($uri)groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')" -ContentType $ctype -Headers $headers | ConvertFrom-Json # Loop through each team to examine its channels and discover if any are email-enabled $i = 0; $EmailAddresses = 0; $Report = [System.Collections.Generic.List[Object]]::new(); $ReportLine = $Null ForEach ($Team in $Teams.Value) { $i++ $ProgressBar = "Processing Team " + $Team.DisplayName + " (" + $i + " of " + $Teams.Value.Count + ")" Write-Progress -Activity "Checking Teams Information" -Status $ProgressBar -PercentComplete ($i/$Teams.Value.Count*100) # Get owners of the team $TeamOwners = Invoke-WebRequest -Method GET -Uri "$($uri)groups/$($team.id)/owners" -ContentType $ctype -Headers $headers | ConvertFrom-Json If ($TeamOwners.Value.Count -eq 1) {$TeamOwner = $TeamOwners.Value.DisplayName} Else { # More than one team owner, so let's split them out and make the string look pretty $Count = 1 ForEach ($Owner in $TeamOwners.Value) { If ($Count -eq 1) { # First owner in the list $TeamOwner = $Owner.DisplayName $Count++ } Else { $TeamOwner = $TeamOwner + "; " + $Owner.DisplayName } }} # Fetch list of channels for the team $Channels = Invoke-WebRequest -Method GET -Uri "$($uri)teams/$($team.id)/channels" -ContentType $ctype -Headers $headers | ConvertFrom-Json #Loop through each channel and get its email address if set ForEach ($Channel in $Channels.Value) { If (-Not [string]::IsNullOrEmpty($Channel.Email)) { # Write-Host "Email address found" $Channel.Email $EmailAddresses++ $ReportLine = [PSCustomObject]@{ Team = $Team.DisplayName TeamEmail = $Team.Mail Owners = $TeamOwner Channel = $Channel.DisplayName ChannelDescription = $Channel.Description ChannelEmailAddress = $Channel.Email } # And store the line in the report object $Report.Add($ReportLine) }} } $Report | Sort Team | Export-CSV C:TempTeamsChannelsWithEmailAddress.Csv -NoTypeInformation Write-Host $EmailAddresses "mail-enabled channels found. Details are in C:TempTeamsChannelsWithEmailAddress.Csv"
The output is a CSV file holding details of the mail-enabled channels (Figure 1).
The Graph is hugely important to Office 365 and Microsoft is moving away from older APIs to use the Graph as quickly as it can. There’s obviously lots more that you can do to exploit the Graph with PowerShell (improve my code for a start), but hopefully this example is enough to get some creative juices going and remove a barrier that might have stopped you going near the Graph. PowerShell is great; it’s just even better when connected to the Graph.