Reporting on Teams shared channels & users

One of the core principles I live my life by is to be informed, to know something fully before I make a decision.

Maybe it’s because I’m Autistic, maybe it’s just common sense, maybe it’s how I was raised, whatever – let’s not get philosophical here.

Virtually every organisation I’ve spoken to has said they’ve disabled shared channels in Microsoft Teams until they know more about it, and some have actually engaged me to help them with a framework for using it.

There are a number of challenges with shared channels, and that comes from the fact it’s trying to provide more granularity than guest access currently does, while at the same time trying to be flexible and easy to use – and we know you generally can’t have all of these together.

For those that have chosen to allow access to external channels and users, how do they know where their people are – and who is inside their tenant?

While there is some reporting in the Teams Admin Center (TAC), we have to dig into each Team, then each shared channel, and only then we can we find the external users and where they came from.

Thankfully we have a few endpoints in Microsoft Graph we can use to build some of our own reporting, using Power Automate to call the Graph endpoints and Power BI to visualise the data.

You can store the data in SharePoint Lists, Dataverse tables, or whatever you like.

There are a few aspects to capture in order to get a full picture:

  • Organisations that have been configured in for cross-tenant access in Azure AD
  • Shared channels attached to Teams
  • Internal users accessing shared channels in other organisations
  • Users from other organisations accessing shared channels in your tenant
  • Identifying what internal shared channels the external users are in

DISCLAIMER: Some of these calls are using beta endpoints and are technically not supported – so use at your own peril/pleasure.


Before we can create any workflows that call Graph, you will need an Azure AD App Registration with the following permissions:

  • Channel.ReadBasic.All
  • ChannelMember.Read.All
  • CrossTenantInformation.ReadBasic.All
  • CrossTenantUserProfileSharing.Read.All
  • Directory.Read.All
  • Policy.Read.All

You’ll also need to already have (or build) a listing of:

  • Users – ID & display name
  • Teams – ID & display name

Key workflows

In all of my workflows, I use a variable called “GraphPath” and sometimes “GraphPathSuffix” to help make the design of them more scalable and repeatable, so where applicable I will include those variables.

List organisations configured for cross-tenant access

Here we will use two key requests:

First, let’s list all the partners we have defined in Azure AD:

As this only returns the tenant ID, we need to run a second call against each of them to capture their organisation name and primary domain:

The data we’ll record looks like this:

And in Power BI our output looks like this:

Listing shared channels in your tenant

Ideally you already have a list of Teams in your tenant stored somewhere, if you don’t – you’ll need it for this next step.

Here we will use one key request:

The two parameters we’re using for our call are:

We’re then going to use both of these together with the Group ID of the Teams to see what shared channels exist:

For each channel we find, we’ll store these values:

Using a relationship between the “tenantid” value in this table and the same value in the table from the previous step we can start to build this out:

Where the values are blank in the “Remote Tenant” column, this is because the tenant is not external (i.e. the channel is hosted in our tenant).

List internal users accessing shared channels in other organisations

Here we will use two key requests:

Let’s set our Graph query elements:

The first step is to simply find who in our tenant is accessing external tenants, with the only result being their ID:

For each of those users, we now need to extrapolate and find the related tenants:

Then for each tenant returned, we only need to store the user ID and external tenant ID:

Again, using a relationship between the “tenantid” value in this table and the same value in the table from the previous step we can start to build this out:

List users from other organisations accessing shared channels in your tenant

Here we will use two key requests:

To see who from other organisations is inside of our tenant, our query is this:

And our action is simple:

Then for each result we find, we need to store the external user’s ID, display name, and home tenant ID:

Again, using a relationship between the “homeTenantId” value in this table and the same value in the table from the previous step we can start to build this out:

Connecting the dots

List which external users are in which shared channels

As we already have a list of shared channels stored in a table somewhere (in my case, it’s in Dataverse), we can now run a query against them.

Using this, we can now go beyond just having a listing of knowing which external users in are in shared channels, but knowing which users are in which channels themselves.

In my query to list those channels, I’ve used the existing “TenantID” variable I declared in my workflow to use with the Azure AD App Registration, so this will filter only channels that are in our tenant:

For each channel we want to list the members:

We again use our tenant ID to filter out any members who are internal, and I’ve also put a condition in place to only proceed if the resulting list has anything in it by using length(body(‘Filter_array_-_only_include_external_users’)):

For any users that do exist, we now record them in our table:

Over in Power BI we’ll establish a relationship between the Channel ID of the shared channels table and the Channel ID from this table (along with some filtering to remove channels where the “User IDs” column is empty), as well as the User ID from this table and the previous listing to get the following result:

Now, if you’re wondering why the list of external users is smaller in this list than in the previous list, it’s because there are effectively two components of the external users that exist:

  • Their inbound user profile
  • Their membership in a channel

Through the course of creating this solution, what I’ve discovered is that if you invite an external user to a shared channel, but they don’t accept the invitation – they will not show up as members of the channel.

It’s for this reason we need to maintain both lists of data (which could be addressed through better Power BI reporting skills than what I possess) – to understand where external users are in your tenant, and where they aren’t. This is the same as what we must do for Azure AD B2B guest users who may exist as an object in your directory, but not actually be a member of a M365 Group or Team (for any number of reasons, such as the Team no longer existing, they were removed by an owner, etc.).


As I started this blog post saying, it’s important to know what we are working with before we make decisions, as well as to provide troubleshooting and support in scenarios where things aren’t working the way we expect.

While a lot of what I’ve shown is possible in PowerShell, my preference is to do this using Power Automate, HTTP calls to Microsoft Graph, store the data in Dataverse tables, and report them with Power BI as it provides me with a solution that can continually operate without human intervention, as well as storing and displaying the data in a way that stakeholders and decision makers can access easily.

You may have reached this point and be wondering where the code for all this is, as I often publish the components on GitHub. As some of this was worked out during the course of writing this blog post, the workflows are not exactly in the most effective structure they could be.

Once I’ve cleaned them up, I’ll publish them on GitHub and update this blog post. For now, I’ve hopefully provided enough information for you to start building out your own reports.

Also published on Medium.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: