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.
- Cloning Teams Channels and Tabs without /clone REST endpoint
- Cloning Planner Tab without /clone REST endpoint
- Cloning OneNote Tab without /clone REST endpoint
- Cloning Web Tab without /clone REST endpoint (TBD)
- Copying Teams Files tab content using MoveCopyUtil <<YOU ARE HERE>>
As part of Teams provisioning, content of Files tab needed to be copied from source Team to the destination Team. As you might know, in practice it just means copying folders and files across SharePoint site collections.
Microsoft.SharePoint.Client.MoveCopyUtil will do the trick UNLESS a folder name contains special characters, see here for example. This has been an issue for long in CSOM, and if at all possible, I recommend not having special characters in folder names, and not in file names either for that matter.
If you must support special character scenarios, it is doable, but will require carefully splitting and constructing the query parameters and effectively means you need to loop and create/copy folders and files one by one. I’ve spent some time creating SPFx solution that manages to do this, and I recommend thinking not twice, but ten times before agreeing to support special characters in this case. Good thing is that in this Teams provisioning scenario you will have control over the source Team and should be able to work around any needs for special characters.
Explanation of the code
What happens in this code can probably be read quite well by looking at the LogInformation entries, but the idea is that we get hold of the Drive item of the source and destination Group. Drive.WebUrl is then absolute URL to the document library where Teams File tab contents are in their own sub-folders.
As a reminder, each File tab in a Team Channel maps to one root level folder (Channel name = folder name) in that document library you find at Drive.WebUrl.
Another nice thing about Group.Drive is that whenever we get hold of that, and it is not returning HTTP 404, we know Group provisioning (the process that runs in the background and creates all back-end dependencies, such as the SharePoint site) has been completed.
retry object is Polly’s AsyncRetryPolicy and ensures retry logic in case provisioning would not be yet ready at this point. It will retry the call as configured, in my case every 5 seconds, until the .GetAsync() completes successfully.
Next up there is some ugly parsing of the source document library path, namely we’re removing the document library part from the URL, leaving us with URL of the SPWeb. There are many ways of doing this, and I admit this one is quite verbose way of doing it but doesn’t at least require additional HTTP query.
Then we get to the actual point, initializing SharePoint context using the source SPWeb, and credentials stored in Azure Key Vault.
After getting all the root level folders (each corresponding a Channel, yes, good), we loop through each folder item and use MoveCopyUtil.CopyFolder to copy it to destination document library. Just remember to set the MoveCopyOptions to KeepBoth, indicating this is a Copy operation and not Move operation.
I felt more comfortable doing .ExecuteQuery for each folder, although it means few more HTTP calls, but allows improved error handling in case of those special characters, or some other problems that might occur during the copy operation.