GET-ADChanges

Viele Programme und Prozesse ändern Einstellungen und Wert im Active Directory. Wer hier etwas genauer wissen will, was überhaupt passiert, muss irgendwie die Aktionen aktiv protokollieren oder per Auditing aufzeichnen. Dazu gibt es entsprechenden Hilfsmittel wie ADInsight und wenn LDAP unverschlüsselt ist auch NetMon und das AD-Auditing per Eventlog. Aber dies sind alles keine Werkzeuge für den Dauerbetrieb und nicht immer einfach auszuwerten.

Get-ADChanges als Demo
http://www.youtube.com/watch?v=OuKiJ11sPUM
Bitte maximieren Sie die Ausgabe, damit die Schrift gut lesbar ist.

Get-ADChanges als WMV Download (5MB)
get-adchanges.wmv

Programme wie der Active Directory Connector oder der Exchange RUS aber auch die DCs untereinander nutzen eine andere Technik, indem Sie einfach das AD auf Änderungen abfragen. Das kann z.B. über die USN erfolgen, wozu der Prozess keine besonderen Rechte (DomainUser mit Lesen reicht) benötigt. Eleganter geht dies über die DirSync API, die Änderungen auf Feld-Ebene bereit stellen kann. Diese Funktion nutzt GET-ADChanges.

Hinweis

Wenn Sie nur wissen wollen, wann ein Objekte von wem geändert wurde, dann ist eine Suche im Security Eventlog der Domain Controller hilfreicher, da hier bei aktiviertem Auditing entsprechende Einträge der folgenden Art zu finden sind:

Log Name: Security
Source: Microsoft-Windows-Security-Auditing
Event ID: 5136
Task Category: Directory Service Changes
Level: Information
Keywords: Audit Success User: N/A
Computer: DC1.msxfaq.de
Description:
A directory service object was modified.

Subject:
Security ID: msxfaq\Administrator
Account Name: Administrator
Account Domain: msxfaq
Logon ID: 0x642C4B9

Directory Service:
Name: msxfaq.de
Type: Active Directory Domain Services

Object:
DN: CN=Testgroup,DC=msxfaq,DC=de
GUID: CN=Testgroup,DC=msxfaq,DC=de
Class: group

Attribute:
LDAP Display Name: member
Syntax (OID): 2.5.5.1
Value: CN=TestUser,Anwender,DC=msxfaq,DC=de

Operation:
Type: Value Added
Correlation ID: {2552bda6-a9a6-4925-a99e-d344a364d14b}
Application Correlation ID: -

Das folgende Skript ermittelt nur die Änderungen aber nicht, wer sie wo ausgelöst hat.

Funktionsweise

Dieses kleine PowerShell-Skript ist mein Schweizer Messer um Änderungen im Active Directory zu protokollieren. Es nutzt dazu die DirSync API, um über eine LDAP-Abfrage die Änderungen seit der letzten Abfrage zu erhalten. Dazu muss es am Ende der Abfrage einen "Cookie" abspeichern, um bei der nächsten Abfrage dort weitermachen zu können, wo die letzte Abfrage geendet hat.

In das Skript ist eine Logik eingebaut, die eine Vereinzelung von FeldÄnderung vornimmt, d.h. wenn an einem Objekt zwei Felder innerhalb des Abfrageintervalls geändert wurden, dann werden zwei einzelne Ausgaben generiert. Auch die Behandlung von "Member" in Gruppen habe ich schon durchgeführt. Einzig die Ausgabe von von "nicht Textfeldern" z.B. Mehrwertige Felder habe ich noch nicht codiert. Das ist aber in den meisten Fällen keine problematische Einschränkung. Ein Code kann die Felder problemlos danach selbst aus dem AD auslesen.

Berechtigungen

Das Skript "Get-ADChanges" nutzt die DirSync-API, um sich wie ein Domaincontroller auszugeben und die Änderungen seit dem letzten Sync zu erhalten. Über diesen Weg kann das Skript sogar die Kennwort auslesen. Es muss sich einfach als "neuer" Domaincontroller ausgeben und eine komplette Replikation anstarten. Dieser Zugriff ist aber durch ein eigenes Recht geschützt. Nur Domain Controller und natürlich Domain Administratoren können die API nutzen.

