Teams Wiki internals and possible migration

Published on Wednesday, November 30, 2022

Introduction

I knew this was a long-time request by the community and that there isn't an out-of-the-box method to do this, I was asked if I could find a way to migrate a Wiki tab from one channel in one team to another channel in another team.

I found this Tech Community post, I tried to use it but SharePoint Designer was not an option, but it helped me understand a few things, so thanks to the person behind it https://techcommunity.microsoft.com/t5/microsoft-teams/how-to-copy-teams-wiki-pages-answered/m-p/2785567.

I spent several days figuring it out how Teams handles wiki tabs, from creation, to updating and to removal, so I decided to share my findings with the community, this is provided as-is, with no guarantee whatsoever, as you will see it is a very craft-made process.

The Wiki tab

Source Wiki

When a Teams Wiki tab is added to a Channel, the following is created on the SharePoint site belonging to the Team:

  • A SharePoint Document library named Teams Wiki Data

https://contoso.sharepoint.com/sites/TeamName/Teams%20Wiki%20Data/Forms/AllItems.aspx

This document library contains .mht files generated by the Wiki tab when someone edits the wiki, it will also contain the images inserted.

  • A SharePoint List, like this, (but it is hidden by default)

https://contoso.sharepoint.com/sites/TeamName/Lists/19pYZDaUICINpaAq7iFZpRNyuEGeXK8gqb5yUC3ja4oc1threa

(The list gets its name from the Team Channel id)

SourceSite Contents

Teams will also save this information about the tab and we can query it using Graph API:

HTTP Request

https://graph.microsoft.com/v1.0/teams/2c009003-bf45-47ab-ac9d-fe4f3f3967f5/channels/19:Kb8nmcctoGWrOYfiB-Cf7wVgX8Lnk0UL8BH-WB6s7hQ1@thread.tacv2/tabs
{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#teams('d925b426-bcff-4d41-8e40-dda0bd157044')/channels('19%3AXV4JrShhjXTNB_EVNNZyoBiMRQkXcWFECKy_aFmZ1Qs1%40thread.tacv2')/tabs",
    "@odata.count": 1,
    "value": [
        {
            "id": "e06b5ed7-404b-4a9e-b9d8-608d3b456bd5",
            "displayName": "Wiki",
            "webUrl": "https://teams.microsoft.com/l/channel/19%3aXV4JrShhjXTNB_EVNNZyoBiMRQkXcWFECKy_aFmZ1Qs1%40thread.tacv2/tab%3a%3ae06b5ed7-404b-4a9e-b9d8-608d3b456bd5?label=Wiki&groupId=d925b426-bcff-4d41-8e40-dda0bd157044&tenantId=239cf0aa-5769-4830-bda9-8eb6f978424e",
            "configuration": {
                "entityId": null,
                "contentUrl": null,
                "removeUrl": null,
                "websiteUrl": null,
                "hasContent": true,
                "wikiTabId@odata.type": "#Int64",
                "wikiTabId": 2,
                "dateAdded": "2022-11-30T12:21:37.605Z",
                "isPrivateMeetingWiki": false,
                "meetingNotes": false,
                "scenarioName": "wiki_init_context"
            }
        }
    ]
}

See the property named wikiTabId? that's an important one and it is used by Teams to identify a specific Teams Wiki tab within the channel, as you might know, we can have more than one Wiki per channel, and if we create 3 Wiki tabs, we should have wikiTabId 1, 2 and 3, those numbers are assigned incrementally by Teams, this wikiTabId is important because, it is referenced in the list I mentioned is created in the site, but we'll return to this later.

Requirements

Install the latest PnP PowerShell module version, you can check here https://msshells.net, we will use it for several things. I won't go into the details on how to use PnP PowerShell, there are good resources for that.

The scenario

  • Two Team's teams, let's call them SourceTeam and DestinationTeam
  • One wiki per team

Procedure

The DestinationTeam has to have at least an empty Wiki, so it creates the Teams Wiki Data DL for us and also the SharePoint list, let's call these placeholder DL and List. So create an empty Wiki tab on DestinationTeam using the regular procedure.

Then, use PnP PowerShell to connect to the source site:

connect-pnponline -Interactive -Url https://contoso.sharepoint.com/sites/SourceTeam

Get the List name by calling Get-PnpList

Get-PnpList

The one we are looking for is named something like this "19:XV4JrShhjXTNB_EVNNZyoBiMRQkXcWFECKy_aFmZ1Qs1@thread.tacv2_wiki"

Get the template of the list and also add the data to it, customize the $list variable and if you want the $template location.

$template = ".\sourceTeamWiki.xml"
$list = "19:XV4JrShhjXTNB_EVNNZyoBiMRQkXcWFECKy_aFmZ1Qs1@thread.tacv2_wiki"
Get-PnPSiteTemplate -Out $template -ListsToExtract $list -Handlers Lists
Add-PnPDataRowsToSiteTemplate -Path $template -List $list

