Wer eine große SharePoint-Migration nicht mit Standard-Tools abbilden kann (warum, steht in Teil 2), landet früher oder später bei der SharePoint Online Migration API — derselben Schnittstelle, die auch ShareGate und SPMT unter der Haube verwenden. Sie ist der einzige Weg, Inhalte mit Versionshistorie, Autoren und Zeitstempeln performant nach SharePoint Online zu importieren, ohne Item für Item über CSOM/REST zu tröpfeln.
Die offizielle Dokumentation deckt aber nur die halbe Wahrheit ab. Dieser Beitrag konsolidiert, was wir in über 50 Reverse-Engineering-Iterationen gegen Live-SharePoint-Online gelernt haben — jeder Punkt durch erfolgreiche End-to-End-Importe verifiziert.
Wie die Migration API grundsätzlich funktioniert
- Container anfordern:
ProvisionMigrationContainersliefert zwei Azure-Blob-Container (Content + Manifeste) samt SAS-URLs und einem AES-256-Schlüssel. - Paket bauen: Die Dateien plus 8 Manifest-XMLs (u. a.
Manifest.xml,UserGroup.xml,SystemData.xml) beschreiben, was wohin importiert wird — inklusive Feldwerten, Versionen, Autoren. - Verschlüsselt hochladen: Jeder Blob wird AES-256-CBC-verschlüsselt hochgeladen.
- Job starten:
CreateMigrationJobEncryptedübergibt Container + Schlüssel; SharePoint importiert serverseitig. - Fortschritt lesen: Eine Azure Queue liefert
JobProgress/JobEnd-Events.
Klingt einfach. Die Fallen stecken in den Details.
Falle 1: Die Verschlüsselung — IV niemals in den Content
Die häufigste Sackgasse (auch in Microsoft-Q&A-Threads): den Initialisierungsvektor vor den Ciphertext zu schreiben. Falsch. Der IV gehört in die Blob-Metadata, der Blob enthält nur den Ciphertext:
internal static (byte[] ciphertext, byte[] iv) EncryptAesCbcPureCiphertext(
byte[] plainBytes, byte[] key)
{
using var aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.KeySize = 256;
aes.Key = key; // Key aus ProvisionMigrationContainers
aes.GenerateIV(); // frischer IV pro Blob!
using var encryptor = aes.CreateEncryptor();
var cipher = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
return (cipher, aes.IV);
}
Der Upload-Ablauf pro Blob — und jeder Schritt ist Pflicht:
1. PUT <blob> Body = NUR Ciphertext
Content-MD5 = MD5 über den CIPHERTEXT (nicht Plaintext!)
2. PUT <blob>?comp=metadata x-ms-meta-IV = Base64(IV)
3. PUT <blob>?comp=snapshot kein Body
Zwei Konsequenzen, die uns Stunden gekostet haben:
- MD5 über den Ciphertext, nicht über den Klartext — sonst lehnt Azure den PUT mit
400 Content-MD5 mismatchab. - Der Snapshot ist kein Nice-to-have. Ohne Snapshot liest der Migration-Worker den Blob gar nicht erst — der Job läuft kommentarlos in den Timeout. Und der Snapshot muss nach dem Metadata-PUT erfolgen, sonst fehlt dem Worker der IV.
Das Fehlerbild Data at the root level is invalid. Line 1, position 1. bedeutet übrigens fast immer: IV im Body statt in den Metadaten — der Worker entschlüsselt Müll und versucht, ihn als XML zu parsen.
Falle 2: Manifest.xml — der strenge Deserializer
Die Schema-Dokumentation zeigt viele Attribute, die der Import-Worker schlicht ignoriert oder an denen er scheitert. Erkenntnisse aus der Praxis:
SPFile.Iddarf nicht gleichSPListItem.Idsein — sonst kollabieren Datei und Listitem zu einem Eintrag.SystemData.xmlbrauchtObjectsProcessed, sonst startet der Job nicht sauber.- Ordnerhierarchien müssen parents-first im Manifest stehen.
- Feld-Werte haben exakte Wire-Formate: User-Felder als
id;#login(und der User muss inUserGroup.xmlstehen), Lookups alsid;#text, Taxonomie mit echten Term-GUIDs.
Falle 3: Der Zwei-Versionen-Bug
Unser Lieblings-Service-Bug: Eine Datei mit genau zwei Versionen verliert beim Import ihre Historie — beide werden zu v1.0. Bei drei und mehr Versionen funktioniert das <Versions>-Pattern korrekt. Wer, wie wir, Versionsketten dedupliziert (Teil 5), erzeugt genau solche Zwei-Versionen-Ketten ständig — und braucht eine bewusste Strategie dafür.
Ebenfalls dokumentationswürdig:
- Autoren-Erhalt ist best-effort: Ist ein historischer Benutzer nicht mehr auflösbar, fällt der Worker still auf den System-Account zurück und meldet nur eine Warning.
JobEnd-Zähler kommen teils als Strings: WerFilesCreatednaiv als Zahl parst, liest0und hält erfolgreiche Jobs für leer. Erst tolerantes Parsen macht die Job-Telemetrie belastbar.- Nur historische Versionen ohne aktuelle im
<Versions>-Block → Worker verwirft die komplette Historie.
Falle 4: Grün heißt nicht fertig
Die wichtigste strategische Lektion: Ein erfolgreicher Migration-Job beweist nicht, dass alles angekommen ist. Teilfehler, verworfene Versionen, stille Fallbacks — all das steckt bestenfalls in Warnings. Wir haben deshalb ein eigenes Abnahme-Gate gebaut: Nach jedem Projekt läuft ein Parity-Check, der jede Quelldatei gegen das Ziel abgleicht (missing=0, SizeMismatch=0), mit SHA256-Stichproben bei Verdacht. Erst dieser Report — nicht der Job-Status — ist unser Abnahme-Artefakt.
Fehler-Lexikon (Auszug)
| Symptom | Tatsächliche Ursache |
|---|---|
Data at the root level is invalid | IV im Blob-Body statt in x-ms-meta-IV |
| Job läuft in Timeout, keine Fehlermeldung | Blob-Snapshot fehlt |
400 Content-MD5 mismatch beim Upload | MD5 über Plaintext statt Ciphertext |
| Versionshistorie fehlt am Ziel | Zwei-Versionen-Bug oder <Versions> ohne aktuelle Version |
| Alle Dokumente gehören dem System-Account | Benutzer nicht in UserGroup.xml / nicht mehr auflösbar |
FilesCreated=0 trotz sichtbarer Dateien | String-codierte Zähler im JobEnd-Event |
Fazit
Die Migration API ist mächtig — Versionen, Autoren, Zeitstempel, hoher Durchsatz — aber sie ist eine Schnittstelle für Werkzeugbauer, keine Endanwender-API. Wer sie direkt nutzt, übernimmt die Verantwortung für Verschlüsselung, Manifest-Korrektheit und vor allem für die eigene Verifikation des Ergebnisses.
Sie evaluieren die Migration API für Ihr Projekt oder stecken in einem der beschriebenen Fehlerbilder fest? Wir haben diese Lernkurve bereits hinter uns und unterstützen gerne — von der Architekturberatung bis zur fertigen Pipeline. Kontakt aufnehmen.
Alle Teile der Serie:
- Warum keine 1:1-Migration
- 25 TB SharePoint-Migration: Warum Standard-Tools nicht reichen
- Die SharePoint Migration API in der Praxis (dieser Beitrag)
- Term Store am Limit: Taxonomie-Migration im geteilten Tenant
- Versionshistorie intelligent migrieren
- Provisioning im Großformat: PnP, Throttling, Berechtigungen
- Migration API
- SharePoint Online
- Azure Blob Storage
- C#
- .NET
- Verschlüsselung