Using the Get-AssociatedTeam Cmdlet to Report Team Memberships

Includes Shared Channel Direct Membership Too

I’ve written several articles describing how to write PowerShell scripts to create membership reports for groups and teams in the past (here’s a recent example). Usually, the scripts involve interrogating groups about their membership or finding sets of users and checking what groups they are members of. For this kind of processing, Graph API requests are the fastest way to generate results because of the number of user accounts or groups to be processed.

This brings me to the Get-AssociatedTeam cmdlet, a new cmdlet that makes its debut in version 4.6 of the MicrosoftTeams PowerShell module. Microsoft hasn’t published documentation for the cmdlet yet, but its purpose is obvious: it reports all the teams a user is a member of. As such, the Get-AssociatedTeam cmdlet could be the basis for a script to report the membership of all Teams.

Before plunging into the details of how to write such a script, the interesting thing about the Get-AssociatedTeam cmdlet is that it reports membership of shared channels. Or rather, direct membership of shared channels, which is when a user is explicitly invited to share a channel hosted in their tenant or in an external tenant. The information returned by the cmdlet includes all instances where a user is a regular member of a team and where they are a direct member of a shared channel. Unfortunately, the information returned for a team doesn’t tell you if the membership is of a regular team or shared channel. Here’s an example of the information for a team:

GroupId     : a53141d5-54ef-4a6d-877d-63b0cbda409f
DisplayName : Marketing Department
TenantId    : a662313f-14fc-43a2-9a7a-d2e27f4f3478

Being able to report membership of shared channels is the unique added value of the Get-AssociatedTeam cmdlet. There doesn’t seem to be a way to extract this information using a Graph API request, nor is there a way to report the membership of private channels without checking each of these channels.

Writing a Report Script

The script I wrote to explore the possibilities of Get-AssociatedTeam is available from GitHub. It:

  • Uses Get-MgUser to find the set of licensed Azure AD member accounts in the tenant. The Get-AssociatedTeam cmdlet doesn’t process guest accounts.
  • For each account, run Get-AssociatedTeam to return the set of teams for the user.
  • For each team, use the findTenantInformationByTenantId Graph API to resolve the tenant identifier to find the tenant name. This helps to highlight access to external tenants.
  • Report the information.

The code for the main loop is:

[int]$i = 0
$UserTeamInfo =  [System.Collections.Generic.List[Object]]::new()
ForEach ($User in $Users) {
  $i++
  Write-Host ("Processing team membership for {0} ({1}/{2})..." -f $User.DisplayName, $i, $Users.Count)
  [array]$TeamInfo = Get-AssociatedTeam -User $User.UserPrincipalName
  ForEach ($Team in $TeamInfo) {
   If ($Team.TenantId -eq $TenantId) { # Resolve the tenant identifier to a name
      $Name = $TenantName }
   Else {
      $LookUpId = $Team.TenantId.toString()
      $Uri = "https://graph.microsoft.com/beta/tenantRelationships/findTenantInformationByTenantId(tenantId='$LookUpId')"
      $ExternalTenantData = Invoke-MgGraphRequest -Uri $Uri -Method Get
      $Name = $ExternalTenantData.DisplayName 
   }
   $TeamData = [PSCustomObject][Ordered]@{  # Write out details of the team
       Id          = $User.Id
       DisplayName = $User.DisplayName 
       UPN         = $User.UserPrincipalName
       Team        = $Team.DisplayName
       TeamId      = $Team.GroupId
       Tenant      = $Name
       TenantId    = $Team.TenantId}
     $UserTeamInfo.Add($TeamData)
  } #End ForEach Team
} # End ForEach User

Analyzing the Data

Figure 1 shows the kind of information generated by the script.

Report of Teams membership generated by the Get-AssociatedTeam cmdlet
Figure 1: Report of Teams membership generated by the Get-AssociatedTeam cmdlet

Once the membership data is available, we can slice and dice it in different ways. For example, some simple analysis reveals nuggets like the average number of teams a user belongs to, how many external teams people are members of, and so on.

[array]$ExternalTeams = $UserTeamInfo | Where-Object {$_.TenantId -ne $TenantId} | Sort-Object TeamId -Unique
$ExternalPeople = $UserTeamInfo | Where-Object {$_.TenantId -ne $TenantId} | Sort-Object UPN -Unique
$ExternalPeople = $ExternalPeople.DisplayName -Join ", "
$ExternalTenants = $ExternalTeams.Tenant | Sort-Object -Unique
$AvgTeams = [math]::round(($UserTeamInfo.Count/$Users.Count),2)

Write-Host ""
Write-Host ("Each of the {0} users belongs to an average of {1} teams" -f $Users.Count, $AvgTeams)
Write-Host ("Membership of {0} teams found in {1} external tenant(s)" -f $ExternalTeams.Count, $ExternalTenants.Count)
Write-Host ("These accounts have membership of external teams: {0}" -f $ExternalPeople)

