GroupSync

Auf der MSXFAQ finden Sie einige Skripte und Beschreibungen zum Thema Verzeichnisabgleich, d.h. Um z.B. die Benutzer auf einem LDAP-Server als Kontakte im anderen LDAP-Server anzulegen. Solche Lösungen kommen immer wieder bei meinen Kunden zum Einsatz um z.B. Liste der gültigen Mailadressen auf einen anderen LDAP-Server beim Spamschutz zu übertragen oder bei Migrationen die GAL abzugleichen. Diese Skripte haben bislang das Thema Gruppen aber meist ausgespart und haben maximal Verteilergruppen in einer Exchange Organisation als Kontakte in der entfernten Organisation angelegt.

Verteiler kopieren und abgleichen

In Verbindung mit Exchange greift dies aber zu kurz, weil Gruppen mit Exchange Eigenschaften auch Kontakte als Mitglieder haben können, die z.B. von ADMT nicht übertragen werden. Hinzu kommt, dass grade beim Betrieb zweier Mailsysteme nebeneinander die Verteiler "besonders" zu behandeln sind. Bei einer Koexistenz sind Benutzer auf dem einen System in der Regel Kontakte auf der anderen Seite. Ein Verteiler kann dabei nun auf drei Arten verwaltet werden:

Variante 1: Ein Verteiler mit lokalen Usern und Kontakten und einen entfernten Kontakt

  • Über einen Verzeichnisabgleich sollten zumindest die relevanten Empfänger von ORG2 als Kontakte in der ORG1 vorliegen um dort in die Verteilerliste aufgenommen werden zu können.
  • Der Verteiler selbst kann in die ORG2 zurück repliziert werden, damit die Benutzer von Dort auch diesen aus dem Adressbuch auswählen und per Mail ansprechen können.
  • Die Verwaltung erfolgt komplett in ORG1
  • Unstimmigkeiten sind nicht möglich.
  • Das führt aber dazu, dass Mails von ORG2 an den Verteiler erst zu ORG1 gehen, um dort aufgeteilt zu werden und vielleicht die Mail wieder zum Postfach in ORG2 zurück übertragen wird.

DLCopy01

Variante 2: Ein Verteiler mit lokalen Usern und einem Kontakt zum einen entfernten Verteiler mit dortigen Benutzern

  • Die Mitglieder aus der anderen Organisation werden über einen Verteilerkontakt mit aufgenommenORG2 werden.
  • Die Replikation von Postfächern einer Organisation als Kontakte in die die andere Organisation ist nicht erforderlich aber möglich.
  • Hierbei pflegt jede ORG ihren Verteiler selbst, welcher nur die eigenen lokalen Benutzer enthält.
  • Unstimmigkeiten sind möglich.
  • Mails werden nur einmal übertragen

DLCopy2

Variante 3: Zwei Verteiler mit synchronisierten Mitgliedern.

  • In jeder Organisation gibt es einen eigenen Verteiler
  • Die Replikation von Postfächern einer Organisation als Kontakte in die die andere Organisation ist erforderlich, damit die Verteiler synchron gehalten werden.
  • Hierbei pflegt jede ORG ihren Verteiler selbst, welcher nur die eigenen lokalen Benutzer enthält.
  • Unstimmigkeiten sind möglich.
  • Mails werden nur einmal übertragen

DLCopy02

Es ist also nicht immer ausreichend oder optimal, wenn man eine Gruppe auf einer Seite auf der anderen Seite als Kontakt anlegt. Auch bei der Migration mit ADMT können zwar Gruppen übertragen werden, aber das ist auch immer nur ein "Additiver Merge" und kein Sync, d.h. Objekte die in der Quelle nicht mehr in einer Gruppen enthalten sind, werden im Ziel nicht entfernt und das Ziel muss ein AD sein.

Anforderung

