CSV2EXO
Auf der Seite CSV2EX habe ich beschrieben, wie ich eine beliebige CSV-Datei als Eingabe nutzen kann, um damit Kontakte in einer anderen Exchange Organisation anzulegen. Diese Kontakte landen auch problemlos über das Microsoft 365 Identity Management, sprich ADSync, auch in Exchange Online. Was machen aber Firmen, bei denen es kein Exchange Hybrid mit ADSync gibt oder die Replikation per ADSync nicht genutzt werden kann, weil sie einen Forest in zwei Tenants replizieren (Siehe ADSync GALSync)? Auch Cross-tenant sync könnte eine Lösung sein, wenn Sie statt Kontakte lieber Gast-Konten nutzen
Einsatzbereich
Zuerst kommt natürlich die Frage auf, ob mich das Thema überhaupt betrifft. Die essentielle Frage dabei ist: "Möchten Sie im Exchange Adressbuch für alle Mitarbeiter auch Personen und Einträge sehen, deren Postfach nicht in ihrer Exchange Organisation ist. Dann geht das in der Regel über die Anlage entsprechender "MailContacts". Wenn dies der Wunsch ist, dann müssen Sie noch unterscheiden, ob Sie "Exchange Online Only" oder Exchange im Hybrid Mode nutzen. Im Hybrid-Mode sollten Sie die Kontakte im lokalen Exchange Forest anlegen und ADSync sorgt dafür, dass diese Kontakte dann auch in Exchange Online erscheinen.
Nur wenn Sie eine reine Exchange Online-Umgebung betreiben oder ein Verzeichnisabgleich durch ADSync aus dem lokalen Active Directory nicht möglich ist, dann wird der Inhalt dieser Seite interessant.
Dann kann CSV2EXO eine Option sein, die Kontakte direkt in Exchange Online anhand einer CSV-Datei automatisiert zu verwalten.
AzureAD/EXO-Kontakte
Ehe ich an die Kodierung per PowerShell gehe, teste ich erst einmal die verschiedenen Funktionen von "New-MailContact". In einem "normalen Tenant" kann ich den Kontakt per Exchange Online PowerShell anlegen:
New-MailContact ` -ExternalEmailAddress user@uclabor.de ` -Name "user@uclabor.de" Name Alias RecipientType ---- ----- ------------- User@uclabor.de User?uclabor.de MailContact
Die vollständigen Eigenschaften erspare ich ihnen und beschränkte mich auf die wesentlichen Felder:
Get-MailContact user@uclabor.de ` | fl Name,ExternalEmailAddress,RecipientType*,EmailAddresses,` alias,IsDirSynced,primarysmtpaddress,HiddenFromAddressListsEnabled Name : user@uclabor.de ExternalEmailAddress : SMTP:user@uclabor.de RecipientType : MailContact RecipientTypeDetails : MailContact EmailAddresses : {SMTP:user@uclabor.de} Alias : user?uclabor.de IsDirSynced : False PrimarySmtpAddress : user@uclabor.de HiddenFromAddressListsEnabled : False
Den Kontakt finden Sie natürlich auch in der AzureAD-PowerShell.
Get-AzureADContact ` -Filter "mail eq 'user@uclabor.de'" ` | fl ObjectType : Contact DirSyncEnabled : DisplayName : user@uclabor.de Mail : user@uclabor.de MailNickName : user?uclabor.de ProvisioningErrors : {} ProxyAddresses : {SMTP:user@uclabor.de} SipProxyAddress :
Die Kontakte finden Sie auch im Exchange Admin-Portal und Microsoft 365 Admin Portal aber NICHT im Azure Admin Portal unter https://portal.azure.com
Im AzureAD Portal gibt es aktuell einfach keine Anzeige für die Kontakte aber Sie sind vorhanden, wie man auch im Audit-Log sehen kann:
Es gibt in der Azure PowerShell auch interessanterweise kein "New-AzureADContact" oder "Set-AzureADContact" und auch über die Graph API kann ich OrgContacts nur auflisten und lesen aber nicht anlegen und auch nicht ändern.
Kontakte | Auflisten/Anzeigen | Anlegen | Ändern | Löschen |
---|---|---|---|---|
Exchange Online PowerShell |
Get-Mailcontact |
New-Mailcontact |
Set-Mailcontact |
Remove-Mailcontact |
AzureAD PowerShell |
Get-AzureADContact |
NA |
NA |
Remove-AzureADContact |
Exchange Online Admin Center |
Ja |
Ja |
Ja |
Ja |
Microsoft Admin Portal |
Ja |
Ja |
Stammdaten. |
Ja |
Azure AD Portal |
nicht sichtbar |
nicht sichtbar |
nicht sichtbar |
nicht sichtbar |
Microsoft Graph |
List orgContacts Get orgContact |
Nein |
Nein |
Nein |
Interessanterweise kann ich Kontakte auch im Microsoft 365 Admin Center unter https://admin.microsoft.com anlegen, wobei die Webseite im Hintergrund eine REST-Api unter https://admin.microsoft.com/admin/api/Contact nutzt. Das ist aber nicht Microsoft Graph-URL. Per Graph könnte ich also nur Kontakte lesen, was hier aber nicht weiter hilft.
- orgContact-Ressourcentyp
https://docs.microsoft.com/de-de/graph/api/resources/orgcontact?view=graph-rest-1.0 - List orgContacts
https://docs.microsoft.com/de-de/graph/api/orgcontact-list?view=graph-rest-1.0&tabs=http
OrgContact.Read.All
- MailUser und MailContacts
- New-MailContact
https://docs.microsoft.com/en-us/powershell/module/exchange/new-mailcontact?view=exchange-ps - Get-AzureADContact
https://docs.microsoft.com/en-us/powershell/module/azuread/get-azureadcontact?view=azureadps-2.0 - Get-MailContact
https://docs.microsoft.com/de-de/powershell/module/exchange/get-mailcontact?view=exchange-ps
EXO Mailuser
Wie in Exchange On-Premises kann es auch in der Cloud Benutzer mit einer Anmeldung aber ohne Exchange Postfach geben, die aber dennoch im Adressbuch erscheinen sollen. Sowohl im Exchange Admin Center als auch per Exchange Online PowerShell können Sie solche "Mailuser" problemlos anlegen, editieren und löschen:
Per PowerShell sind das dann die Befehle
- New-Mailuser
https://learn.microsoft.com/en-us/powershell/module/exchange/new-mailuser?view=exchange-ps - Set-MailUser
https://learn.microsoft.com/en-us/powershell/module/exchange/set-mailuser?view=exchange-ps - Remove-MailUser
https://learn.microsoft.com/en-us/powershell/module/exchange/remove-mailuser?view=exchange-ps
Der Befehl "Enable-Mailuser" ist hingegen nicht in Exchange Online verfügbar.
Quelle:
https://learn.microsoft.com/en-us/powershell/module/exchange/enable-mailuser?view=exchange-ps
Ich kann daher z.B.: keinen CloudOnly-User nachträglich aktivieren. Im AzurePortal sehe ich auch keinen Unterschied, warum der "Clouduser2" in einem Lab nicht in Exchange erscheint.
Die MailKontakte sind im Azure-Portal gänzlich unsichtbar. Ich kann sie aber per Azure PowerShell abfragen
- Get-AzureADContact
https://learn.microsoft.com/en-us/powershell/module/azuread/get-azureadcontact?view=azureadps-2.0
Kontakte per ADSync
In Exchange Online gibt es neben der Möglichkeit, direkt entsprechende MailContacts/MailUser anzulegen auch den klassischen Weg bei einer Hybrid-Bereitstellung. Die Mailuser/Mailcontacts werden im lokalen AD angelegt und ADSync repliziert diese ins AzureAD und Exchange Online. Allerdings müssen Sie hier ein paar Besonderheiten beachten:
- ADSync repliziert Mailcontact und
MailUser
Die Objekte sind in der Cloud "Readonly", da sie durch das lokale AD verwaltet werden - Kontakt in der Cloud sind im Azure
Portal (portal.azure.com) nicht sichtbar
Sie finden diese Objekte im Exchange Portal (https://admin.exchange.microsoft.com), im Microsoft Admin Portal (https://admin.microsoft.com) und natürlich in der Powershell - Es gibt in Exchange Online ein New-Mailuser
aber kein Enable-Mailuser
Ich kann per New-Mailuser einen CloudOnly-Benutzer anlegen, der in Exchange als externer Empfänger erscheint. Ich kann aber keinen bestehenden User ohne Exchange Mailbox zu einem MailUser konvertieren.
Die Umsetzung für ADSync ist eigentlich immer zwingend, wenn Sie Exchange Hybrid richtig nutzen wollen. Der Einsatz von CDV2EXO zielt auf Umgebungen, in denen es entweder kein lokales AD mit ADSync gibt oder sie zwei Tenants von einem Active Directory aus bedienen und daher hier nicht zugleich ein Postfach und einen Kontakt mit den gleichen Mailadressen anlegen können.
Konflikte mit Kontakten
Bei Anlegen von neuen Kontakten kann es durchaus zu Konflikten kommen. Folgende Probleme habe ich schon gesehen:
Konflikt | Fehlermeldung |
---|---|
Konflikt mit bestehendem GastkontoGerade bei der Zusammenarbeit zwischen Tenants kann es durchaus passieren, dass es schon Gastkonten gibt. |
New-MailContact ` -ExternalEmailAddress ADUserA4@dom2016.msxfaq.de ` - Name ADUserA4@dom2016.msxfaq.de New-MailContact: Die Proxyadresse "SMTP:ADUserA4@dom2016.msxfaq.de" wird bereits als Proxyadresse ` oder vom LegacyExchangeDN von "ADUserA4_dom2016.msxfaq.de#EXT#" verwendet. ` Wählen Sie eine andere Proxyadresse aus. |
Konflikt mit bestehendem KontaktIch kann natürlich nicht ausschließen, dass schon ein anderer Prozess einen Kontakt angelegt hat. Die Meldung ist mit dem Gast vergleichbar |
New-MailContact ` -ExternalEmailAddress user1@msxfaq.de ` -Name "Frank@carius.de CloudOnly" New-MailContact: Die Proxyadresse "SMTP:user1@msxfaq.de" wird bereits als Proxyadresse oder ` vom LegacyExchangeDN von "User1 (msxfaq.de)" verwendet. Wählen Sie eine andere Proxyadresse aus. |
New-Mailcontact ohne Exchange LizenzDas ist natürlich ein Sonderfall. Ich habe einen Tenant, in dem gar keine Exchange Lizenz mehr vorhanden ist. Hier versagt New-Mailcontact komplett. |
New-MailContact ` -ExternalEmailAddress user2@msxfaq.de ` -Name "user2@msxfaq.de CloudOnly" New-MailContact: Fehler bei Überprüfung im Agent 'Substrate Only Agent': 'Die Organisation "NAMPR20A001.PROD.OUTLOOK.COM/Microsoft Exchange Hosted Organizations/fcarius.onmicrosoft.com" ist nicht für die Exchange-E-Mail-Funktion lizenziert. Die Cmdlet-Verwendung ist eingeschränkt.' |
Gast nach KontaktWenn Sie schon einen Kontakt haben, behindert anscheinend nicht Einladung eines Gasts mit der gleichen Adresse. |
Ich bin gespannt, welche weiteren Probleme ich im produktiven Einsatz noch erwische.
*-mailcontact vs *-contact
Wenn Sie genau mitgelesen haben, gibt es mehrere Commandlets die Kontakte anlegen, bearbeiten und löschen können.
PS C:\> get-command *-*contact -Module tmpEXO_2nl2rbr3.rki CommandType Name Version ----------- ---- ------- Function Get-Contact 0.0.1 Function Get-MailContact 0.0.1 Function New-MailContact 0.0.1 Function Remove-MailContact 0.0.1 Function Set-Contact 0.0.1 Function Set-MailContact 0.0.1
Zudem gibt es in der PSGallery noch ein "ExchangeContacts"-Modul, welches per EWS aber Kontakt im Postfach verwaltet. Das hilft mit mit CSV2EXO nicht weiter und maximal "Export-EXCGALContact" könnte interessant sein, wenn der Export durch einen Benutzer, quasi unbemerkt vom IT-Betrieb, erfolgen soll.
- ExchangeContacts
https://www.powershellgallery.com/packages/ExchangeContacts/
Bei den Commandlets der Exchange PowerShell ist interessant, dass die Properties sehr unterschiedlich sind. Wenn ich "Delete-MailContact", Get-Contact und Get-Mailcontact einmal weg lasse, dann gibt es folgende schreibende Commandlets in der Exchange Online PowerShell.
So gibt es nur ein "New-MailContact" aber kein "New-Contact".
Feld | New-Mailcontact | Set-Mailcontact | Set-Contact |
---|---|---|---|
Name |
Ja |
Ja |
Ja |
ExternalEmailAddress |
Ja |
Ja |
Nein |
Alias |
Ja |
Ja |
Nein |
Displayname |
Ja |
Ja |
Ja |
Firstname |
Ja |
Nein |
Ja |
Initials |
Ja |
Nein |
Ja |
Lastname |
Ja |
Nein |
Ja |
CustomAttribute1 bis CustomAttribute15 |
Nein |
Ja |
Nein |
ExtensionCustomAttribute1
bis ExtensionCustomAttribute5 |
Nein |
Ja |
Nein |
Emailaddresses |
Nein |
Ja |
Nein |
HiddenFromAddressListsEnabled |
Nein |
Ja |
Nein |
MailTip |
Nein |
Ja |
Nein |
MessageBodyFormat |
Ja |
Ja |
Nein |
SecondaryAddress |
Nein |
Ja |
Nein |
WindowsEmailAddress |
Nein |
Ja |
Ja |
City Company CountryorRegion Department Fax HomePhone Manager MobilePhone Notes Office OtherFax OtherHomePhone OtherTelephone Pager Phone PostalCode PostOfficeBox SimpleDisplayName StateOrProvince StreetAddress TelephoneAssistant Title webpage |
Nein |
Nein |
Ja |
Bei den Felder ist gut zu sehen, dass ich bei New-MailContact z.B. Firstname, Lastname, Phone mitgeben knan, die ich über ein Set-MailContact noch nicht setzen kann. Für einige Felder muss ich daher "Set-Contact" nutzen. Nur gut, dass es keine Felder gibt, die nur mit "New-MailContact" erreichbar sind. Dennoch muss ich beim Update eines Objekts nun nach Commandlets trennen.
Das wirkt alles noch nicht wie aus einem Guss.
Bei der unten bereitgestellten Basisversion beschränke ich mich auf die Felder, die ich per Set-MailContact setzen kann.
CustomAttribute statt Ziel-OU
Mein Skript kann niemals eine komplette Identity-Lösung ersetzen. Es verwaltet einfach nur Kontakte per Exchange Powershell oder hier per Exchange Online PowerShell. Bei CSV2EX habe ich die Kontakte allerdings in eine eigens dafür bestimmte OU im Active Directory angelegt. Damit konnte ich zuverlässig dann auch die Objekte wieder finden und in der Quelle gelöschte Objekte auch im Ziel entfernen.
Im AzureAD gibt es keine OUs im klassischen Sinn und die Benutzerdatenbank im Tenant ist quasi eine NT4-Liste. Interessanterweise kennt aber die Exchange Online-PowerShell das Commandlet "Get-OrganizationalUnit" und listet sogar zwei OUs auf.
PS C:\> Get-OrganizationalUnit | ft distinguishedname DistinguishedName ----------------- OU=msxfaqlab.onmicrosoft.com,OU=Microsoft Exchange Hosted Organizations,DC=DEUP281A004,DC=PROD,DC=OUTLOOK,DC=COM OU=Soft Deleted Objects,OU=msxfaqlab.onmicrosoft.com,OU=Microsoft Exchange Hosted Organizations,DC=DEUP281A004,DC=PROD,DC=O…
Auch bei New-MailContact gibt es den Parameter "-OrganizationalUnit", den die Exchange Online PowerShell auch anbietet aber keine Funktion hat.
https://docs.microsoft.com/en-us/powershell/module/exchange/new-mailcontact?view=exchange-ps
Ich habe aber keinen Weg gefunden, weitere OUs anzulegen. Daher muss ich mir entweder in einer eigenen Datenbank merken, welche "meine" Kontakte sind oder ich kennzeichne die Kontakte in der Cloud anhand eines Feldes, welches sonst niemand verwendet. Exchange bietet dazu ja die "CustomAttribute0-15" und die "ExtensionCustomAttribute1-5" an. Da es aber mehrere Instanzen von CSV2EX geben kann, die alle in den gleichen Tenant schreiben, sollte ich in den Wert nicht nur "CSV2EX" schreiben, sondern vielleicht gleich "CSV2EX:<quelle>".
Gruppen sind Kontakte
Die zweite Herausforderung bei einem Abgleich von Kontakten sind Verteiler. Natürlich kann ich aus der Quelle auch Gruppen erhalten und diese im Ziel anlegen und mit den Kontakten befüllen. So arbeitet z.B. ADSync, wenn er Identitäten aus einem AD in einen Tenant überträgt (Siehe auch ADSync GALSync). Allerdings hat dieser Ansatz auch Nachteile, z.B.
- Mitgliederliste nicht ReadOnly
Da die Mitgliedschaft der synchronisierten Gruppe von der Quelle abhängt aber Änderungen durch den Administrator im Ziel nicht ausgeschlossen sind, haben wir einen Konflikt bei der nächsten Replikation. Soll das Skript die manuellen Änderungen rückgängig machen oder belassen? - Message Bifurcation
Zudem wäre dann die Zielumgebung des Verzeichnisimports zugleich der Absender der Mail und würde daher vor Ort schon die Mitgliedschaften auffächern. Inkonsistenzen Mitgliedschaften führen zu falschen Empfängerkreisen. - Partielle Daten
Wenn ich Gruppen als Gruppe im Ziel anlegen, dann müssen auch alle Mitglieder der Quelle zwingend ebenfalls als Kontakte im Ziel angelegt werden, da diese ansonsten keine Mail bekommen. Ist das imer gewünscht? - Datenschutz
Vielleicht möchte die Quelle gar nicht, dass das Ziel genau die Mitgliedschaften der Gruppen kennt.
Daher habe ich mich auf "Kontakte" beschränkt und quasi jede primäre Mailadresse, ggfls. mit zusätzlichen Adressen, ist ein Kontakt. Das gilt für Postfächer in der Quelle, Räume, Shared Mailboxen aber auch öffentliche Ordner, Verteiler und weitere Kontakte. Letztlich erwarte ich von der Quelle eine entsprechend formatierte CSV-Datei, die dann durch das Skript im Ziel entsprechende Kontakte anlegt, aktualisiert und ggfls. wieder löscht.
Performance beim Anlegen
Natürlich wollte ich auch wissen, in welche Limits ich vielleicht laufen und habe daher mit folgendem Einzeiler einfach mal versucht 10.000 Kontakte anzulegen
1..10000 | %{new-mailcontact -ExternalEmailAddress ("contact"+$_+"@msxfaq.de") -Name "contact$($_)"}
Nach etwas 15 Minuten und 1271 Kontakte ist die Anlage aber mit folgendem wenig aussagekräftigen Fehler abgebrochen.
contact1270 contact1270 MailContact contact1271 contact1271 MailContact OperationStopped: Starting a command on the remote server failed with the following error message : WinRM kann den Vorgang nicht abschließen. Überprüfen Sie, ob der angegebene Computername gültig, der Computer über das Netzwerk erreichbar und eine Firewallausnahme für den WinRM-Dienst aktiviert ist und den Zugriff von diesem Computer zulässt. Standardmäßig wird der Zugriff auf Remotecomputer innerhalb desselben lokalen Subnetzes von der WinRM-Firewallausnahme für öffentliche Profile eingeschränkt. For more information, see the about_Remote_Troubleshooting Help topic. ... Creating a new Remote PowerShell session using Modern Authentication for implicit remoting of "New-MailContact" command ... Error Acquiring Token: System.Net.Http.HttpRequestException: Der angegebene Host ist unbekannt. (login.microsoftonline.com:443) ---> System.Net.Sockets.SocketException (11001): Der angegebene Host ist unbekannt. at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellatio
Ich bin eigentlich sicher, dass meine Internet-Verbindung fehlerfrei bestanden hat und auch die automatische Neuanforderung des Tokens selbst ist ja fehlgeschlagen.
Ich habe dann die Anlage vorgesetzt und da wurde nach ca. 3600 weiter fehlerfrei angelegten Kontakten nur die Verbindung neu problemlos aufgebaut.
contact4863 contact4863 MailContact contact4864 contact4864 MailContact Creating a new Remote PowerShell session using Modern Authentication for implicit remoting of "New-MailContact" command ... contact4865 contact4865 MailContact contact4866 contact4866 MailContact
Scheint so, als ob mein Fehler wohl nur temporärer Natur war. Aber ich bin sicher, dass es ein Throttling gibt und daher meine Prozesse damit umgehen müssen. Das Commandlet "get-throttlingpolicy/set-throttlingpolicy" gibt es aber nur On-Premises
- get-throttlingpolicy (Nur OnPrem)
https://docs.microsoft.com/de-de/powershell/module/exchange/get-throttlingpolicy?view=exchange-ps
Und auch ein "MicroDelay" habe ich noch nicht gesehen.
- "Micro delay applied" warning or delays occur in Exchange Online
https://support.microsoft.com/en-us/topic/-micro-delay-applied-warning-or-delays-occur-in-exchange-online-86716b03-53ff-d07f-3b24-b638a9f6b733
Der Artikel verweist auf die Möglichkeiten dies zu verhindern und benennt auch ein "RobustCloudCommand"-Module, in dessen Quelltext einige interessante Hinweise stehen
- Function Start-RobustCloudCommand
https://www.powershellgallery.com/packages/RobustCloudCommand/2.0.1/Content/RobustCloudCommand.psm1
Aber diese Fehler habe ich mit der neuen Exchange Online PowerShell V2 bislang ja nicht gesehen. Also muss ich nur selbst eine Fehlerbehandlung mit einer Verzögerung einbauen oder wie ADSync beim nächsten Lauf wieder aufsetzen.
Allein mit dem Anlegen ist es aber nicht getan, denn ich möchte auch noch weitere Felder setzen, die ich beim "New-MailContact" nicht mit angeben kann. Neben Straße, Ort etc. ist das mindestens ein extensionAttribute, damit ich unter den vielen Kontakte die wieder finde, die ich verwalte. Eine OU-Struktur zur Separierung, wie bei CSV2EX gibt es im Exchange Online nicht. Das Update eines bestehenden Kontakts ist deutlich schneller (ca. 250-400ms) und ich muss auch keine Replikation abwarten, sondern kann direkt loslegen.
- New-Mailcontact
https://docs.microsoft.com/de-de/powershell/module/exchange/new-mailcontact?view=exchange-ps - Set-Mailcontact
https://docs.microsoft.com/de-de/powershell/module/exchange/set-mailcontact?view=exchange-ps
Kontakte Auslesen
Ich brauche gleich zweimal die Funktion, bestehende Kontakte auszulesen.
- Lesen aus der Quelle
Wenn die Quelle selbst ein Exchange Online Tenant ist, dann könnte ich mir die Daten dort selbst über Graph oder die Exchange Online Powershell holen. Über die PowerShell habe ich natürlich mehr Rechte, während es bei Graph die Berechtigung auf "OrgContact.Read.All" erforderlich wäre. Letztlich obliegt es aber eh dem Administrator im der Quelle, welche Daten er mir als CSV-Datei bereitstellt. Ansonsten gibt es ja immer noch die MGGraph PowerShell, mit der solche Dinge schnell umgesetzt werden können - Lesen im Ziel zur Delta-Erkennung
Interessanter ist das Auslesen im Ziel, denn ich muss ja erkennen, welche Objekte es schon im Ziel von vorherigen Sync-Vorgängen gibt und vielleicht entfernt werden müssten. Ein "Get-MailContact" macht das aber recht einfach, da sowohl größere Datenmengen als auch Filter genutzt werden können.
Zumindest in meine Umfeld bin ich da noch nicht auf Probleme gestoßen.
$bestand = get-mailcontact -Filter customattribute5 -eq "test" -ResultSize unlimited
Das Auslesen von ca. 10.000 Kontakte dauerte bei mir 40 Sek, also ca. 250 Kontakte/Sekunde
- Ge-Mailcontact
https://docs.microsoft.com/de-de/powershell/module/exchange/get-mailcontact?view=exchange-ps - OrgContact abrufen
https://docs.microsoft.com/de-de/graph/api/orgcontact-get?view=graph-rest-1.0&tabs=http
CSV2EXO-Skript
Mit diesen Vorarbeiten kann ich nun an die Umsetzung meines vereinfachten DirSync gehen, der eine CSV-Datei mit entsprechenden Benutzern in einen Exchange Online-Tenant importiert.
Warnung: Dies ist eine "einfache" Lösung, die neue Objekte addiert, bestehende aktualisiert und überzählige Objekte löscht und das Feld "Mail" als primären Schlüssel nutzt. Das bedeutet aber auch, dass z.B. eine Änderung der Mailadresse aus Sicht des Programms dann ein "Löschen"/Neuanlegen" ist und der Kontakt damit aus Gruppenmitgliedschaften entfernt wird..
Ich gehe davon aus, dass ich als Eingabeformat wieder eine CSV-Datei habe, welche mindestens die erforderlichen Felder für einen Kontakt haben.
- Name
Die Mailadresse ist für die weitere Betrachtung wichtig, da ich diese Feld als primären Schlüssel verwende, um nach Übereinstimmungen zu suchen. Allerdings sind die Möglichkeiten von "New-MailContact/Set-Mailcontact" leider beschränkt. So kann ich damit weder Telefonnummern noch Vorname, Nachname, Straße, Ort o.ä. füllen. Im lokalen Exchange ist dies per AD noch möglich aber im AzureAD gibt es meines Wissens keine Möglichkeit diese Informationen zu setzen.
Das Skript macht dann eigentlich nichts anderes als:
- CSV-Datei importieren
Hierbei prüfe ich sicherheitshalber, dass die Mailadressen eindeutig sind und die Datei komplett ist. Der letzte Datensatz muss ein "#" enthalten. Nicht dass die Datei nur partiell übertragen wurde. - Durch früheren Sync erstellte Kontakte
aus dem Ziel einlesen und bereinigen
Dabei werden die Kontakte mit dem bestehenden Objekten abgeglichen. Übereinstimmungen werden in der Lookup-Tabelle entfernt und nicht gefundene Einträge im Ziel gleich gelöscht. Übereinstimmungen werden aktualisiert. - Kontakte anlegen und aktualisieren
Was nun noch im Cache ist, muss im Ziel neu angelegt werden.
Es klingt einfach aber durch Fehlerbehandlung und Sonderfälle ist der Code schon etwas umfangreicher. Es erwartet die Übergabe als Parameter und natürlich müssen Sie vorher im Zieltenant natürlich das Skript als "App" einrichten.
Details zur Einrichtung einer App habe ich auch EXO PowerShell Automation beschrieben.
Sie müssen dann die entsprechenden Daten entweder in der "Param"-Sektion anpassen oder beim Aufruf mit übergeben.
- CSVFile, Delimiter und Encoding
Sie spezifizieren die Import-Datei und Formatierung - CertificateThumbPrint AppID,
Organization
Tragen Sie hier den AppID, den Thumbprint des Zertifikats und die Organisation ein, die bei der App-Registration (Siehe EXO PowerShell Automation) definiert wurde. - Keyproperty/keyvalue
Jeder vom Skript angelegte Kontakt bekommt dieses Feld mit dem Wert gesetzt und beim späteren Lesen und Löschen werden nur diese Kontakte berücksichtigt.
Für den Import ist eine CSV-Datei erforderlich, die wenige Voraussetzungen erfüllen muss:
- Erster Spalte "mail"
Das ist der primäre Schlüssel für den Kontakt und muss daher vorhanden und eindeutig sein - weitere Spalte "name"
Das Feld "Name" wird zum Displayname und sieht der Anwender. Es sollte entsprechend sinnvoll gefüllt sein - Weitere Felder
Weitere Felder sind möglich und werden mit "Set-MailContact" als Parameter 1:1 übernommen und gesetzt. - Letzte Zeile "#"
Damit stelle ich sicher, dass der Export "komplett" ist und nicht beim Export oder der Bereitstellung etwas verloren gegangen ist. Das ist ein Schutz, dass dann nicht vorhandene Kontakte entfernt werden.
Wie Sie die CSV-Datei erstellen, überlasse ich ihnen. Sie können Sie aus einer ERP-Software generieren, aus dem lokalen AD per "CSVDE" exportieren oder mit wenig Aufwand aus einem anderen Exchange Online Tenant per Get-MailContact oder Get-EXORecipient abziehen.
Durch den "Mittelweg" einer CSV-Datei können Sie Export, Transport, Transformation und Import durch verschiedene Personen mit unterschiedlichen Berechtigungen durchführen lassen und speziell den Import auch in Test-Tenants einfach verifizieren, ehe Sie an den Live-Tenant gehen.
CSV2EXO vNext
Das oben bereitgestellte Skript ist eine einfache Basisfunktion, die durchaus "verbessert" werden kann, z.B. mit Fehlerbehandlung, Logging/Protokollierung, Diagnose-Ausgaben, Laufzeit-Optimierungen, Konfliktbehandlung, ausgelagerte Konfiguration etc. ich arbeite an einem entsprechenden Skript, welches aber aktuell nur in Kundenprojekten zum Einsatz kommt und ich möchte ja auch sicher sein, dass der Einsatz möglichst ungefährlich ist.
Diese erweiterte Funktion ist noch nicht
öffentlich. Wenn Sie Bedarf an einem "Cross Tenant Dirsync"
haben, dann kommen Sie gerne auf mich zu
Kontakt bzw.
Net at Work
Weitere Links
- CSV2EX
- Cross-tenant sync
- MGGraph PowerShell
- ADSync GALSync
- MailUser und MailContacts
- MiniSync
- Verbinden von Organisationen
- Verzeichnisabgleich für Exchange
-
Filter im EXO V2-Modul
https://docs.microsoft.com/de-de/powershell/exchange/filters-v2?view=exchange-ps - Sync contacts between Microsoft Exchange
and Microsoft Graph API with sync.blue
https://info.sync.blue/sync/microsoft-exchange/microsoft-graph-api/ - GALSYNC v2
https://www.wapshere.com/missmiis/galsync-v2