Über Microsoft Graph und PowerShell können Nachrichten aus einem bestehenden Teams Kanal in ein neues Team migriert werden. Teams Migration hat den Beta-Status verlassen. Eine gescriptete Anleitung für die Migration.
In manchen Fällen ist es notwendig einen Kanal und alle Unterhaltungen in ein neues Team zu migrieren. Im besten Fall soll es Metadaten wie Ersteller, Zeit und anderes übernehmen. Bisher war das oft nur eingeschränkt oder mit Tools von Drittherstellern möglich. Im April hat Teams Migration den Beta-Status verlassen. Damit ist eine Migration über Microsoft Graph möglich. Bevor jemand eine Migration plant sind vorab ein paar Punkte zu beachten.
Dinge die jemand vor einer Migration wissen sollte:
- Eine Migration ist nur in ein neues Team möglich das während der Migration über Graph angelegt wird.
- Bei der Erstellung des neuen Teams wird es in einen Migration State gesetzt. Dieser State kann nur über den Migrationsmodus von Graph gesetzt werden.
- Schlägt bei der Migration etwas fehl war es das. Das neue Team muss gelöscht und mit der Migration neu begonnen werden. Die alten Inhalte bleiben während der Migration unverändert. Damit kostet es praktisch nur etwas Zeit und Zusatzaufwand.
- Solange der Migration State nicht abgeschlossen ist kann jemand migrieren was gewünscht ist. Das neue Team ist während dem Status für andere Personen nicht einsatzbereit. Wurde die Migration abgeschlossen muss der Migration State beendet werden.
- Wurde ein Migration State beendet kann dieser nicht erneut aktiviert werden. Vergessene Inhalte lassen sich nachträglich nicht über eine weitere Migration ergänzen. Ein Neustart ist erforderlich.
- Beachten von Throttling der Migration API: Messages import at 5 RPS per channel (requests per second)
- Microsoft schreibt es könnten zukünftig weitere Kosten für solche Migrationen anfallen.
Alle Inhalte einer Unterhaltung sind noch nicht möglich und manche Objekte sind nicht vorgesehen. Reaktionen kann es beispielsweise im Moment nicht migrieren. Microsoft hat dafür eine Tabelle erstellt.
Passend zur Situation möchten meine virtuellen Kollegen aus dem Marketing für sich ein eigenes Team. In der Demo migriere ich den Kanal Marketing in neues Team Marketing.
Für die Migration wird eine Azure App Registration benötigt. Wie du so eine App mit Client Secret oder Zertifikat erstellen kannst beschreibe ich hier. Notiere dir für die App die Application ID und den Client Secret. Für die Migration der Teams Kanalunterhaltungen müssen Berechtigungen vom Typ Delegated und Application gemeinsam eingesetzt werden. Achte darauf die korrekten Berechtigungstypen auszuwählen.
Für die Berechtigungen gehe ich von den Annahmen aus:
- Der ausführende Account hat die Rolle Teams Admin oder Global Admin.
- Der ausführende Account ist während der Migration Mitglied in dem alten Team.
Channel.ReadBasic.All | Application | Der Account muss für eine Migration die bestehenden Kanäle aus dem alten Team auslesen können. Application Permission sind erforderlich da der Account sonst am Ende den Migration State des neuen Kanals nicht abschliessen könnte. |
ChannelMessage.Read.All | Delegated | Für die Migration muss der Account die Unterhaltungen im alten Kanal lesen können. Die Berechtigung darf nicht Application sein. Graph hat für Teams besonders geschützte APIs. ChannelMessage.Read.All ist eine davon. Für Application Permissions müsste bei Microsoft ein gesonderter Antrag erstellt werden. |
Group.Read.All | Delegated | Der Account muss die Teams auslesen können in denen er Mitglied ist. |
TeamMember.ReadWrite.All | Delegated | Wird ein neues Team über die Migration Methode erstellt hat es keine Mitglieder. Die Mitglieder sind nach der Migration zu ergänzen. Mit der Berechtigung ist es möglich. |
Teamwork.Migrate.All | Application | Die primäre Berechtigung für Teams Migration. Für die Berechtigung steht nur der Typ Application zur Auswahl. |
User.Read | Delegated | Standardberechtigung, wird bei der Erstellung einer Azure App automatisch hinzugefügt. |
Die Konfiguration sollte am Ende in der Art aussehen.
Wurde alles eingerichtet kann es losgehen. Am Ende von diesem Post gibt es das vollständige Script via GitHub zusammengefasst.
- Wie immer muss zuerst über die App Registration ein Authentication Token bezogen werden. Du könntest mein Command Get-TAMSAuthToken einsetzen. Für die Migration benötigt es zwei Token. Einen für die Berechtigung Application und einen für Delegated.
$AuthHeaderDelegated = Get-TAMSAuthToken -ClientSecret "[ClientSecret]" -Tenantname "[Tenant].onmicrosoft.com" -AppId "[ApplicationID]" -API Graph -PermissionType Delegated -AppRedirectUri "http://localhost/myapp/" -ReturnAuthHeader
$AuthHeaderApplication = Get-TAMSAuthToken -ClientSecret "[ClientSecret]" -Tenantname "[Tenant].onmicrosoft.com" -AppId "[ApplicationID]" -API Graph -PermissionType Application -ReturnAuthHeader
- Zuerst müssen die Kanalinformationen aus dem alten Team ausgelesen werden. Prüfe die ID deines alten Teams und ergänze den Wert. Zur Einfachheit könntest du im Teams Admin Center das betroffene Team öffnen. In der Browserzeile wird es die Guid des Teams anzeigen.
$OldTeamID = "[YourOldTeamID]"
- Auslesen aller Kanäle des alten Teams, danach filtere ich den Kanal für Marketing.
$Url = "https://graph.microsoft.com/v1.0/teams/$OldTeamID/channels"
$Result = Invoke-RestMethod -Method Get -uri $Url -Headers $AuthHeaderApplication
$OldTeamChannel = $Result.value | ?{$_.displayName -eq "Marketing" }
$OldTeamChannelID = $OldTeamChannel.id
- Auslesen aller Nachrichten im Kanal Marketing.
Beachte, sollten es viele Nachrichten sein wird es Graph pagen. Du musst darauf achten ob es mehrere Seiten zurückgibt. Für meine Demo sind es zu wenige Nachrichten.
$Url = "https://graph.microsoft.com/v1.0/teams/$OldTeamID/channels/$OldTeamChannelID/messages"
$OldTeamChannelMessages = Invoke-RestMethod -Method Get -uri $Url -Headers $AuthHeaderDelegated
$OldTeamChannelMessages = $OldTeamChannelMessages.value
- Erstellen des neuen Teams im Status Migration.
Für die Neuerstellung müssen Daten wie Namen und Erstelldatum vermerkt sein. Als Erstelldatum nehme ich die aktuelle Zeit, alternativ könntest du auch das Erstelldatum von deinem alten Kanal nehmen.
$Date = (Get-Date).ToUniversalTime()
$Date = (Get-Date $Date -Format u).Replace(" ","T")
$Body = @{
"@microsoft.graph.teamCreationMode" = "migration"
"[email protected]" = "https://graph.microsoft.com/v1.0/teamsTemplates('standard')"
"displayName" = "Marketing"
"createdDateTime" = $Date
}
$Url = "https://graph.microsoft.com/v1.0/teams"
$NewTeam = Invoke-WebRequest -Method POST -URI $Url -Headers $AuthHeaderApplication -Body ($Body | ConvertTo-Json) -ContentType 'application/json'
Hier hat die API im Moment einen Bug. $NewTeam sollte als Response die Guid des neuen Teams inkludieren. Aktuell ist die Response leer. Ich erstellte dafür einen Issue.
***********
Update vom 31. Mai 2021:
Durch den Tipp von Microsoft sollte die Anfrage mit Invoke-WebRequest abgesetzt werden. Damit gibt es einen Header zurück der die Guid des neuen Teams inkludiert. Invoke-RestMethod gibt den Header zurück sollte die Anfrage fehlerhaft sein.
Da die Guid im Header inkludiert ist wurde das Script entsprechend angepasst.
$NewTeamID = $NewTeam.Headers["Content-Location"]
$NewTeamID = $NewTeamID.Substring($NewTeamID.IndexOf("'") + 1)
$NewTeamID = $NewTeamID.Substring(0,$NewTeamID.LastIndexOf("'"))
***********
- Im nächsten Schritt erstelle ich im neuen Team einen neuen Kanal Design, in den die Unterhaltungen aus dem alten Kanal Marketing zu migrieren sind. Der Kanal wird ebenfalls im Status Migration angelegt.
$Body = @{
"@microsoft.graph.channelCreationMode" = "migration"
"displayName" = "Design"
"membershipType" = "standard"
"createdDateTime" = $Date
}
$Url = "https://graph.microsoft.com/v1.0/teams/$NewTeamID/channels"
$NewTeamChannel = Invoke-RestMethod -Method POST -uri $Url -Headers $AuthHeaderApplication -Body ($Body | ConvertTo-Json) -ContentType 'application/json'
$NewTeamChannelID = $NewTeamChannel.id
- Jetzt ist das neue Team soweit um die Migration der Nachrichten zu starten. Damit es alle Nachrichten migriert ist es eine Schleife die alle bestehenden Nachrichten abarbeitet.
Wichtig, bei vielen Nachrichten musst du ab dem Punkt auf das Throttling achten: Messages import at 5 RPS per channel (requests per second)
$MessageCount = 1
foreach( $ChannelMessage in $OldTeamChannelMessages )
{ Write-Host ("Adding channel message $MessageCount of " + $OldTeamChannelMessages.count)
$OldTeamChannelMessageID = $ChannelMessage.id
$MessageBody = @{"createdDateTime" = (get-date $ChannelMessage.createdDateTime -Format u).Replace(" ","T") }
$MessageBody.from = @{}
$MessageBody.from.user = @{ "id" = $ChannelMessage.from.user.id; "displayName" = $ChannelMessage.from.user.displayName; "userIdentityType" = $ChannelMessage.from.user.userIdentityType }
$MessageBody.body = @{ "contentType" = $ChannelMessage.body.contentType; "content" = $ChannelMessage.body.content }
$Url = "https://graph.microsoft.com/v1.0/teams/$NewTeamID/channels/$NewTeamChannelID/messages"
$NewChannelMessage = Invoke-RestMethod -Method POST -uri $Url -Headers $AuthHeaderApplication -Body ($MessageBody | ConvertTo-Json) -ContentType 'application/json; charset=utf-8'
$NewChannelMessageID = $NewChannelMessage.id
- Weiters kann jede Nachricht mehrere Antworten inkludieren. Somit prüft es jede alte Nachricht ob es Antworten inkludiert, geht die Antworten erneut alle durch und fügt es der neuen Nachricht als Antwort an.
$Url = "https://graph.microsoft.com/v1.0/teams/$OldTeamID/channels/$OldTeamChannelID/messages/$OldTeamChannelMessageID/replies"
$OldTeamChannelMessageReplies = Invoke-RestMethod -Method Get -uri $Url -Headers $AuthHeaderDelegated
$Url = "https://graph.microsoft.com/v1.0/teams/$NewTeamID/channels/$NewTeamChannelID/messages/$NewChannelMessageID/replies"
$MessageReplyCount = 1
foreach( $MessageReply in $OldTeamChannelMessageReplies.value )
{
Write-Host ("Adding message reply $MessageReplyCount of " + $OldTeamChannelMessageReplies.value.count)
$ReplyBody = @{"createdDateTime" = (get-date $MessageReply.createdDateTime -Format u).Replace(" ","T") }
$ReplyBody.from = @{}
$ReplyBody.from.user = @{ "id" = $MessageReply.from.user.id; "displayName" = $MessageReply.from.user.displayName; "userIdentityType" = $MessageReply.from.user.userIdentityType }
$ReplyBody.body = @{ "contentType" = $MessageReply.body.contentType; "content" = $MessageReply.body.content }
$Result = Invoke-RestMethod -Method POST -uri $Url -Headers $AuthHeaderApplication -Body ($ReplyBody | ConvertTo-Json) -ContentType 'application/json; charset=utf-8'
$MessageReplyCount++
}
$MessageCount++
}
- Damit wurde die Migration des alten Kanals Marketing in den neuen Kanal Design abgeschlossen.
- Als einen der letzten Schritte muss der Migration Status für die Kanäle und das Team aufgehoben werden. Hier ist zu beachten, es wurde zwar nur ein neuer Kanal Design erstellt, das Team hat aber noch den Standardkanal General. Alle Kanäle sind aktuell im Status Migration. Bevor ein Team den Migration State verlassen kann müssen alle Kanäle die Migration abschliessen. Ich gehe also alle Kanäle durch und schliesse die Migration ab.
$Url = "https://graph.microsoft.com/v1.0/teams/$NewTeamID/channels"
$AllNewTeamChannels = Invoke-RestMethod -Method Get -uri $Url -Headers $AuthHeaderApplication
foreach($Channel in $AllNewTeamChannels.value )
{ $Url = ("https://graph.microsoft.com/v1.0/teams/$NewTeamID/channels/" + $Channel.id + "/completeMigration")
$Result = Invoke-RestMethod -Method POST -uri $Url -Headers $AuthHeaderApplication
}
- Nachdem die Migration der Kanäle abgeschlossen ist wird die Migration für das Team ebenfalls abgeschlossen.
$Url = "https://graph.microsoft.com/v1.0/teams/$NewTeamID/completeMigration"
$Result = Invoke-RestMethod -Method POST -uri $Url -Headers $AuthHeaderApplication
- Zur Erinnerung, das neue Team hat im Moment weder Besitzer noch Mitglieder. Nachdem die Migration abgeschlossen wurde können Mitglieder hinzugefügt werden. In meinem Beispiel füge ich meinen Account als Besitzer hinzu. Passe den UserPrincipalName entsprechend an. Die restlichen Aktivitäten sind ab dem Zeitpunkt über die Teams Clients möglich.
$Body = @{
"@odata.type" = "#microsoft.graph.aadUserConversationMember"
"roles" = @("owner")
"[email protected]" = "https://graph.microsoft.com/v1.0/users('[UserPrincipalName]')"
}
$Url = "https://graph.microsoft.com/v1.0/teams/$NewTeamID/members"
$Result = Invoke-RestMethod -Method POST -uri $Url -Headers $AuthHeaderDelegated -Body ($Body | ConvertTo-Json) -ContentType 'application/json'
- Meine Kollegin Patti kontrolliert ihr neues Team und sieht die Migration der Unterhaltungen war erfolgreich. 👍👏 Aus dem alten Team übernahm es die Unterhaltungen, Antworten, Datum und Ersteller.
Zum Abschluss das Script über GitHub zusammengefasst.