Microsoft Teams | Provisioning private channels
So it is a most common known problem that when you create automated a private channel, there will be no Sharepoint site provisioned until you click Files in the teams app. I Found a solution.
Situation
We provision a new team and add some private channels. To these private channels, we would like to migrate some data. If the private teams site is not yet provisioned, you will not get a WebUrl for this site. No WebUrl, is no destination for a file share migration. Microsoft Sharepoint Migration Tool but also Sharegate needs this url to know the destination.
Solution
The provisioning of teams and the private channel, we need Powershell. Powershell, Graph API and an app registration with application permissions will do the trick. After the creation of a Team, the drive will not be ready. This will be when you click on “Files” in the General tab or when you create something on the drive. If the drive in General is ready, the drive in the Private Channel will also be ready instantly. So after creating a new team, we create temporary a folder with Graph API and delete that folder also. After that we create a private channel.
App registration
Create an app registration in Azure Active Directory with the permissions:
- Channel.Create
- Files.ReadWrite.All
- Sites.Read.All
- Team.Create
- User.Read
These permissions are enough to do the trick. Provide it with an Admin Consent and you are all set.
Custom PowerShell functions
I Wrote some custom PowerShell functions to help me in my scripts. This time I will share them with you.
Get-TokenForGraphApi
A PowerShell function to get the token from Graph API. It needs the input of:
- AppId
- Clientsecret
- TenantId
function Get-TokenForGraphAPI {
param(
[Parameter(Mandatory)]
[string]$appid,
[Parameter(Mandatory)]
[string]$clientsecret,
[Parameter(Mandatory)]
[string]$tenantid
)
$Body = @{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
Client_Id = $appid
Client_Secret = $ClientSecret
}
$Connection = Invoke-RestMethod `
-Uri https://login.microsoftonline.com/$($TenantID)/oauth2/v2.0/token `
-Method POST `
-Body $body
#Get the Access Token
return $connection
}
Create-Teams
A PowerShell function to create a new Team using Microsoft Graph API. It needs input:
- TeamsName (sort of DisplayName)
- Owner (using the UPN is enough)
- Description (just write what it is about the team)
function Create-Teams {
# #Team.Create
param(
[Parameter(Mandatory)]
[string]$Teamsname,
[Parameter(Mandatory)]
[string]$Owner,
[Parameter(Mandatory)]
[string]$Description
)
$userDatabind = "https://graph.microsoft.com/v1.0/users('$($owner)')"
$params = @{
"template@odata.bind" = "https://graph.microsoft.com/v1.0/teamsTemplates('standard')"
displayName = $Teamsname
description = $description
members = @(
@{
"@odata.type" = "#microsoft.graph.aadUserConversationMember"
"user@odata.bind" = $userDatabind
roles = @(
"owner"
)
}
)
}
$body = $params | ConvertTo-Json -Depth 5
$uri = "https://graph.microsoft.com/v1.0/teams"
$header = @{
'Authorization' = "BEARER $($Token.access_token)"
'Content-type' = "application/json"
}
try {
$createTeams = Invoke-Restmethod -Method POST -Uri $uri -headers $header -Body $body
return $createTeams
}
catch {
$webError = $_
$webError = ($weberror | convertFrom-json).error.innererror.code
return $weberror
}
}
Create-Channel
A PowerShell function to create a new Channel within a team using Microsoft Graph API. It needs input:
- TeamsId (you will need to retrieve this after creation of the team)
- ChannelName (sort of DisplayName for the naming of the Channel)
- Description (a description about the Channel)
- Owner (in case of a private channel)
- Type (input can only be: Standard, Private, Shared)
function Create-Channel {
param(
[Parameter(Mandatory)]
[string]$TeamsId,
[Parameter(Mandatory)]
[string]$ChannelName,
[Parameter(Mandatory)]
[string]$Description,
[Parameter(Mandatory)]
[string]$owner,
[Parameter(Mandatory)]
[string]$Type
)
$userDatabind = "https://graph.microsoft.com/v1.0/users('$($owner)')"
$params = @{
"@odata.type" = "#Microsoft.Graph.channel"
membershipType = $Type
displayName = $ChannelName
description = $description
members = @(
@{
"@odata.type" = "#microsoft.graph.aadUserConversationMember"
"user@odata.bind" = $userDatabind
roles = @(
"owner"
)
}
)
}
$body = $params | ConvertTo-Json -Depth 5
$apiUri = "https://graph.microsoft.com/v1.0/Teams/$teamsId/channels"
$header = @{
'Authorization' = "BEARER $($Token.access_token)"
'Content-type' = "application/json"
}
try {
$createChannel = Invoke-Restmethod -Method POST -Uri $apiUri -headers $header -Body $body
}
catch {
$webError = $_
$createChannel = ($weberror | convertFrom-json).error.innererror.code
}
return $createChannel
}
Variables
For input we have variables. We need to define them:
$appid = "YOUR APP ID"
$clientsecret = "YOUR CLIENT SECRET"
$tenantid = "YOUR TENANT ID"
$teamsName = "THE NAME OF THE TEAM"
$channel1 = "Standard Channel"
$channel2 = "Private Channel"
$Description = "THE DESCRIPTION OF THE TEAM"
$owner = "THE UPN OF THE OWNER OF TEAM"
$folderToCreate = "FOLDER TO CREATE"
Getting a token
$Token = Get-TokenForGraphAPI -appid $appid -clientsecret $clientsecret -Tenantid $tenantid
Creating a new team
$team = Create-Teams -Teamsname $teamsName -Owner $owner -Description $Description
Checking if team exists
do {
$Apiuri = "https://graph.microsoft.com/v1.0/teams" + '?$filter=startsWith(displayName, ' + "'$teamsname')"
$Teams = (Invoke-RestMethod -Headers @{Authorization = "Bearer $($Token.access_token)" } -Uri $apiUri -Method Get).value
start-sleep -seconds 2
} while ($Teams.count -eq 0)
Getting the “GENERAL” channelId
$apiUri = "https://graph.microsoft.com/v1.0/Teams/$($teams.id)/channels"
$channels = (Invoke-RestMethod -Headers @{Authorization = "Bearer $($Token.access_token)" } -Uri $apiUri -Method Get).value | Where-Object { $_.DisplayName -eq "General" }
Getting information about the “Drive” in Sharepoint
$apiUri = "https://graph.microsoft.com/v1.0/teams/$($teams.id)/channels/$($Channels.id)/filesFolder"
$drive = Invoke-Restmethod -method Get -headers @{Authorization = "Bearer $($Token.access_token)" } -Uri $apiUri
$driveId = $drive.id
$driveitemId = $drive.parentReference.DriveId
Setting Header for Graph API Post call
$header = @{
'Authorization' = "BEARER $($Token.access_token)"
'Content-type' = "application/json"
}
Building parameters for folder creation
$params = @{
name = $FolderToCreate
folder = @{
}
"@microsoft.graph.conflictBehavior" = "rename"
}
$params = $params | ConvertTo-Json
Creating a folder
$ApiUri = "https://graph.microsoft.com/v1.0/drives/$driveitemid/items/$DriveId/children"
$createFolder = Invoke-Restmethod -Method POST -Uri $apiUri -Headers $header -body $params
Deleting the folder
$apiUri = "https://graph.microsoft.com/v1.0/drives/$driveItemId/items/$($createFolder.id)"
$DeleteFolder = Invoke-RestMethod -Method DELETE -uri $apiUri -Headers $header
Creating Channels
$FirstChannel = Create-Channel -TeamsId $Teams.id -ChannelName $Channel1 -Description $Description -owner $owner -Type "Standard"
$privateChannel = Create-Channel -TeamsId $Teams.id -ChannelName $Channel2 -Description $Description -owner $owner -Type "Private"
Getting WebUrl from Private channel
do {
try {
$folderlocation = Invoke-Restmethod -method Get -headers @{Authorization = "Bearer $($Token.access_token)" } -Uri https://graph.microsoft.com/v1.0/teams/$($teams.id)/channels/$($privateChannel.id)/filesFolder
$mustRetry = 0
}
catch {
$weberror = $_
$mustRetry = 1
Start-Sleep -seconds 5
}
} while (
$mustRetry -eq 1
)
$Folderlocation
Results
So now you can provision automated through Graph API a teams and a private channel without the worry that the storage location will not be available. You can use it in your scripts migrating fileshares to Microsoft Teams.