Entra / Microsoft 365 · Teams
Find tabs and apps in Teams
Report tabs and apps installed in Teams channels using Microsoft Graph app-only authentication. Set the tenant ID, app ID, and app secret before running.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Set $TenantId, $AppId, and $AppSecret for your app registration, then run the script.
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([string] $TenantId = "a662313f-14fc-43a2-9a7a-d2e27f4f3476",[string] $AppId = "d716b32c-0edb-48be-9385-30a9cfd96153")function Get-GraphData {# GET data from Microsoft Graph.# Based on https://danielchronlund.com/2018/11/19/fetch-data-from-microsoft-graph-with-powershell-paging-support/param ([parameter(Mandatory = $true)]$AccessToken,[parameter(Mandatory = $true)]$Uri)# Check if authentication was successful.if ($AccessToken) {# Format headers.$Headers = @{'Content-Type' = "application\json"'Authorization' = "Bearer $AccessToken"'ConsistencyLevel' = "eventual" }# Create an empty array to store the result.$QueryResults = @()# Invoke REST method and fetch data until there are no pages left.do {$Results = ""$StatusCode = ""do {try {$Results = Invoke-RestMethod -Headers $Headers -Uri $Uri -UseBasicParsing -Method "GET" -ContentType "application/json"$StatusCode = $Results.StatusCode} catch {$StatusCode = $_.Exception.Response.StatusCode.value__if ($StatusCode -eq 429) {Write-Warning "Got throttled by Microsoft. Sleeping for 45 seconds..."Start-Sleep -Seconds 45}else {Write-Error $_.Exception}}} while ($StatusCode -eq 429)if ($Results.value) {$QueryResults += $Results.value}else {$QueryResults += $Results}$uri = $Results.'@odata.nextlink'} until (!($uri))# Return the result.$QueryResults}else {Write-Error "No Access Token"}Cls# Define the values applicable for the application used to connect to the Graph$AppSecret = 's_rkvIn1oZ1cNceUBvJ2or1lrrIsb*:=' # and this# Construct URI and body needed for authentication$uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"$body = @{client_id = $AppIdscope = "https://graph.microsoft.com/.default"client_secret = $AppSecretgrant_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$Headers = @{'Content-Type' = "application\json"'Authorization' = "Bearer $Token"'ConsistencyLevel' = "eventual" }# Create list of Teams in the tenantWrite-Host "Fetching list of Teams in the tenant"$Uri = "https://graph.microsoft.com/beta/groups?`$filter=resourceProvisioningOptions/Any(x:x eq 'Team')"$Teams = Get-GraphData -AccessToken $Token -Uri $UriCLS$Report = [System.Collections.Generic.List[Object]]::new() # Create output file for report; $ReportLine = $Null$i = 0# Loop through each team to examine its channels and discover its channels, tabs, and appsForEach ($Team in $Teams) {$i++$ProgressBar = "Processing Team " + $Team.DisplayName + " (" + $i + " of " + $Teams.Value.Count + ")"$Uri = "https://graph.microsoft.com/v1.0/teams/" + $Team.Id$TeamDetails = Get-GraphData -AccessToken $Token -Uri $UriIf ($TeamDetails.IsArchived -ne $True) { # Team is not archived, so we can fetch informationWrite-Progress -Activity "Checking Teams Information" -Status $ProgressBar -PercentComplete ($i/$Teams.Value.Count*100)# Get apps installed in the team$Uri = "https://graph.microsoft.com/v1.0/teams/$($Team.id)/installedApps?`$expand=teamsApp"$TeamApps = Get-GraphData -AccessToken $Token -Uri $Uri$TeamAppNumber = 0ForEach ($App in $TeamApps) {$TeamAppNumber++$ReportLine = [PSCustomObject][Ordered]@{Record = "App"Number = $TeamAppNumberTeam = $Team.DisplayNameTeamId = $Team.IdApp = $App.TeamsApp.DisplayNameAppId = $App.TeamsApp.IdDistribution = $App.TeamsApp.DistributionMethod }$Report.Add($ReportLine) }# Get the channels so we can report the tabs created in each channel$Uri = "https://graph.microsoft.com/beta/Teams/$($Team.id)/channels"$TeamChannels = Get-GraphData -AccessToken $Token -Uri $Uri# Find the tabs created for each channel (standard tabs like Files don't show up here)ForEach ($Channel in $TeamChannels) {$Uri = "https://graph.microsoft.com/beta/teams/$($Team.id)/channels/$($channel.id)/tabs?`$expand=teamsApp"$Tabs = Get-GraphData -AccessToken $Token -Uri $Uri$TabNumber = 0ForEach ($Tab in $Tabs) {$TabNumber++$ReportLine = [PSCustomObject][Ordered]@{Record = "Channel tab"Number = $TabNumberTeam = $Team.DisplayNameTeamId = $TeamIdChannel = $Channel.DisplayNameTab = $Tab.DisplayNameAppId = $Tab.TeamsApp.IdDistribution = $Tab.TeamsApp.DistributionMethodWebURL = $Tab.WebURL}$Report.Add($ReportLine) }}} #End If (archived check)Else {Write-Host "The" $Team.DisplayName "team is archived - no check done" }}$Report | Sort Team, Record, App | Export-CSV C:\Temp\TeamsChannelsAppInfo.Csv -NoTypeInformationWrite-Host $EmailAddresses "Info about Teams channels, apps and tabs exported to C:\Temp\TeamsChannelsAppInfo.Csv"}
Parameters
ParameterDefaultNotes
-TenantId""Microsoft Entra tenant ID for app-only Graph authentication.-AppId""Application (client) ID for the app registration used to connect.Attribution
Author
Office365itpros