Daher habe ich mir Gedanken gemacht, wie ich Gruppen in zwei AD-Forests ein eine Richtung abgleichen kann.

  • Matching
    Windows speichert die Mitglieder von Gruppen als DN im Feld "Member". Dieser String kann natürlich nicht 1:1 übertragen werden, da auf der anderen Seite der LDAP-Pfad sicher anders ist. Denkbar ist z.B. den SAMAccountName, wenn dieser gleich ist. Wenn es primär um Mailverteiler geht, dann kann die Mailadresse ein gutes Kriterium sein, sofern alle Objekte damit ausgestattet sind. Da ich PowerShell einsetze, kann ich das Script natürlich auch schnell anpassen.
  • Große Listen
    Weiterhin muss berücksichtigt werden, dass LDAP-Server sehr große Mitgliederlisten nicht mit einem Aufruf ausliefern. Windows 2000 hat maximal 1000 Mitglieder geliefert. Windows 2003 liefert 1500 Mitglieder aus. Alles drüber muss mit einem "RangeSearch" abgeholt werden.
  • Merge und Sync
    Es geht aber nicht nur darum die Mitglieder auf einer Seite zu exportieren und auf der anderen Seite zu importieren sondern zumindest optional sollte es auch möglich sein die Mitglieder im Ziel zu entfernen, wenn diese auch in der Quelle entfernt wurden.
  • Missing
    Es kann natürlich auch sein, dass ein Objekte im Ziel noch gar nicht vorhanden ist und daher nicht addiert werden kann oder dass das Objekt im Ziel nicht eindeutig identifiziert werden kann. Es ist durchaus möglich, dass in einem Forest mit mehreren Domänen ein SamAccountName mehrfach vorhanden ist (z.B. Administrator). Hier sollte eine Warnung erfolgen.
  • Transport
    Nicht immer sind Quelle und Ziel direkt per TCP miteinander verbunden. Daher habe ich mir überlegt den Export und den Import-Prozess einfach in zwei Skripte auszulagern, so dass eine Datei als Transfermedium genutzt werden kann. Zudem kann so auch ein Debugging einfach erfolgen. Man hat ja die Zwischendatei.

Funktionsweise

Klar kann ich ein PowerShell-Skript immer wieder anpassen aber am Ende läuft das auf viele unterversionen hinaus. Daher habe ich die Lösung in zwei Skripte aufgeteilt. Das "Export"-Skript hat nur die Aufgabe aus einer Quelle eine Liste der Mitglieder zu generieren. Mein Skript liest einen beliebige Gruppe in einem LDAP-Server aus. Die Liste können Sie aber auch aus einer anderen Quelle generieren, solange die Werte später im Ziel genutzt werden können.


Die rot umrandeten Blöcke sind auf dieser Seite beschrieben.

Die Liste der Mitglieder wird dann an Import-GroupMember.ps1 per Pipeline übergeben, der die angegebene Gruppe im Ziel entsprechend aktualisiert. Sie können also den Export und den Import direkt aneinander ketten oder über die Pipeline die Zwischendaten in einer Datei ablegen und im Ziel wieder einlesen. Das Format der Datei ist recht einfach: einfach

Export