Für den Einsatz mit Get-ADChanges müssen Sie aber nicht als Domain Administrator arbeiten, denn Sie können das Recht auch auf der Domäne delegieren.

Hier addieren Sie einfach das Dienstkonto, mit dem Get-ADChanges laufen soll und geben ihm das Recht "Replicating Directory Changes".

Einsatz

Exemplarisch zeige ich, wie sie Get-ADChanges einfach so einsetzen, um Änderungen in einer Domäne auf den Bildschirm ausgehen zu lassen. Die meisten Parameter sind selbsterklärend und mit sinnvollen Daten vorbelegt.

Sie können das Skript aber auch einfach ganz ohne Parameter aufrufen. Dann kommen die Defaults zu tragen, die folgendes bewirken:

  • Verbindung mit der Anmeldedomänen-Partition des angemeldeten Benutzers
  • Überspringen aller alten Änderungen , d.h. Ausgabe der Änderungen seit dem Skriptstart
  • Es wird keine Cookie-Datei gespeichert
  • Skript läuft permanent und beendet sich durch CTRL-C oder Fehler.
  • Änderungen werden in ".\changes.csv" gespeichert.

Wer also "Stateful" arbeiten will, d.h. nach Skriptende den letzten Stand speichern, muss mit zusätzlichen Parametern arbeiten. Der Aufruf ohne Parameter dient zur visuellen Kontrolle von Änderungen , aber zeigt z.B. keine früheren Änderungen an.

Aufruf

Das Skript selbst benötigt keine weiteren Eingaben oder Voraussetzungen und einfach nur eine PowerShell auf einem PC, welcher Mitglied eines Active Directory ist. Beispielhafte Aufrufe sind:

# Aufruf ohne Parameter überwacht Default Domain alle 3 Sekunden mit Ausgabe auf Bildschirm und CSV
get-adchanges.ps1

# Überwachung der Konfigurationpartition in eigenes Log mit Statusspeicherung
get-adchanges.ps1 -ldappath "LDAP://srv01/cn=configuration,dc=msxfaq,dc=local" -cookiefilename = ".\config.cookie" -csvfilename ".\config.csv"

# Sinnloser Aufruf. Once ohne Cookiefile und SkipOld als Default startet und beendet sich
get-adchanges.ps1 -once

Alle Änderungen werden so erst mal auf den Bildschirm geschrieben:

Die meisten Leser werden Get-ADChanges als Diagnosewerkzeug einsetzen und daher das CSV-Protokoll und die Bildschirmausgabe nutzen. Sie können das Skript aber auch auf zwei Arten leistungsfähiger nutzen:

  • get-adchanges.ps1 | process-changes.ps1
    Sie schreiben sich ein eigenes Programm, welche die Ausgaben von Get-ADChanges als Eingaben einliest und nutzt. Das entspricht dem klassischen Pipelining in PowerShell ist ist für schnelle Lösungen interessant. Allerdings gibt es hier keinen Rückkanal, um Fehler an Get-ADChanges zu melden.
  • Funktion in eigenem Skript.
    Daher können Sie natürlich auch einfach in ihrem Skript auf die Funktion von Get-ADChanges zurück greifen und diese im Skript als Routine einbinden oder aufrufen und verarbeiten. Interessant ist dies dann, wenn Sie Get-ADChanges mit "-once" und einem DirSyncCookie kombinieren, da Sie so mit jedem Aufruf die Änderungen erhalten und Verarbeiten können. im Fehlerfall können Sie dann leistungsfähiger darauf reagieren, z.B. die Anfrage mit "mode=prevcookie" erneut aufrufen oder notfalls mit einem "mode=restart"

Die Parameterdefaults sind so gewählt, dass Get-ADChanges einfach nur die aktuelle Anmeldedomäne überwacht und alle Änderungen seit dem Start des Programms auf den Bildschirm ausgegeben werden. Frühere Änderungen werden übersprungen.

Parameter

Die verschiedenen Optionen des Aufrufs von Get-ADChanges werden deutlich, wenn Sie sich die Parameter und deren Funktion anschauen. Die Parameter bedeuten im Einzelnen:

Typ Parameter Default Bedeutung

[string]

cookiefilename

