Get Microsoft Teams private channel members using PowerShell

When managing teams within Microsoft Teams, you may run into an odd situation in which you are an owner of the team but are unable to view that team's private channels. In this article, I'll show you how you can not only view the complete list of channels within a team that you own but also view the membership list for those channels. In addition, we'll format and print the entire list of channels and members to a text file for future reference or to serve as a sort of backup when preparing to make potentially breaking changes.

I'm an owner. Why can't I see all the channels?

While this may not make sense at first glance, there is a great reason for this. Consider the following configuration:

  • Manager A is working on a project containing sensitive documents

  • Admin B is responsible for managing access to the team, but doesn't have a business requirement for accessing those sensitive documents

If Admin B were to have the ability to make changes to any private channel within a team that they own, nothing would prevent them from granting themselves or anyone else access to these sensitive documents and discussions contained within the private channel. Therefore, it makes sense that the owner of a team isn't authorized to make changes to its membership.

Fine, but why can't I view the membership?

This is a great question, and it's the exact question that drove me to create the PowerShell script and this accompanying blog post. Referring to the Microsoft documentation regarding Private channels in Microsoft Teams, we can see that "Team owners can see the names of all private channels in their team". That's great, but we can easily see that we, in fact, can not see the private channels while in Microsoft Teams. However, as I'll show you, this is possible using PowerShell.

Prerequisites

Before we can start building our PowerShell script, let's make sure we have all the necessary pieces installed.

Install PowerShell

If you're using Windows, you already have PowerShell installed. For any other operating system, or if you'd like to install a different version of PowerShell than the one installed by default, follow these instructions for Windows, MacOS, or Linux.

Install the Microsoft Teams PowerShell module

Now that we have PowerShell installed, we'll need to also install the Microsoft Teams module to connect and perform operations with Microsoft Teams. To do this, follow these instructions to Install Microsoft Teams PowerShell Module.

The script

Alright, we now understand why we need to view the private channels and members within a team that we own, we know that it's not possible through Microsoft Teams and must be done through PowerShell, and we've even installed all the necessary tools for the job. The only thing left to do now is to actually write the script.

Authenticating to Microsoft Teams

To be able to perform any operations against the teams that we own, we first need to tell Microsoft Teams who we are, so our authorization can be verified. To do this, we simply run the command:

Connect-MicrosoftTeams;

Running this command will open a new browser window and walk us through the Microsoft authentication process. Be sure to use the same login credentials that correspond to the teams that you are looking to perform operations against.

Get the GroupId and private Channels

Now that we've connected to Microsoft Teams, let's get the GroupId for our team, as well as the list of private Channels. The GroupId is necessary for us to get the list of channels, as well as when we start requesting the members for each channel.

To get the GroupId, the command is:

$GroupId = (Get-Team -DisplayName "Your team name here").GroupId;

Then, to get the list of channels, the command is:

$Channels = Get-TeamChannel -GroupId $GroupId | Where-Object { $_.MembershipType -eq "Private" };

To make sure we were successful, let's write the output to our terminal window, like so:

Write-Host $GroupId;
foreach ($channel in $Channels) {
  Write-Host $channel.DisplayName;
}

If successful, we should see an output similar to this:

Your team name here
Channel 1
Channel 2
...

Get the members for each private channel

We now have a list of the private Channels, so that's great. Next, let's see if we can get a list of the members within each private channel. To do this, add the following command within the foreach loop you created earlier:

$members = Get-TeamChannelUser -GroupId $GroupId -DisplayName $channel.DisplayName;

Just like before, let's check it by writing the output to the terminal, like so:

foreach ($member in $members) {
  Write-Host $member.Name
}

If successful, we should see an output similar to this:

Member 1
Member 2
...

Putting it all together

Sweet! We are now able to get a list of all private Channels within a team and, for each channel, we can get a list of all members. At this point, you're full script should look like this:

Connect-MicrosoftTeams;

$GroupId = (Get-Team -DisplayName "Your team name here").GroupId;
$Channels = Get-TeamChannel -GroupId $GroupId | Where-Object { $_.MembershipType -eq "Private" };

Write-Host $GroupId;
foreach ($channel in $Channels) {
  Write-Host $channel.DisplayName;

  $members = Get-TeamChannelUser -GroupId $GroupId -DisplayName $channel.DisplayName;

  foreach ($member in $members) {
    Write-Host $member.Name
  }
}

Which should generate the following output:

Your team name here
Channel 1
Member 1
Member 2
...
Channel 2
Member 1
Member 2
...

Improvements

The script above is great, but there are a couple of key improvements I'd like to make, which will make this script even more useful. First, we'll refactor the script to loop through a list of teams instead of just a single team. Then, we'll replace our Write-Host commands with something else so that the output is saved into a text file.

Looping through a list of teams

Right after we connect to Microsoft Teams, but before we request the GroupId, let's add an array or list of teams containing the Name of each team we want to look at, along with a GroupId that will contain the unique ID for this team, and an array of Channels that will contain the information for each private channel within this team.