Schritt 1 ist der Export der Mitglieder einer gegebenen Gruppe mit einem vorgewählten Feld per PowerShell einfach in die Pipeline. So stehen mir alle Wege offen die Ausgabe in eine CSV,TXT,XML-Datei oder direkt an einen nachfolgenden Prozess zu übergeben. Die Grundfunktionen sind':

  • Auslesen der Mitglieder einer als DN bekannten Gruppe aus eine, LDAP-Server
  • Ermitteln des Exportfelds pro Mitglied
    Einen DN braucht ich nicht in ein anderes Ziel zu übertragen. Das Feld "SamAccountName", "Mail" oder "UserPrincipalName" sind vielleicht besser geeignet, um einen primären Schlüssel für das Ziel zu exportieren.
  • Keine Rekursion
    Ich habe vereinfacht darauf verzichtet, Gruppen in Gruppen weiter aufzuschlüsseln. Die Gruppe als Mitglied wird natürlich auch mit exportiert und wenn Sie die zweite Gruppe ebenfalls mit GroupSync abgleichen, dann erhalten Sie auch eine vergleichbare Kopie. Wer mag, kann die Rekursion ja selbst schreiben.
  • Anmeldedaten für Quelle
    Nicht immer ist die "integrierte Anmeldung mit dem aktuellen Benutzer der richtige Weg. Das Skript muss sowohl füre die Suche als auch die Pflege die Angabe von Anmeldedaten erlauben.
  • Ausgabe per Pipeline
    Die PowerShell Pipeline ist sehr effektiv für die Verkettung von Skripten. Und wenn Sie keine direkte Kette nutzen können, weil Quelle und Ziel vielleicht gar keine direkte Verbindung haben, dann können Sie mit Out-File und Get-Content immer noch eine Datei als Transport nutzen.
  • Nutzung von ADSI ohne Add-ons
    Ich weiß auch, dass Windows 2008 sehr leistungsfähige Commandlets zu AD-Gruppen hat. Aber diese erfordern einen Windows 2008R2 DC mit AD-WebServices und helfen nicht, wenn man andere LDAP-Server oder Quellen nutzen will. Daher habe ich "native" ADSI genutzt, auch wenn das etwas aufwändiger ist.
  • Logging als Transcript
    Ein einfaches Logging der Aktionen sollte enthalten sein. mögliche Fehlerfälle sind neben Programmfehlern und falschen Anmeldedaten und nicht erreichbare Server auch Gruppe nicht da oder Mitglied ohne Export-Property.

Ich habe absichtlich den Export "einfach" gehalten und auf eine Gruppe beschränkt, die per Parameter anzugeben ist. Wenn ich mehrere Gruppen exportieren und woanders importieren will, dann kann ich das Script ja mehrfach aufrufen. Ich betrachte dieses Skript als "Baustein" in einer Sammlung und nicht als fertige Verzeichnisabgleichlösung. Daher gibt es auch keine XML-Datei als Konfigurationsprofil o.ä. Die Parameter können Sie im Header des Skripts sehen. Hier ein Beispiel, welche gegen meine Test-VM funktioniert.

param (
   $ldapserver   = "LDAP://192.168.23.130/",
   $groupdn      = "CN=syncgroup3,OU=groupsync,DC=msxfaq,DC=local",
   $ldapUser     = "MSXFAQ\Administrator",
   $ldappass     = "Password!",
   $ldapauthtype = "Secure",
   $property     = "mail"
)

Dieses Setup exportiert die Mitglieder der Gruppe "syncgroup1". Das relevante Feld ist "Mail", d.h. Objekte ohne Mailadresse werden gar nicht exportiert.

Import