Unhide the List with Set-PnpList

Set-PnpList -Identity bdd7b031-57f6-47fd-9922-67cc1c68cb6e -Hidden:$false

As we will probably need to review it on the site.

Now, let's do the same with the destination List, so we will have two xml files to compare.

$template = ".\destinationTeamWiki.xml"
$list = "19:7BvdpssdDqtYTSLvKDRRUlF-RKLX5XUMnfSvinc4lFA1@thread.tacv2_wiki"
Get-PnPSiteTemplate -Out $template -ListsToExtract $list -Handlers Lists
Add-PnPDataRowsToSiteTemplate -Path $template -List $list

Open both .xml files, compare the structure and get familiar with how things are organized there.

On the source .xml file, we need to replace any occurrence of the list guid of the source with the guid of the destination (in the 2nd xml file), for example:

19:XV4JrShhjXTNB_EVNNZyoBiMRQkXcWFECKy_aFmZ1Qs1@thread.tacv2_wiki

with

19:7BvdpssdDqtYTSLvKDRRUlF-RKLX5XUMnfSvinc4lFA1@thread.tacv2_wiki

and

19XV4JrShhjXTNB_EVNNZyoBiMRQkXcWFECKy_aFmZ1Qs1thre

with

197BvdpssdDqtYTSLvKDRRUlFRKLX5XUMnfSvinc4lFA1threa

Save this .xml file as migratedTeamWiki.xml or something like that, browse to the destination site and delete the empty list (be sure not to be on the source site!)

Grab the PnP connection against the destination Sharepoint Site and import it into the site with Invoke-PnPSiteTemplate

Invoke-PnPSiteTemplate -Path C:\temp\Wiki\migratedTeamWiki.xml

This should leave you with a list of the same name on the destination site.

Browse to both source and the destination's site Teams Wiki Data Document Library, download and copy all the mht files from the source Teams Wiki Data to the destination's Teams Wiki Data.

TeamsWikiData Contents

Open the Team's destination team and clic on the Wiki tab, it should load the migrated Wiki, if everything was done correctly, if something looks odd, review the steps taken.

Developer tools

If we dig deeper activating the browser's developer tools, we can see that, when the wiki loads, it is using this request

https://contoso.sharepoint.com/sites/DestinationTeam/_api/web/lists/getbytitle('19:7BvdpssdDqtYTSLvKDRRUlF-RKLX5XUMnfSvinc4lFA1@thread.tacv2_wiki')/items?$filter=(Id eq '10' or (wikiCanvasId eq '10' and wikiDeleted eq 'false'))&$top=5000

(The HTTP url is already decoded)

If we look closer, it queries the Wiki list and filters by

  • Id eq 10
  • wikiCanvasId eq 10
  • wikiDeleted eq false

(It will differ in your case)

What I don't understand is how the wiki will still load when the Id does not eq what Teams is expecting, this Id corresponds to the wikiTabId I mentioned earlier, and unfortunately, there is no supported way of updating it:

HTTP Request:

https://graph.microsoft.com/v1.0/teams/2c009003-bf45-47ab-ac9d-fe4f3f3967f5/channels/19:Kb8nmcctoGWrOYfiB-Cf7wVgX8Lnk0UL8BH-WB6s7hQ1@thread.tacv2/tabs/6dd527de-ee8e-4f65-a389-ca8920adf3e0
{
    "configuration": {
        "wikiTabId": 1
    }
}
{
    "error": {
        "code": "BadRequest",
        "message": "Setting the tab configuration for app 'com.microsoft.teamspace.tab.wiki' is not supported.",
        "innerError": {
            "message": "Setting the tab configuration for app 'com.microsoft.teamspace.tab.wiki' is not supported.",
            "code": "InvalidRequest",
            "innerError": {},
            "date": "2022-11-25T20:19:19",
            "request-id": "da8fd32f-f0e4-4f11-b7f3-bee20ddb2b30",
            "client-request-id": "58eef061-4999-feda-86d6-44105932a1c5"
        }
    }
}

For more information about that Graph API Call see here.

So Teams will keep looking for whatever wikiTabId has in its own configuration, and also, unfortunately, Microsoft does not provide much information about this, as the effort and recommendations now seem to be to move on to OneNote, and this might be the small detail that makes this whole process still not perfect (?).

Extra

If you want to dig deeper into this, browse to the List in the source and destination site and create a SharePoint List view adding this columns:

Custom List View

Final thoughts

If you have to do this because maybe you can't migrate content manually, I think you can follow this procedure but, test, test and test, until you get the desired results, and if you find an error or improvement to this guide, please, share it with me so I can update the post and share with the community.

comments powered by Disqus