Migrate Microsoft Team and Private Channel Members with Graph API and PowerShell

As Microsoft Teams grows into one of the most successful tools in the Microsoft 365 suite, more organizations are seeing Teams as business critical. Entire processes are built around Teams and it has become more important to be able to manage Teams at scale. Generally when managing Teams membership, we can use the Exchange Online cmdlets to manage membership of the Unified Group that underpins the Team, however when those Teams include Private Channels, this method doesn’t meet the requirements. The Teams PowerShell Module does have the cmdlets available but in my experience with it, it doesn’t perform well at scale. This will surely improve over time but to meet the requirement today, I have created a script to allow the copying of Teams and Private Channel membership between Teams both within the same tenancy, and between tenancies.

Prepare the Source Tenancy

As we are using Graph API, we need to create an Application Registration to handle authentication and permissions. To set this up, check out the post I wrote (Tip 1) on 7 Tips for Working with the Graph API in PowerShell. With the Application Registration set up in the source tenancy, create a Client Secret. Take note of the Application ID, Tenant ID and Client Secret as per Tip 3 and then add the Application permissions shown in Figure 1.

Figure 1: Required Source Application Registration Permissions

Prepare the Target Tenancy

The target tenancy is setup similar to the source however there are different permissions required to write the membership in the target Teams. Set up a app reg in the target and add Delegated permissions shown in Figure 2. As the ability to include Guest users in the process was something I wanted to include in the script, delegated permissions are required. This is because we can’t add Guests to a Team using application permissions for some reason…

Figure 2: Required Target Application Registration Permissions

As we will be using delegated permissions, you don’t need to create a Client Secret for the destination tenant, however make sure to enable “Allow Public Client Flows” under “Authentication” as shown in Figure 3.

Figure 3: Enable “Allow Public Client Flows”

It’s also important to note that using delegated permissions, your account must be an owner of the Teams and Channels in the destination.

Build the Mapping File

To translate the members in the source tenant into members in the target tenant, we need to provide a mapping file. This mapping file is a CSV and is pretty straightforward. We only need to provide two columns, “SourceID”, and “TargetID”. SourceID is the Object ID of the users in your source tenant and TargetID is the same value for the user in the target tenant. The below command can help export a list of all of the users and their Object IDs to CSV. It can be run in each tenant and used to create a mapping file using some Excel skills.

Get-AzureADUser -Filter "userType eq 'Guest'" -All $true | select Displayname, Mail, ObjectID | Export-Csv TargetGuests.csv -NoTypeInformation 

In the end, the mapping file should look like the example in Figure 4.

This image has an empty alt attribute; its file name is image-1.png
Figure 4: Example of a Mapping File

If there are members you don’t want to migrate, simply exclude them from the mapping file.

Running the Script

Once you download the script from GitHub (Link at the bottom of the post), prepare the following parameters:

  • SourceTeamObjectID – The Object ID of the Team / Group in the Source Tenant
  • SourceTenantID – The Tenant ID of the source tenancy
  • SourceClientID – The Client ID of the App Reg in the source tenancy
  • SourceClientSecret – The Client Secret of the App Reg in the source tenancy
  • TargetTeamObjectID – The Object ID of the Team / Group in the TargetTenant
  • TargetTenantID – The Tenant ID of the Target tenancy
  • TargetClientID – The Client ID of the App Reg in the Target tenancy
  • MappingFile- The full file path to the mapping file
  • TargetToken – An access token for the target tenancy, more on this below

To generate the Token for the TargetToken parameter, check out this post about requesting a delegated token, I recommend using MSAL.PS.

 .\graph-Teams-Migration-Script.ps1 -SorceTeamObjectID fa9377ca-0824-4da5-aea9-496856298ecc -SourceClientSecret $SourceclientSecret -SourceClientID $SourceclientID -SourceTenantID $SourcetenantID  -TargetClientSecret $TargetclientSecret -TargetClientID $TargetclientID -TargetTenantID $TargettenantID -TargetTeamObjectID 49736ecc-0755-4ec8-abba-6995e7c34a05 -MappingFile C:\temp\Mappingfile.csv -TargetToken $TargetToken.accesstoken

Figure 5: The script in action

The script should look similar to Figure 5 and once complete, you’ll also find a log at “C:\temp” on your machine, the members will be present in the target Team in line with the source and according to the mapping file provided (Figures 6 and 7).

Figure 6: Source Team Members
Figure 7: Target Team Members

Note: There is a delay in Private Channel Membership showing updated in the Teams interface but searching for a user will show the membership is present (Figure 8 and 9)

Figure 8: Teams interface does not show private channel members immediately
Figure 9: Searching for the user confirms they are members


This script is one part of an overall set of tools I have created to support migrations which I will be uploading here over the next few weeks. I highly recommend that you read and customize this script to meet your requirements before running in production. My favourite thing about this script is that it doesn’t rely on exporting members from source and translating that for import. The export and translation is done on the fly and it can be run on a loop as long as a complete mapping file is provided.

Download the script from GitHub here!

One thought on “Migrate Microsoft Team and Private Channel Members with Graph API and PowerShell

  1. Pingback: Dealing with Teams Guest Users During Tenant to Tenant Migrations – Sean McAvinue

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s