Kein Cookiefile

Das Skript muss, wenn es beendet und später wieder aufgerufen wird, irgendwo den letzten Stand speichern. In der angegebenen Datei landet der "Cookie", der beim nächsten Aufruf wieder an den DC übergeben wird. Die vorherige Version wird als ".old" gespeichert. So kann ein Programm im Fehlerfall über "-redo" diese alte Datei wieder holen.

[string]

ldappath

Kein Default

Wird kein Pfad zu einer Partition angegeben, dann nutzt das Skript die Default Domain Partition des angemeldeten Benutzers. Wer also die Konfiguration Partition überwachen will, muss diese angeben, z.B.: "LDAP://srv01/cn=Configuration,dc=msxfaq,dc=de"

[int]

sleeptime

3 Sekunden

Immer wenn das Skript eine Suche abgeschlossen und die Elemente ausgegeben hat, legt es eine "Pause" ein. Kürzere Pausen belasten das System mehr aber sie können schneller reagieren. Bedenken Sie aber, dass auch die DCs sich untereinander nicht "realtime" abgleichen.

[switch]

once

$false

Wird der Schalter "-once" gesetzt, dann beendet sich das Skript nach einem Durchlauf. Beachten Sie, dass dies nur in Verbindung mit einem Cookie-File Sinn macht. Ansonsten gibt es keine Änderungen zu melden

[switch]

mode

