April 9, 2020

Cloning Teams Channels and Tabs without /clone REST endpoint

Series

This is series of blog articles showing how to clone Teams Channels and Tabs without using the Clone REST endpoint or direct REST queries. Except we use the Microsoft.Graph (3.1.0). First post discusses things in general, details about different tab types are separated to individual articles.

  1. Cloning Teams Channels and Tabs without /clone REST endpoint <<YOU ARE HERE>>
  2. Cloning Planner Tab without /clone REST endpoint
  3. Cloning OneNote Tab without /clone REST endpoint
  4. Cloning Web Tab without /clone REST endpoint (TBD)
  5. Copying Teams Files tab content using MoveCopyUtil

Task

As cloning a Microsoft Teams Team using the https://graph.microsoft.com/v1.0/teams/{sourceeamId}/clone has few things that might surprise you (such as described here), you will anyway need go through all cloned tabs to verify they’re working. Why not instead create a fresh Channel based on the source Team Channel, then enumerate source Team tabs and recreate new tabs in the destination Channel based on the properties of the source Tab?

Solution

Enough chit-chat, lets dive in!

What happens in the code is that we first get source Team, Channels, and their Tabs. Having already created new Group and related Team (out of scope of this specific article), we start creating Channels in the new Team based on source Team Channels.

Reason why I didn’t include details of creating new Group and related Team here is because you could apply this same logic on existing Teams as well. Maybe if you’d like to push new Channel or Tab to existing Teams? Welcome, this code will work just nice.

After creating each Channel, we loop through the source Channel Tabs. Based on the source Tab type, and also how it is eventually been configured, we recreate identical Tab.

Good thing in this way of doing this is that you have full control of the Tab cloning operation.Then again it also means that you will have to implement that full control logic and program code.

Depending on the tab type, term cloning might be a bit misleading, as in all cases we’re not cloning tab content but creating new background resource (such as Planner Plan or OneNote Notebook) and just binding to it from the tab. Maybe think of cloning here as cloning the Team structure including channels and tabs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
logger?.LogInformation("Getting source team channels and tabs");

var sourceTeamChannels = await retry.ExecuteAsync(async () =>
{
    return await graphClient.Teams[sourceTeamId].Channels.Request().GetAsync();
});

foreach (var chan in sourceTeamChannels)
{
    chan.Tabs = await retry.ExecuteAsync(async () =>
    {
        return await graphClient.Teams[sourceTeamId].Channels[chan.Id].Tabs.Request().Expand("TeamsApp").GetAsync();
    });
}

foreach (var sourceChannel in sourceTeamChannels)
{
    var newChannel = await retry.ExecuteAsync(async () =>
    {
        logger?.LogInformation($"Adding channel {sourceChannel.DisplayName}. {newGroup.Id}");
        return await graphClient.Teams[newTeamId].Channels.Request().AddAsync(new Microsoft.Graph.Channel()
        {
            ODataType = null,
            DisplayName = sourceChannel.DisplayName,
            Description = sourceChannel.Description
        });
    });

    channelId = newChannel.Id;

    // here you should remove the Wiki tab (see separate article)

    foreach (var sourceTab in sourceChannel.Tabs)
    {
        // here you will have switch or if/else construct to determine source tab type
        if (sourceTab.TeamsApp.Id.Equals(TeamsAppId./*TAB TYPE*/))
        {
            // and actual tab type specific creation and configuration logic
        }
    }
}

By the way, you’ll be needing this Exception handler later, as tab creation will throw specific ServiceExceptions even though creation succeeds.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
private static void HandleTabCreationException(ServiceException ex, ILogger logger, TeamsTab sourceTab)
{
    if (ex.StatusCode.ToString() == "BadRequest" && ex.Error.Message == "Value cannot be null.\r\nParameter name: entity")
    {
        // this is fine: https://stackoverflow.com/questions/59784200/programmatically-creating-teamtab-used-to-work-but-now-gives-error-after-updati
        logger?.LogInformation($"Tab {sourceTab.TeamsApp.Id} created: {sourceTab.DisplayName}");
    }
    else
    {
        logger?.LogInformation($"Error creating tab: {sourceTab.TeamsApp.Id}: {sourceTab.DisplayName}");
    }
}

Also, extending Laura’s TeamsAppId class a bit will come handy, thank you Laura!

1
2
3
4
5
6
7
8
public static class TeamsAppId
{
    public static readonly string OneNote = "0d820ecd-def2-4297-adad-78056cde7c78";
    public static readonly string Planner = "com.microsoft.teamspace.tab.planner";
    public static readonly string SharePoint = "2a527703-1f6f-4559-a332-d8a7d288cd88";
    public static readonly string Web = "com.microsoft.teamspace.tab.web";
    public static readonly string Wiki = "com.microsoft.teamspace.tab.wiki";
}

No comments:

Post a Comment