Das etwas aufwändigere Gegenstück betrifft den Import einer Mitgliederliste in einem Ziel. Auch hier gibt es ein paar Vorbedingungen zu können-.

  • Objekte im Ziel schon vorhanden
    Die Aufgabe von GroupSync ist nur der Abgleich der Mitgliedschaften aber kein Ersatz für einen DirSync, der auch Objekte anlegt und pflegt. Es ist daher erforderlich, dass im Ziel sowohl die zu pflegende Gruppe als auch die möglichen Mitglieder vorhanden sind. Wenn Sie einen anderen Verzeichnisabgleich nutzen, dann müssen Sie GroupSync nach dem Anlegen der Objekte starten.
  • Eingabe per Pipeline
    Das Skript erwartet die Liste der Mitglieder über die Pipeline. Das kann der Output von Export-GroupMember sein oder eine Datei, die per "Get-Content | ..." an das Skript gesendet wird.
  • Anmeldedaten für Ziel
    Nicht immer ist die "integrierte Anmeldung mit dem aktuellen Benutzer der richtige Weg. Das Skript muss sowohl für die Suche als auch die Pflege die Angabe von Anmeldedaten erlauben.
  • Vollständigkeit der Daten
    Beim Import der Daten muss das Skript sicherstellen, dass die Eingabedaten komplett sind. Stellen Sie sich vor der Export würde mitten drin abgebrochen und eine partielle Datei würde dem Import vorgelegt. Mit der Option "removeorphaned" entfernt das Skript Mitglieder, die in der Quelle nicht mehr enthalten sind. Das wäre gefährlich. Das Skript erwartet, dass am Ende der Datei drei "###" Zeichen stehen, um die Vollständigkeit zu prüfen.Match und Merge
    Das Script sucht Eintrag für Eintrag als LDAP-Suche im angegebenen System um den DN des Objekts zu finden und den Benutzer zu addieren, wenn er noch nicht bereits Mitglied ist. Dazu holt das Skript zuerst eine komplette Liste der Mitglieder, aus der dann die gefundenen Objekte gestrichen werden.
  • Orphaned
    Über den Schalter "RemoveOrphaned" wird eine Logik aktiviert, die am Ende all die Objekte aus der Gruppe entfernt, die in der Streichliste noch enthalten sind, weil Sie in der Quelle nicht enthalten waren.
  • Logging als Transcript und Summen-CSV.
    Wie der Export soll das Skript einmal alle Ausgaben als "Transcript" mitloggen und diese Logs nach einigen Tagen entfernen und zudem eine CSV-Datei aller im Ziel vorgenommenen Änderungen mitschreiben. Verschiedene Fehler im Code, bei den Parametern oder der eigentlichen Funktion sollen protokolliert werden.

Das Skript wird wie der Export über Parameter gesteuert, die eigentlich selbst erklärend sind. ich habe hier als Beispiel die Parameter, um mich von einem PC mit dem DC zu verbinden, obwohl es keinen Trust o.ä. gibt. Es werden in dem Fall explizite Anmeldedaten angegeben:

param (
   $ldapserver        = "LDAP://192.168.23.130/",
   $groupdn           = "CN=syncgroup4,OU=groupsync,DC=msxfaq,DC=local",
   $searchpath        = "GC://192.168.23.130/dc=msxfaq,dc=local",
   $ldapUser          = "MSXFAQ\Administrator",
   $ldappass          = "Password!",
   $ldapauthtype      = "Secure",
   $property          = "mail",
   $resultfile        = ".\groupsync.change.csv",
   $removeorphaned    = $true
)

Ich habe dieses Skript absichtlich mit der expliziten Angabe von Credentials programmiert, da bei solchen "Sync"-Aufgaben in der Regel die grenzen von Forests überschritten werden und Trust nicht als gegeben angesehen werden können.

Mehrere Gruppen

Das Skript exportiert immer genau eine Gruppe. Wenn Sie mehrere Gruppen exportieren wollen, dann müssen Sie für jede Gruppe das Skript als Export und Import starten. Wer mit PowerShell etwas umgehen kann, kann das natürlich einfach in einer Schleife machen. Es bietet sich an im Skript einfach die Zugangsdaten zu hinterlegen und als Parameter dann nur die veränderlichen Komponenten anzugeben

get-group | foreach {
   export-groupmember.ps1 -outfile ($_.windowsemailaddress+."txt") -groupdn $_.distinguishedname
   import-groupmember.ps1 -infile ($_.windowsemailaddress+."txt") -groupdn ("cn="+$_.samaccountmame+",dc=msxfaq,dc=de")
}

Dies ist nur ein Beispiel und sollte auf ihre Belange angepasst werden. Das Skript braucht beim Import einen absoluten distinguishedName. Wenn man den nicht irgendwie aus der Quelle ableiten kann, dann können Sie mit einer CSV-Datei ein Mapping selbst programmieren. Ursprünglich wollte ich die SMTP-Adresse als Ziel nutzen aber nicht alle Umgebungen nutzen Exchange im Ziel und GroupSync ist ja nicht zwingend für Exchange von Vorteil. Es sollte einem Admin aber kein Problem bereiten, das Import-Skript derart zu erweitern, dass eine Mailadresse einer Gruppe übergeben werden kann.