skipold bzw. lastcookie (wenn Cookiefile angegeben

Bestimmt die Betriebsart:

  • lastcookie
    Nutze die zuletzt geschriebene Cookiedatei, wenn ein Cookiefile angegeben wurde
    Standard, wenn Cookiefile angegeben
  • skipold
    Ignoriere alle früheren Änderungen und schreibe nur die Änderungen seit dem Start heraus
    Standard, wenn kein Cookiefile angegeben
  • prevcookie
    Nutz die Backup-Cookiedatei, d.h. seit dem letzten Durchlauf erkannten Änderungen werden erneut gemeldet
  • restart
    Setze eventuell angegebene Cookiedaten und starte vom Anfang an

[switch]

AddMemberOf

$false

Generiert zusätzliche Events für die Benutzer, wenn die Objekte bei einer Gruppe addiert werden. Kann leider nicht ermitteln, wenn eine Gruppe gelöscht wurde

[switch]

nosave

$false

Mit dem Schalter "-nosave" wird der Cookie am Ende einer Bearbeitung nicht zurück geschrieben. Beim nächsten Aufruf werden also die zuletzt gefundenen Änderungen erneut zurück gegeben. Dies ist primär für Tests geeignet.

[switch]

verbose

$false

Durch die Angabe von "-verbose" werden detailliertere Ausgaben während der Verarbeitung gemacht, die bei eier Fehlersuche helfen können.

[switch]

noscreen

$false

Deaktiviert die zusätzliche Ausgabe auf den Bildschirm der gefundenen Änderungen . Sinnvoll, wenn die normale Ausgabe in die Pipeline nicht mit "| out-null" unterdrückt oder mit einem anderen Prozess weiter verarbeitet wird.

[switch]

pipeline

no

Steuert die Ausgabe der Daten an die Pipeline zur weiteren Verarbeitung

  • No
    Keine Ausgabe in die Pipeline
  • mini
    Gibt nicht das komplette Objekt sondern nur die "Identity" zurück. Damit eröffnen sich Möglichkeiten per Pipeline dann nachfolgende Commandlets (z.B. get-ADUser) anzusteuern
  • full
    Kompletter Datensatz

[switch]

AddMoveSub

$false

Noch nicht implementiert !
Generiert zusätzliche Events für jedes Objekt unter einer OU, welcher verschoben wurde. Das bedeutet aber dass das Skript recursiv alle Unterobjekte auflistet und als "move" ausgibt..

[string]

filterDN

".*"

erlaubt die Filterung der Ausgabe anhand eines Teilstrings des distinguishedname. Leider kann die DirSync-API immer nur komplette Partitionen überwachen so dass ich nach dem Erkennen einen Filter eingebaut habe. Denkbar ist z.B.

  • .*\cn=Users,.*
    Bearbeitet nur Objekte in der Default-OU "User"
  • .*\ou=Benutzer,.*
    Bearbeitet nur Objekte in allen OUs, die "Benutzer" heißen
  • .*,cn=adm-.*$  (vereinfacht)
    Berücksichtigt nur Objekte, die mit einem "ADM-" im Namen beginnen.

Weitere Kombinationen sind natürlich möglich. Siehe auch RegEx

[string]

csvfilename

".\changes"

Eigentlich ist das Skript so angelegt, dass die Änderungen über die Pipeline an ein nachfolgendes Programm gegeben werden. Oft ist das der Fall aber zu Diagnosezwecken ist es doch wünschenswert ein "Changelog" zu haben. Diese Datei wird automatisch beschrieben. Setzten Sie den Parameter auf "$null", damit die Datei nicht angelegt wird.
An den Dateinamen wird das Jahr, Monat, Tag in der Form YYYYMMTT angehängt. (Vergleichbar zum IISlog)

Da PowerShell-Skripte nicht wirklich "verschlüsselt" sind, können Sie entsprechende Änderungen leicht selbst vornehmen.

Ausgaben

Die Bildschirmausgaben sind natürlich nur eine Funktion der PowerShell, welche die Pipeline am Ende auf die Konsole schreibt. Eine Weiterverarbeitung ist natürlich sehr einfach möglich.

... in die Pipeline

Das Übergebene Objekte hat folgende Eigenschaften

Property Typ Beispiel Bedeutung
Timestamp

DateTime

Donnerstag, 16. Juni 2011 05:45:34

Zeitstempel, wann die Aktion erkannt wurde. Dies ist nicht identisch mit dem Zeitpunkt der Änderung. Diese kann auf einem anderen DC schon früher erfolgt sein und durch AD-Replikation verzögert sein. Aber auch GET-ADChanges kann nicht "realtime" überwachen

objectguid

ADS Collection

{94 186 163 249 47 205 44 67 150 251 38 89 152 202 96 183}

Jedes Objekt im Active Directory hat eine eindeutige GUID, welche sich auch beim Verschieben zwischen Domains im gleichen Forest nicht ändern sollte. Ideal also um Objekte nachzuverfolgen.

Hinweis: Das Feld ist nicht gefüllt, wenn mit "AddMemberOf"-Events für die Mitglieder einer Gruppe generiert werden

Identity

AD Property

{CN=User1,OU=msxfaq,DC=E2010,DC=local}

DistiguishedName aus dem Das ADPAth-Property der Suchanfrage. Die ist kein String !

action

String

Ein String aus folgender Sammlung:
Modify, Delete, CreateOrMove, MemberAdd, MemberDel, MemberError, Start, End

Zeigt ihnen die Art der Veränderung an:

  • Modify
    Feld wurde geändert
  • CreateOrMove
    Objekte wurde angelegt oder in die ZielOU verschoben
  • MemberAdd, MemberDel
    Sonderbehandlung des Multivalue-Felds "Member". Werden mehrere Benutzer addiert, dann werden pro Mitglied eigene Einträge generiert
  • MemberError
    MemberError bedeutet, dass ein nicht näher behandelter Fehler aufgetreten ist
  • MemberOfAdd, MemberOfDel
    Synthetische Events mit den Benutzern als Identity
  • Add
    Behandlung von "range=1-1" im Feldnamen
  • Del
    Behandlung von "range=0-0" im Feldnamen
  • Start, End
    Diese Einträge signalisieren, dass GET-ADChanges gestartet oder.
field

String

department

Name des geändertes Feldes.
Achtung: Mehrwertige Felder können mit einem ";range=1-1" oder ";range=0-0" abgeschlossen werden um einzelne Änderungen zu melden. für das Feld "member" bei Gruppen habe ich eine eigene Behandlung bereits eingebaut.

value

Variable

{test}

Neuer Wert des Felds. Kann "$null" sein, wenn das Feld geleert wurde aber auch Arrays enthalten. Ihr Programm sollte anhand des Feldnamens wissen, wie es mit den Inhalten umgehen muss.

Die Ausgaben sind "Objekte", d.h. per Pipeline können Sie die Ausgaben auch weiter verarbeiten. Das einfachste ist natürlich die Ausgabe in einer CSV-Datei

get-adchanges.ps1 | export-csv .\aenderungen.csv

... CSV-Datei

Get-ADChanges schreibt aber zusätzlich die Änderungen auch in eine Datei ".changes.csv", welche mit dem Parameter CSVFile auch abweichend spezifiziert oder mit $null sogar abgeschaltet werden kann. Allerdings handelt es sich bei dem Namen immer nur um ein Prefix. Das Skript addiert per Default immer noch das Jahr, den Monat und den Tag, so dass je Tag eine angelegt wird. So erreiche ich, dass die CSV-Datei nicht endlos groß wird.

Die CSV-Datei hat folgende Spalten, die im Header der CSV-Datei auch beschrieben sind

  • datetime - Ein Zeitstempel (UTC)
  • Action - Die erkannte Aktion
  • Identity -das betreffende Objekt
  • Field - das Active Directory Feld
  • Value -der Wert, sofern er "druckbar" ist

Die genauere Beschreibung der Inhalte finden Sie weiter oben bei der Ausgabe in die Pipeline. Der Inhalt einer solchen Datei ist entsprechend überschaubar:

Diese Datei ist aber wirklich eher wie ein IISLog als Protokoll zu sehen und kann natürlich mit einer Volltextsuche o.ä. bearbeitet werden. für eine zeitnahe Ausführung von Aktionen sollte jedoch besser die Pipeline-Ausgabe ausgewertet werden. Aufgrund der begrenzen AusgabeMöglichkeiten enthält die Spalte "Value" nur als Text darstellbaren Daten, d.h. String, numerische Werte. Arrays und andere komplexe Konstruktionen werden nicht ausgegeben.

Ausgabe

Get-ADChanges ist ein PowerShell-Skript, was natürlich in einer PowerShell ausgeführt werden muss. Entsprechend "schmucklos" sind die normalen Ausgaben. Das ist auch nicht relevant, da die Daten sowieso in einer CSV-Datei und optional der Pipeline zur Weiterverarbeitung landen. Dennoch können Sie natürlich Get-ADChanges auch einfach interaktiv aufrufen. Allerdings sollten Sie dann die Option "-noscreen" addieren oder die Pipelineausgabe mit " | out-null" unterdrücken, damit die Daten nicht doppelt ausgegeben werden. Hier ein Beispiel einer einzelnen Änderung:

Sie sehen hier zum einen, dass das Feld "L" (Location) mit "NeuerOrt" gefüllt wurde. Die anderen Aktionen zeigen Änderungen an den Gruppenmitgliedschaften an. Per "-NoScreen" wurden die Bildschirmausgaben mit "-noscreen" unterdrückt und die Pipelineausgabe mit "Format-Table" zurecht geschnitten, um z.B. die GUID nicht zu benötigen.

Wenn Sie die Ausgaben der Pipeline weiter verarbeiten möchten, dann wird dies durch Angabe der "Option "-minipipeline" unterstützt. Hier mit Get-ADUser als verarbeitenden Commandlets.

Spielen Sie einfach etwas damit herum. Solange Sie die Ergebnisse nicht in einem "SET-*"-Commandlet verarbeiten, ist GET-ADChanges nur "readonly"

Einschränkungen

  • Ein DC als Ziel
    Um die DirSync-API sinnvoll nutzen zu können, muss immer der gleiche DC verwendet werden. Der Wechsel eines DC kann dazu führen, dass frühere Änderungen erneut gemeldet werden
  • Eine Partition
    Die API bindet sich auf eine Partition, Wer also mehrere Domains und die Configuration-Partition überwachen will, muss mehrere Instanzen starten.
  • Kein Wer und Wo und Wann
    Sie können nicht sehen, welcher Benutzer oder Prozess das Feld geändert hat. Auch sehen Sie die Änderung erst, wenn sie auf dem DC angekommen ist, den Sie abfragen. Wer genauere Informationen braucht, muss auf den Domain Controllern eine Zusatzsoftware installiere oder die Überwachung aktivieren (-> Auditing)
  • Letzte Änderung gewinnt
    Wird ein Feld mehrfach geändert, dann erhält man nur die letzte Änderung. Das ist kritisch, wenn Sie z.B. die Mitglieder der Domänen Administratoren überwachen wollen und jemand sich zwischen zwei Abfragen kurz addiert, anmeldet und wieder entfernt. Dazu ist diese API nicht gedacht (-> Auditing)
  • Full Export nicht pro Feld
    Wenn ich alle Änderungen vom Anfang an exportiere, dann geht dies pro Objekt und nicht mehr pro einzelnem Feld. Änderungen an Feldern eines Objekts kommen gebündelt.
  • Berechtigungen
    Um die a href="../../../code/dirsynccontrol.htm" title="../../../code/dirsynccontrol.htm" class="linkintern">DirSync API zu nutzen, reicht es nicht mehr nur Domain Benutzer zu sein. Die meisten werden daher das Skript als Administrator starten. Sie können einem Dienstkonto natürlich auch die passenden Berechtigungen geben.

Zudem gibt es die Einschränkungen, dass z.B. nur direkte Änderungen erkannt werden. Wir ein Benutzer also in eine Gruppe aufgenommen, dann wird nur eine Änderung von "Member" an der Gruppe erkannt. Beim Benutzer ändert sich das Feld "MemberOf" nicht. Eine Aufzählung aller Besonderheiten würde aber den Rahmen dieses Artikels sprengen./p>

Download

Dieses Skript ist aktuell noch kein öffentlich verfügbarer Download. Ich habe gerade in den letzten Wochen vor Publizierung dieser Seite gemerkt, dass das Skript und vor allem die Schnittstellen (Parameter als auch Pipelineausgabe) noch zu oft geändert wurden. Diese Flexibilität hätte nicht nicht mehr, wenn das Skript "Public" ist und ich was ändern müsste.
Bitte haben Sie daher noch etwas Geduld, dass ich das Skript aktuell nur in Kundenprojekten einsetze und weiter entwickle.

Zudem plane ich, Get-ADChanges in ein größeres Projekt einzubinden und möchte mir daher alle Freiheiten behalten, den Code zu ändern, anzupassen etc.

Offen

Get-ADChanges hat einige Iterationen durchlaufen, bis es auf dem aktuellen Stand angekommen ist. Und eventuell werden zukünftige Weiterentwicklungen hier weitere Änderungen erfordern.

  • Optimierte Bildschirmausgabe
    Wer mit Get-ADChanges keine Weiterverarbeitung macht, sondern nur die Bildschirmausgaben für eine Fehlersuche oder Analyse benötigt, wird bestimmte Felder nicht "schön" anzeigen können. z.B. Arrays oder Security Descriptoren. Hier könnte ich mir eine Erweiterung derart vorstellen, dass ein Parameter dem Skript sagt, dass es eine nettere Formatierung vornimmt und keine Ausgabe in die Pipeline schreibt, die ansonsten den Bildschirm verwirbeln könnte.
  • Multivalue-Felder
    Auch fehlt eine bessere Verarbeitung diese Multivalue und Binary-Felder zur Ausgabe in der CSV-Datei. Hier steht dann nur der Variablentyp, z.B. "byte[] " und nicht die Inhalte, weil der "Write.Host" oder der "Out-File" die Daten noch nicht interpretieren kann. Das ist besonders für die GUIDs, die ein Objekt eindeutig selbst bei Verlagerungen zwischen Domains im gleichen Forest eindeutig identifiziert
  • Rekursives Verschieben von OUs
    Wird eine OU an eine andere Position verschoben, dann wird per DirSync nur der Move der einzelnen OU gemeldet aber nicht der Unterobjekte . Insofern wäre es für eine einfachere Verarbeitung natürlich auch interessant, für die untergeordneten Objekte einen "Move"-Event zu generieren und so die Verarbeitung zu vereinfachten.
  • Optional Ausgabe in SQL
    CSV-Dateien sind nett aber skalieren schlecht, wenn man suchen will. Eine SQL-Tabelle mit passenden SELECT wäre eine nette Idee um die Änderungshistorie eines Objekts zu verfolgen. Eventuell sogar mit SQL-Reporting Services. Das Skript müsste dann aber natürlich Transaktionssicher die Daten addieren. Bis dahin sollten Sie einfach die CSV-Dateien in ihre SQL-Tabelle importieren.

Es bleibt also noch das ein oder andere zu tun.

Weitere Links