Die Public Folders sind nach Exchange Online migriert, die alte On-Prem-Hierarchie ist abgeräumt — und trotzdem ist die Sache nicht erledigt. Denn Entra Connect synchronisiert Public-Folder-Objekte nicht. Weder in die eine noch in die andere Richtung. Wer weiterhin On-Prem-Postfächer betreibt und will, dass diese Nutzer eine mail-aktivierte öffentliche Ordneradresse in der GAL finden und anmailen können, braucht einen eigenen Mechanismus.
Microsoft liefert dafür Sync-MailPublicFoldersCloudToOnprem.ps1. Das Skript zieht die mail-enabled Public Folders (MEPFs) aus Exchange Online und legt on-prem SyncMailPublicFolder-Objekte an — im Kern Weiterleitungsobjekte mit einer targetAddress in die Cloud. Sie liegen im Container Microsoft Exchange System Objects unterhalb der Domain-Wurzel und sind in der EAC nicht sichtbar.
Das Skript tut prinzipiell genau das Richtige. Bei näherem Hinsehen hat es allerdings ein paar Eigenschaften, die man kennen sollte, bevor man es in einer Produktivumgebung mit mehreren hundert Ordnern laufen lässt.
Der Fehler, der sich selbst verstärkt
Das Skript baut während der Verarbeitung eine Liste gültiger externer Adressen auf. Am Ende gilt: Jedes On-Prem-Objekt, dessen Adresse nicht in dieser Liste steht, hat kein Gegenstück in der Cloud und wird per Disable-MailPublicFolder mail-deaktiviert.
Der Haken liegt in der Reihenfolge. Der Eintrag in diese Liste erfolgt nach dem Set-MailPublicFolder-Aufruf, innerhalb desselben try-Blocks. Scheitert das Setzen der Eigenschaften — Proxy-Adresskonflikt, Timeout, gesperrtes Objekt —, greift der catch, die Adresse landet nie in der Liste, und derselbe Ordner wird wenige Zeilen später abgeräumt.
Im Anlege-Pfad ist das besonders unangenehm: New-SyncMailPublicFolder läuft durch, das nachfolgende Set-MailPublicFolder scheitert — und das gerade erzeugte Objekt wird im selben Lauf wieder mail-deaktiviert. Ein transienter Fehler baut aktiv ab, was das Skript eigentlich herstellen sollte.
Verschärft wird das dadurch, dass die Cloud-Abfrage mit -ErrorAction:SilentlyContinue läuft. Liefert sie wegen eines Verbindungs- oder Berechtigungsproblems nichts zurück, ist die Liste leer und jedes On-Prem-Objekt steht auf der Löschliste. Immerhin: Neuere Fassungen schreiben die Kandidaten in eine Textdatei und fragen per Read-Host nach. Das ist der einzige echte Schutz — und er funktioniert nur, wenn jemand die Datei vor dem „Y” tatsächlich liest.
Die Routing-Domain, die keine ist
Zur Bestimmung der Zieladresse prüft das Skript, ob eine Proxy-Adresse auf .onmicrosoft.com endet. Das klingt plausibel, ist aber zu grob.
In einem typischen Hybrid-Setup gibt es zwei solche Domains: die Tenant-Domain contoso.onmicrosoft.com und die vom HCW angelegte Routing-Domain contoso.mail.onmicrosoft.com. Nur für letztere existiert on-prem ein Address Space am Hybrid-Send-Connector. Wird ein Ordner in EXO nativ mail-aktiviert, landet seine Primäradresse gern auf der erstgenannten — und das Sync-Skript schreibt sie brav als ExternalEmailAddress nach on-prem. Zustellbar ist sie dort nicht, jedenfalls nicht über den gesicherten Hybrid-Pfad.
Noch unangenehmer ist der Fall, in dem gar keine passende Proxy-Adresse gefunden wird: Dann fällt das Skript auf die Primäradresse in der Kundendomain zurück. Ist on-prem für diese Domain autoritativ, zeigt das Objekt auf sich selbst. Das Skript warnt am Ende generisch vor Mail-Schleifen, prüfen kann es das nicht.
Und die Prüfung, die es dafür bräuchte, läuft ins Leere: Der Send-Connector wird über den hartkodierten Namen "Outbound to Office 365" gesucht — per exaktem Match. Der HCW hängt an diesen Namen aber eine GUID an. Gefunden wird also nichts, und die Warnung erscheint pauschal für alle Domains statt gezielt für die problematische.
Namenskollisionen bei Skalierung
Objekte in Microsoft Exchange System Objects brauchen innerhalb des Containers einen eindeutigen cn. Zwei gleichnamige Ordner in unterschiedlichen Pfaden — \Projekt A\Archiv und \Projekt B\Archiv — kollidieren also. Bei zwölf Ordnern ein theoretisches Problem, bei fünfhundert eine Gewissheit.
Das verwandte Import-PublicFolderMailboxes.ps1 zeigt, wie man es nicht macht: Es baut einen Namen aus Präfix, Anzeigename und GUID zusammen und kürzt das Ergebnis anschließend hart auf 64 Zeichen. Gekürzt wird hinten — also genau die GUID. Ab etwa 51 Zeichen Anzeigename ist die Eindeutigkeit vollständig weggeschnitten.
Richtig ist der umgekehrte Weg: Suffix-Länge reservieren, Basis vorher kürzen. Dann bleibt das Ergebnis immer innerhalb des Limits und immer eindeutig.
Was ich geändert habe
Ich habe das Skript nicht gepatcht, sondern neu geschrieben – bzw. “schreiben lassen” (Claude sei Dank) :-). Die Signatur von Microsoft überlebt keine Änderung, und die Struktur der Hauptschleife musste ohnehin gedreht werden.
Die zentrale Änderung ist die Reihenfolge: Beide Seiten werden vollständig eingelesen, aus dem On-Prem-Bestand entsteht ein Index über jede bekannte Adresse jedes Objekts, und das Matching passiert vollständig, bevor der erste Schreibzugriff erfolgt. Ein gefundenes Objekt wird sofort als geschützt markiert. Ob das anschließende Set gelingt, ist für die Löschphase damit irrelevant — der beschriebene Fehlerpfad existiert nicht mehr.
Dazu:
- Routing-Domains werden ermittelt, nicht geraten. Aus den Address Spaces des Send-Connectors (Name als Parameter, Wildcards erlaubt) entsteht die Menge zulässiger Zieldomains. Eine Adresse außerhalb dieser Menge wird abgelehnt, statt still geschrieben zu werden.
- Drei unabhängige Bremsen vor der Löschphase: Mindestanzahl an Cloud-Objekten, maximaler Löschanteil in Prozent, und ein harter Stopp, sobald vorher irgendein Fehler aufgetreten ist.
- Namen werden für AD bereinigt und bekommen bei Kollision eine Zufallszahl angehängt — mit vorher gekürzter Basis. Bestehende Objekte werden nie in eine Kollision zurückbenannt, was sonst bei jedem geplanten Lauf einen Fehler produzieren würde.
- Kollisionserkennung sprachneutral. Statt den lokalisierten Fehlertext zu parsen, wird nach einem gescheiterten Anlegen geprüft, ob unter dem Namen tatsächlich ein Empfänger existiert. Nur dann wird mit neuem Namen erneut versucht.
- Echtes
SupportsShouldProcessmit-WhatIf, plus-Forcefür den unbeaufsichtigten Betrieb. - Bestandssicherung als CSV vor jeder Änderung.
- Zertifikatsbasierte Authentifizierung für Scheduled Tasks, und Wiederverwendung einer bereits offenen Session für den interaktiven Betrieb.
Import-LocalizedDataentfernt. Die ausgelieferten.psd1-Dateien heißen anders als das, was die Skripte erwarten — mit Unterstrich statt Punkt. Läuft der Import ins Leere, sind sämtliche Konsolenmeldungen und die halbe CSV leer. Inline-Strings lösen das Problem ersatzlos.
Fazit
Für kleine Umgebungen und beaufsichtigte Ausführung ist Microsofts Skript brauchbar. Man startet es, liest die Ausgabe, prüft die Liste vor dem Löschen — und wenn etwas schiefgeht, sieht man es und korrigiert von Hand.
Als geplanter Task gegen mehrere hundert Ordner ist es das nicht. Die drei Schwachstellen greifen dort ineinander: Namenskollisionen sind bei dieser Größenordnung nicht die Ausnahme, sondern der Normalfall. Jede fehlgeschlagene Verarbeitung macht das betroffene Objekt zum Löschkandidaten. Und niemand liest um drei Uhr nachts die Bestätigungsabfrage. Das Ergebnis ist ein Skript, das im Fehlerfall abbaut, was es herstellen soll — und zwar unbemerkt, weil der Mailfluss erst dann auffällt, wenn ein Nutzer eine Adresse nicht mehr erreicht.
Wer das Original einsetzt, sollte deshalb mindestens drei Dinge tun: den Bestand vor jedem Lauf wegsichern, die Ausgabe in ein Transcript schreiben und regelmäßig gegenprüfen, ob die Anzahl der On-Prem-Objekte noch zur Cloud passt. Wer den Aufwand ohnehin treibt, kann das Verhalten auch gleich reparieren.
Der Kern der Reparatur ist keine große Sache: Matching vor Schreibzugriff, Zieldomains ermitteln statt raten, und Abbruchschwellen vor destruktiven Operationen. Das sind Prinzipien, die für jedes Sync-Skript gelten, nicht nur für dieses.

by