Each of the 28 users belongs to an average of 15.54 teams
Membership of 2 teams found in 1 external tenant(s)
These accounts have membership of external teams: Chris Bishop, James Ryan, Ken Bowers, Sean Landy, Tony Redmond

Generating Report Files

Carefully sliced and diced data makes an excellent foundation for reports, which can be generated as CSV files, Excel spreadsheets, HTML files, and so on.

I’ve mentioned the PSWriteHTML module before as an easy way to generate good-looking reports from PowerShell. Apart from its ability to format data in various ways, PSWriteHTML includes a very nice search builder capability to filter data. For instance, in Figure 2 I used the search builder to find the members of a specific team. When I’m happy with the data, generating a PDF, Excel, or CSV file is as easy as clicking a button.

Reporting Teams membership with PSWriteHTML
Figure 2: Reporting Teams membership with PSWriteHTML

New Cmdlet is Useful

I’m always amazed by the number of organizations that want to report memberships of groups and teams. The Get-AssociatedTeam cmdlet certainly makes it easier to create reports for Teams. As always, the caveat for these kinds of reports is that the scripts to generate the report can take a long time to run as the number of users and teams increases into the thousands. At that point, it’s time to investigate the use of Azure Automation to create reports as scheduled jobs. Finally, if you need to generate PDF reports from PowerShell, check out the PSWriteHTML module.


Keep up to date with developments like new PowerShell cmdlets by subscribing to the Office 365 for IT Pros eBook. Our monthly updates make sure that our subscribers understand the most important changes happening across Office 365.

7 Replies to “Using the Get-AssociatedTeam Cmdlet to Report Team Memberships”

  1. Hello!
    THANKS for this great script. However, this one does not run stable for me. Out of 5 tries it runs through, all the other attempts end with some errors.
    I am pretty sure that the reason is as following:
    >> Get-AssociatedTeam -User xxxx is totally unreliable for me!!
    Only once in around 8 attempts the response does include the TenantID! In all the other queries, only GroupID and DisplayName is returned and TenantID stays empty/null.

    Obviously this does not help for the rest of the script… Do you see similar strangeness??

    1. I haven’t seen any problems. Is the problem with the Get-AssociatedTeam cmdlet or with the resolution of the tenant identifier?

      Is the problem showing up here? You say that the tenantid doesn’t show up, but this value is returned by Get-AssociatedTeam so I can’t see how it would be missing.

      ForEach ($Team in $TeamInfo) {
      If ($Team.TenantId -eq $TenantId) { # Resolve the tenant identifier to a name
      $Name = $TenantName }
      Else {
      $LookUpId = $Team.TenantId.toString()
      $Uri = “https://graph.microsoft.com/beta/tenantRelationships/findTenantInformationByTenantId(tenantId=’$LookUpId’)”
      $ExternalTenantData = Invoke-MgGraphRequest -Uri $Uri -Method Get
      $Name = $ExternalTenantData.DisplayName
      }

      1. Hello Tony, thanks for your reply.

        Yes, the problem is with the cmdlet itself, not with your script.
        Means, I doubt your statement of “but this value is returned by Get-AssociatedTeam” as it’s not (for me). 😉

        I am firing this manually so no scripting involved whatsoever. Sometimes I get a TenantID back, but mostly not. Maybe 1 success in about 8 attempts.
        I have no idea what causes this….But see for yourself:

        PS C:\Users\haral> Get-AssociatedTeam -User office@xxxxx.onmicrosoft.com|fl

        GroupId : 13db627c-0f46-xxxx-xxxx-9c2b44936869
        DisplayName : HST Consulting Team1
        TenantId :

        PS C:\Users\haral> Get-AssociatedTeam -User office@xxxxx.onmicrosoft.com|fl

        GroupId : 13db627c-0f46-xxxx-xxxx-9c2b44936869
        DisplayName : HST Consulting Team1
        TenantId : 3544a441-xxxx-xxxx-aa3a-8b5a22bbf6a6

        BTW: I am on MicrosoftTeams Powershell Module releaase 4.6.0 if this is of any relevance.
        ==> I would say there is something buggy on their side.

      2. As a test, change the user parameter to be the Azure AD user account identifier instead of the User Principal Name and see what happens.

        [array]$TeamInfo = Get-AssociatedTeam -User $User.Id

        Using the account identifier is OK for the script (I just tested it) and I am curious to know if it fixes this problem.

      3. Thanks for the tip, but pretty much SAME result. It feels like the success rate went up from 1 out of 8 into 1 out of 5, but this is no fundamental change, right?
        PS C:\Users\haral> Get-AssociatedTeam -user 21ce8fef-xxxx-xxxx-xxxx-8759a25fb40e | fl
        GroupId : 13db627c-xxxx-xxxx-xxxx-9c2b44936869
        DisplayName : HST Consulting Team1
        TenantId :

      4. According to Microsoft, it’s a bug that’s caused by some components in the service not being completely updated. They are working on a fix. It should be resolved soon.

Leave a Reply

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