$teams = @(
  [PSCustomObject]@{
    Name = "Your first team name here"
    GroupId = "" # Empty for now
    Channels = @() # Also empty for now
  },
  [PSCustomObject]@{
    Name = "Your second team name here"
    GroupId = "" # Empty for now
    Channels = @() # Also empty for now
  },
  [PSCustomObject]@{
    Name = "Your third team name here"
    GroupId = "" # Empty for now
    Channels = @() # Also empty for now
  }
);

If this syntax is unfamiliar to you, refer to Everything you wanted to know about arrays, specifically the section on Arrays of Objects.

Next, let's enclose the rest of our script within a foreach loop, which will iterate through each team in our $teams array.

# $teams array above
foreach ($team in $teams) {
  # team-specific scripts go here
};

Finally, let's replace every instance of $GroupId and $Channels with $team.GroupId and $team.Channels, respectively. Also, make sure to replace the text "Your team name here" with $team.Name to refer to the current team being processed. Let's also write each team name instead of the GroupId to the output and do a bit of formatting so it's easier to see the hierarchy of teams, channels, and members. The end result of these changes looks like this:

Connect-MicrosoftTeams;

$teams = @(
  [PSCustomObject]@{
    Name = "Your first team name here"
    GroupId = "" # Empty for now
    Channels = @() # Also empty for now
  },
  [PSCustomObject]@{
    Name = "Your second team name here"
    GroupId = "" # Empty for now
    Channels = @() # Also empty for now
  },
  [PSCustomObject]@{
    Name = "Your third team name here"
    GroupId = "" # Empty for now
    Channels = @() # Also empty for now
  }
);

foreach ($team in $teams) {
  $team.GroupId = (Get-Team -DisplayName $team.Name).GroupId;
  $team.Channels = Get-TeamChannel -GroupId $team.GroupId | Where-Object { $_.MembershipType -eq "Private" };

  Write-Host $team.Name;
  foreach ($channel in $team.Channels) {
    Write-Host "--$($channel.DisplayName)";

    $members = Get-TeamChannelUser -GroupId $GroupId -DisplayName $channel.DisplayName;

    foreach ($member in $members) {
      Write-Host "----$($member.Name)";
    };
  };
};

Now, if we run this updated script, we should see the output:

Your first team name here
--Channel 1
----Member 1
----Member 2
...
--Channel 2
----Member 1
----Member 2
...
Your second team name here
...
Your third team name here
...

Writing to a text file

Our terminal output is getting quite long, so let's send it to a text file instead. This also has the benefit of giving you a file that you can save as a reference of the membership status on a given date and time, in case you need it later.

First, let's create the new file right after we create the list of teams, like so:

$filePath = "Teams private channel members.txt";
"" | Out-File -FilePath $filePath;

This line creates a new text file in the same directory as your PowerShell script, containing a blank line.

Now that we have a new text file to write to, we simply need to replace all instances of Write-Host ... with ... | Out-File -FilePath $filePath -Append. The -Append flag will ensure that the content being written doesn't replace the existing content in the file.

With those small changes made, our final script looks like this:

Connect-MicrosoftTeams;

$teams = @(
  [PSCustomObject]@{
    Name = "Your first team name here"
    GroupId = "" # Empty for now
    Channels = @() # Also empty for now
  },
  [PSCustomObject]@{
    Name = "Your second team name here"
    GroupId = "" # Empty for now
    Channels = @() # Also empty for now
  },
  [PSCustomObject]@{
    Name = "Your third team name here"
    GroupId = "" # Empty for now
    Channels = @() # Also empty for now
  }
);

$filePath = "Teams private channel members.txt";
"" | Out-File -FilePath $filePath;

foreach ($team in $teams) {
  $team.GroupId = (Get-Team -DisplayName $team.Name).GroupId;
  $team.Channels = Get-TeamChannel -GroupId $team.GroupId | Where-Object { $_.MembershipType -eq "Private" };

  $team.Name | Out-File -FilePath $filePath -Append;
  foreach ($channel in $team.Channels) {
    "--$($channel.DisplayName)" | Out-File -FilePath $filePath -Append;

    $members = Get-TeamChannelUser -GroupId $GroupId -DisplayName $channel.DisplayName;

    foreach ($member in $members) {
      "----$($member.Name)" | Out-File -FilePath $filePath -Append;
    };
  };
};

Now, when we run this script, a text file named "Teams private channel members" will be created and will contain the same output that we were previously writing to the terminal.

Great work!

Conclusion

In this article, we discussed why you might want to view the private channels and members of a team that you own, even if you're not a member of those channels. We also wrote a PowerShell script that:

  • Loops through a list of teams that you provide

  • Gets a list of private channels for each team

  • Gets a list of members for each private channel

  • Writes the information to a text file

Thanks for reading and, if you found this article useful, please consider sharing it across your network so others may be able to benefit from this information.

Take care!