When migrating files to Microsoft Teams, the connection to a private or shared channel SharePoint Online site comes into play. The URL naming conventions are different between the two, making programmatic site connections a bit more cumbersome. This post outlines the differences and how to resolve the issue using PnP.PowerShell.

Setting the stage

Independent of any migration tool, at some point you need to connect to a SharePoint site to migrate data into Microsoft Teams. By default, the parent site of a team uses the mailnickname in the URL, e.g.:

https://contoso.sharepoint.com/sites/{mailNickname}

For private channel sites, the following naming convention applies:

https://contoso.sharepoint.com/sites/{mailNickname}-{channelName}

For the URL, the “channelName” element may be altered by replacing certain characters or spaces with hyphens. Pretty straight forward, right? For generating the URL in PowerShell, the parent site URL of the team is used as a base URL and the channelname is appended:

$channelName = $channelName -replace '[@()\[\]\\/;:<>&\s]', ''
$siteURL = "$baseUrl-$channelName"

This worked flawlessly in the migrations I performed thus far.

Enter the shared channel

Now that we have shared channels next to private ones, I initially assumed the same URL naming convention. Boy, was I wrong! The URL naming convention is completely different from private channels, although the same logic for handling exotic characters and spaces applies.

https://contoso.sharepoint.com/sites/{teamDisplayname}-{channelName}

So for teams that have a displayname different from the mailnickname, the code for generating the site URL failed. First thing I thought after discovering this: “Why, Microsoft Why?!”

Being a guy that likes to solves puzzles, a solution must be found. And with the help of the all powerful PnP.PowerShell, that did not take too much time.

A solution for all channel types

Assuming you have already set up an Azure AD app registration for connecting to SharePoint Online and the Microsoft Graph with the required application permissions, you can retrieve the filesfolder object for the default, private or shared channel site, including the URL. Obviously after creating a connection through Connect-PnPOnline using client ID and cert. thumbprint from the app registration and retrieving a team object using Get-PnPTeamsTeam:

[string]$channelFilesFolder = Get-PnPTeamsChannelFilesFolder `
    -Team $team.GroupId `
    -Channel $channelName `
    -Connection $connection | Select-Object -ExpandProperty 'webUrl'

This outputs into something like the following example:

https://contoso.sharepoint.com/sites/Team-Finance-Department-Partners/Shared%20Documents

As we usually work with the site URL, we only need to split and rejoin it from the ‘/’-occurrences:

($channelFilesFolder -split '/')[0..4] -join '/'

The end result is a clean site URL:

https://contoso.sharepoint.com/sites/Team-Finance-Department-Partners

This is a more dynamic approach to retrieving the site URL for the shared, private and even the default channel. In the meanwhile I’m still wondering why the Url naming convention is so different between private and shared channels.
To integrate this into a migration factory approach, the PowerShell-function would look something like the following:

function Get-FactorySPOChannelSiteUrl {
    [OutputType('string')]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [PSObject]$team,
        [Parameter(Mandatory = $true)]
        [string]$channelName,
        [Parameter(Mandatory = $true)]
        $connection)
    try {
        #Get weburl from the channelFilesFolder object
        Write-Output "Getting site url for channel site ($($team.DisplayName))"
        [string]$channelFilesFolder = Get-PnPTeamsChannelFilesFolder `
            -Team $team.GroupId `
            -Channel $channelName `
            -Connection $connection | Select-Object -ExpandProperty 'webUrl'
        #Return the site URL for the channel excluding the library and channel folder elements
        $($channelFilesFolder -split '/')[0..4] -join '/'
    }
    catch {
        #Errorhandling logic
    }
}