Beispiel

Der Einsatz ist relativ einfach und eine kurze Verkettung mit den Defaults im Parameter ist einfach ein :

.\export-groupmember.ps1 -property samaccountname | `
    .\import-groupmember.ps1 -property samaccountname

Natürlich müssen Sie bei sich entweder die Parameter im Skript ändern oder ihre Werte beim Aufruf addieren.

Logging

Automatisch per Taskplaner gestartet Scripts sollten zumindest ein Protokoll schreiben. Festplattenplatz ist zu günstig um auf eine qualifizierte Fehlersuche zu verzichten. Daher schreiben beide Skripte entsprechende Logs

  • Transcript
    Alle Bildschirmausgaben werden mit "Start-Transcript" in eine jeweils neue Datei geschrieben. Der Dateiname leitet sich aus dem Script und einem Zeitstempel ab.
    Diese Detailinformationen müssen aber nicht für immer aufbewahrt werden. Daher enthalten beide Skripte auch eine Funktion, die alte Dateien nach dem Namensschema nach 5 Tagen wieder löscht.
  • CSV Datei (Nur für den Import)
    Weiterhin wird eine CSV-Datei fortgeschrieben, die alle Veränderungen und mit einem Zeitstempel protokolliert.

Ich könnte mit vorstellen, das Script später auch noch mit Eventlog-Meldungen zu versehen.

Download

groupsync.1.1.zip

Das Archiv enthält beide Skripte:

  • export-groupmember.ps1
    Exportiert die Mitglieder einer Gruppe in eine Datei
  • import-groupmember.ps1
    Aktualisiert die Mitglieder einer Gruppe anhand der Informationen einer Datei

Dies sind beides nur die Basis-Skripte aber liefern alleine natürlich einen Verzeichnisabgleich o.ä. Sie sind schon selbst gefordert die Liste der zu synchronisierenden Gruppen zu erstellen und die Skripte über FOR-Schleifen o.ä. Und individuellen Dateien aufzurufen.

Offen

  • Meldungen im Eventlog
    Damit eine zentrale Überwachung möglich ist, könnte das Skript Warnungen und Fehler im Eventlog melden.
  • Error Handling
    Aktuell ist das Skript so ausgelegt, dass es bei einem Fehler abbricht. für einen dauerhaften unbewachten Betrieb wäre ein Abfangen und Melden der Fehler mit einem kontrollierten Abbruch wünschenswert.
  • Konfiguration als Parameterdatei (XML)
    Das macht es einfacher mehrere Gruppen abzugleichen. Dann muss aber die Ausgabe natürlich direkt in einer anzugebenden Datei landen und eine Verkettung geht nicht mehr so einfach
  • Optimierung mit Cache-Datei
    Wenn ich in einer großen Umgebung mit mehreren tausend Anwendern und Mitgliedern arbeite, dann dauert der Export der aktuellen Mitglieder in einen Hashtable nur kurz. Aber die zum addieren erforderliche Suche von Objekten ist sehr Ressourcenintensiv. Hier könnte man optimieren, indem man sich die Eingangdaten mit der vorherigen Version abgleich und so schon mal erkennt, wer neu dazu gekommen ist. Damit man dann aber auhc die löschen kann, die entfallen sind, muss man diese auch suchun und dann aus der Gruppen entfernen. Dann könnte man aber komplett auf einen Abgleich der "Member" verzichten. Allerdings ist dabei natürlich zwinged erforderlich, dass niemand anderes das Feld "member" pflegt. Ansonsten laufen die Mitglieder der Quelle und des Ziels auseinander.

Weitere Links