Printout Header
RSS Feed

LDAP Objekte im Verzeichnis suchen (ADO)


Wenn man weiß, welchen LDAP-Pfad das Objekt hat, auf dass man zugreifen will, oder in welcher OU oder welchem Container sich die gewünschten Objekte befinden, ist es kein Problem, darauf zuzugreifen: Dies geschieht mit einem einfachen Bind-Vorgang.

Schwieriger wird es, wenn Sie im Verzeichnis nach Objekten suchen wollen, und zwar

       - nach bestimmten Kriterien und
       - rekursiv in allen Unter-Container ab einer bestimmten Stelle des Verzeichnisses.

Dies ist für ADS und auch für beliebige andere LDAP-Verzeichnisse problemlos möglich, und zwar mit Hilfe einer speziellen Schnittstelle, den ActiveX Data Objects (ADO).

Folgende Abschnitte stehe auf dieser Seite zur Verfügung:


Activex Data Objects (ADO)
 
Verzeichnissuche vorbereiten
Verzeichnissuche durchführen
Ergebnisse verwenden
Attribute als Ergebnisse
Suche im gesamten ADS Forest (GC-Suche)
Paged Results - Max. Anzahl an Suchergebnissen
 
Server-Pagesize konfigurieren (ADS)
Server-Pagesize konfigurieren (Exch55)
 
Beispielsuche - ADS
Beispielsuche - Exchange 5.5



Active Data Objects (ADO)


Hinter ADO (auch ADODB genannt) verbirgt sich eine Schnittstelle, mit der sich auf Datenbanken zugreifen läßt. Dieser Zugriff kann dann innerhalb eines VBScripts, aber auch mit anderen Programmiersprachen geschehen. Mit ADO lassen sich nahezu alle gebräuchlichen Datenbanken ansprechen, wie z.B. Microsoft Access, SQL-Server, MySQL, Oracle oder Informix - und eben auch Verzeichnis-Datenbanken, auf die per LDAP zugegriffen werden kann.

ADO ist Teil der Microsoft Data Acces Components (MDAC), die Sie ab Windows 2000 als integraler Bestandteil des Betriebssystems verwenden können. Sie können die unten folgenden Beispiele also sofort ausprobieren, ohne spezielle DLLs installieren zu müssen. Neuere Versionen von MDAC gibt es hier: Download-Seite für Microsoft Data Access Components (MDAC).

Die wichtigsten Informationen über die AcitveX Data Objects findet man ebenfalls bei Microsoft:

MSDN: ADO Reference - ActiveX Data Objects
Technet: Searching Active Directory
MSDN: Searching the directory with ADO

Der Microsoft KnowledgeBase-Artikel Q183606 bietet außerdem einen recht guten Überblick und eine kleine FAQ-Liste zu ADO.

Bei ADO-Connections wird der Zugriff auf die verschiedenen Datenbank-Typen durch sogenannte ADO-"Provider" realisiert. Für den ADSI-Zugriff auf LDAP existiert der spezielle ADO-Provider "ADSDSOObject". Dieser Provider erlaubt die Übergabe von Anmeldedaten, die gefilterte Suche mittels Standard-LDAP-Filtern und vor allem die hierarchische Suche in Substrukturen, z.B. in Unter-OUs.

Da ab Windows 2000 MDAC und damt auch ADO integraler Bestandteil des Betriebssystems ist, können sie die folgenden Beispiele sofort ausprobieren, ohne spezielle DLLs installieren zu müssen.


Verzeichnissuche vorbereiten


Zuerst müssen wir ein ADO-Objekt vorbereiten, das den ADSSDOObject-Provider verwendet. Hier werden dann die Anmeldedaten festgelegt. Der Parameter "Encrypt Password" sorgt dafür, dass gegenüber einem Domänen-Controller unter Windows 2000 aufwärts eine Kerberos-Anmeldung mit verschlüsselten Benuternamen und Passwörtern abläuft.


Set ado = CreateObject("ADODB.Connection")            'Neue ADO Connection erzeugen
ado.Provider = "ADSDSOObject"                         'Die ADSI-Schnittstelle verwenden
ado.Properties("User ID") = Anmeldname                'Credentials angeben - geschieht das nicht, wird die Suche mit den ...
ado.Properties("Password") = Passwort                 '...Anmeldedaten des aktuellen Benutzers ausgeführt
ado.Properties("Encrypt Password") = True
ado.Open "Verbindungs-Name"                           'Beliebigen Namen für die Connection vergeben

Als Anmeldename können alle Formen verwendet werden, die im ensprechenden SelfADSI-Abschnitt über den LDAP-Bind Vorgang beschrieben wurden. Als Verbindungsname können Sie einen Bezeichner frei wählen - er dient lediglich zur internen Identifizierung der Verbindung im Script.


Verzeichnissuche durchführen


Eine gefilterte Suche wird durch die Methode Executedes vorher erstellten ADO-Objektes ausgeführt:


Set objectList = ado.Execute("<LDAP://dc2.cerro.local/ou=test,dc=cerro,dc=local>;LDAP-Filter;ADSPath;subtree")

Hierbei wird die sogenannte Search-Base angegegeben, also der Container, von dem aus gesucht wird. Es kann sich hierbei auch um eine ADS-Domäne handeln. Die Search-Base muss als kompletter LDAP-Pfadname angegeben werden.


Zusätzlich dazu muss ein LDAP-Filter mit angegeben werden. Dieser entscheidet, nach welchen Kriterien gesucht wird. Es gelten die allgemeinen Regeln für LDAP-Filter.


Außerdem ist noch die Angabe der Objekteigenschaft notwendig, die als Ergebnis zurückgegeben werden soll- wir verwenden hier meist die Eigenschaft "ADSPath" - es kommen hier als Ergebnis die LDAP-Pfadnamen der gefundenen Objekte zurück.

 

Der letze Parameter "subtree" bewirkt, dass die Suche in allen rekursiv in allen Unter-Containern durchgeführt wird. Als mögliche Parameter stehe hier zur verfügung:

 

     base:       Die Suche liefert nur das Objekt selbst zurück, dass als Search-Base angegeben wurde. So kann gecheckt werden,
                     ob das Objekt existiert oder nicht.

     onelevel: Die Suche liefert nur Objekte zurück, die unmittelbar innerhalb des angegebenen Basis-Containers liegen.

     subtree:  Rekursive Suche im angegebenen Basis-Container und allen vorhandenen Subcontainern.


Ergebnisse verwenden


Das Ergebnis des Execute-Kommandos liefert das ADO-Objekt in einem Array namens Fields zurück. Die in der Suche abgefragten Eigenschaft (in unserem Fall der LDAP-Pfadname der gefunden Objekte) stehen als "Value" bei den einzelnen Array-Elementen bereit.


Man könnte sich z.B. direkt mit einem Standard-Bind Vorgang jedes gefundene Objekt nehmen und irgendetwas damit machen - Infos ausgeben, verändern, löschen, usw. usw.


WScript.Echo objList.RecordCount

While Not objectList.EOF                     'Hier wird die angeforderte Eigenschaft ADSPath verwendet
    Set object = GetObject(objectList.Fields(0).Value)
    ...
    ...
    ...
    objectList.MoveNext                      'Zum nächsten Suchergebnis weitergehen
Wend

Um zum nächsten Ergebnis zu gelangen, verwenden wir den Befehl MoveNext.


Falls Sie wider Erwarten keine Ergebnisse Ihrer LDAP-Sucher erhalten und es auch keine Fehlermeldung gibt, dann ist die Suchafrage am Server vielleicht durch einen MaxPageSize-Wert beschränkt. Lesen Sie dazu den nachfolgenden Abschnitt Paged Result - Maximale Anzahl an Suchergebnissen.


Attribute als Ergebnisse


Im "Execute"-Aufruf der ADODB-Connection wird als Parameter die Eigenschaft mitgegeben, die man als Ergebnis der Suche zurückhaben möchte. In den vorherigen Erklärungen sind wir nach dem Prinzip vorgegangen, als Ergebnisparameter den LDAP-Pfadnamen zurückzufordern ("ADSPath"). Mit diesem LDAP-Pfad wurde dann eine Verbindung mit dem eigentlichen Objekt gemacht. Dies ist notwendig, wenn wir auf die Objekte auch tatsächlich zugreifen wollen, z.B. um dort Attribute zu änbdern.


Wenn wir hingegen nur Eigenschaften anzeigen lassen wollen und sonst keinen direkten Zugriff auf die Objekte benötigen, können wir diese Eigenschaften auch direkt im Execute-Kommando mit übergeben. Es kann sich dabei auch um mehrere Atribute handeln, diese werden dann einfach durch Kommas getrennt.


Ein Beispiel, mit dem direkt nach den Anzeigenamen und Mail-Adressen von Benutzern in einer Domäne gesucht wird:


Set objectList = ado.Execute("<LDAP://dc1.cerro.local/dc=cerro,dc=local>;" & _
                             "(objectClass=user);displayName,mail;SubTree")
While Not objectList.EOF
                                             'Hier werden die gefundenen Anzeigenamen und Mailadresse angezeigt
    WScript.Echo objectList.Fields("displayName") & " : " & objectList.Fields("mail")
    ...
    ...
    ...
    objectList.MoveNext                      'Zum nächsten Suchergebnis weitergehen
Wend

Wie sie sehen, können wir anstatt des Array-Index (objectList.Fields(0)) auch direkt den jeweiligen Attributnamen als Zugriffsindex verwenden (objectList.Fields("displayName")).


Suche im gesamten ADS Forest


Die bisherige Vorgehensweise bei einer ADO-Suche nach Active Directory Objekten war es, einen Domänen-Controller über LDAP mittels der ADO-Schnittstelle anzufragen. Dabei muss man sich jedoch bewußt sein, dass Domänen-Controller grundsätzlich nur Objekte der eigenen Domäne speichern - vom Schema und der Configuration Partition einmal abgesehen. Wenn wir jedoch nach "normalen" Objekten (User, Gruppen, Computer) im gesamten Forest suchen wollen, müssen wir spezielle Vorkehrungen treffen. Zwei verschiedene Ansätze sind denkbar:

  1. Man fragt aus der Configuration Partition die Anzahl der Domänen ab, sucht für jede Domäne den nächstgelegenden Domänen-Controller (über DNS), und stellt die Suchanfrage an alle gefundenen Domänencontroller. Diese Methode ist natürlich sehr aufwendig. Man muss dazu zunächst einmal aus jeder Domäne im Forest einen passenden Domänen-Controller ermittlen.

  2. Einfacher geht´s über den Global Catalog. Hier können auf einen Schlag Objekte aus dem gesamten Forest zurückgegeben werden. Man muss dazu nur einen Global Catalog-Domänencontroller ausfindig machen. Seien Sie sich jedoch bewußt, dass im Global Catalog nicht alle Attribute der Objekte enthalten sind, sondern lediglich ein Subset der wichtigsten Attribute! Eine ADO-Suche im Global Catalog wird durchgeführt, indem man beim Server den Global Catalog-Port 3268 verwendet und dann als LDAP-Pfad die Root-Domäne des Forest einträgt. Die Suchtiefe muss dabei stets "SubTree" betragen. Als Ergebnis bekommt man Objekte aus dem gesamten Forest, die den Suchkriterien entsprechen:
. . .                                         'GC-Suche mit Root-Domain als Searchbase durchführen:
adoCmd.CommandText = "<LDAP://gc1.cerro.local:3268/dc=cerro,dc=local>;LDAP-Filter;ADSPath;subtree"

Set objectList = adoCmd.Execute               'Suche durchführen

Wenn Sie z.B. einfach nur alle Gruppen des Forests aufzählen wollen, dann ist eine GC-Suche genau das richtige. Wenn Sie jedoch alle Benutzer suchen, die ein bestimmtes Login-Script verwenden, dann müssen Sie zur ersten Methode greifen, denn das Attribut für das Login-Script ist nicht im Global Catalog enthalten.

Microsoft-Erläuterungen zur LDAP Suche im Global Catalog (alt, aber immer noch nützlich)


Paged Result - Maximale Anzahl an Suchergebnissen


In einem LDAP-Suchvorgang muss stets damit gerechnet werden, dass der LDAP-Server eine Obergrenze hat bei der Anzahl der Ergebnisse in einer Suchanfrage. Man sucht z.B. nach allen User-Objekten in einer gesamten OU-Struktur, bekommt aber nur 500 User als Ergebnis zurück - obwohl sich weit über 2000 User auf dem Server befinden müssen. Der Server liefert in einem solchen Fall stets nur eine begrenzte Anzahl an Suchergebnissen, egal wie oft man die LDAP-Suche z.B. über die ADO-Schnittstelle durchführt. Man nennt diese Beschränkung eines LDAP-Servers auch "MaxPageSize".


Speziell ADS-Domänen-Controller und Exchange 5.5-Server haben standardmäßig eine MaxPageSize gesetzt (1000 für Active Directory und 100 für Exchange 5.5). Wie man diesen Parameter zentral auf der Server-Seite ändern kann, lesen Sie die nächsten zwei Abschnitte.


Man kann jedoch auch aus dem eigenen Script heraus ALLE Objekte eines Suchvorgangs erhalten, selbst wenn der Server eine MaxPageSize-Bschränkung aufweist. Man muss dazu eine LDAP-Suche mit der Eigenschaft "Paged Results"angeben. Es handelt sich um einen Wert, der den Server anweist, die Ergebnisse "päckchenweise" zu übermitteln, und zwar so lange, bis wirklich alle Objekte des Suchvorgangs an den anfragenden Client übermittelt sind. Diese Technik spielt sich direkt im LDAP-Protokoll ab (spezifiziert in RFC 4511), im Script selbst muss man nur dafür sorgen, dass bei der LDAP-Suche der Paged Result Wert gesetzt wird:

Die Syntax für eine Paged Result Suche sieht so aus:


Set ado = CreateObject("ADODB.Connection")            'Neue ADO Connection erzeugen
ado.Provider = "ADSDSOObject"                         'Die ADSI-Schnittstelle verwenden
ado.Properties("User ID") = "Anmeldname"              'Credentials angeben
ado.Properties("Password") = "Passwort"
ado.Properties("Encrypt Password") = True
ado.Open "Verbindungs-Name"                           'Beliebigen Namen für die Connection vergeben

Set adoCmd = CreateObject("ADODB.Command")            'Neues ADO-Kommando erzeugen
adoCmd.ActiveConnection = ado                         'Zuordnung zur bestehenden ADO-Connection
adoCmd.Properties("Page Size") = 100                  'Jetzt können spezielle Suchparameter gesetzt werden
adoCmd.Properties("Cache Results") = True
adoCmd.CommandText = "<LDAP://dc2.firma.de/ou=test,dc=firma,dc=de>;LDAP-Filter;ADSPath;subtree"

Set objectList = adoCmd.Execute                       'Suche durchführen


Maximale Anzahl der Suchergebnisse im AD konfigurieren


Bei einer mit ADO durchgeführten Verzeichnis-Suche müssen Sie beachten, dass ein Windows Domänen-Controller per default maximal 1000 Suchergebnisse zurückliefert. Dies soll verhinden, dass Benutzer der Domäne, die standardmäßig über Leserechte im Verzeichnis verfügen, mit groß angelegten LDAP-Suchvorgängen den Server lahmlegen können.

Die maximale Anzahl der zurückgegebenen Sucherergebnisse wird im Active Directory Parameter "MaxPageResult" festgelegt. Dieser läßt sich mit dem Utility NTDSUTIL ändern. Die Vorgehensweise wird im KnowledgeBase-Artikel Q315071 beschrieben. Sie starten dazu als Enterprise Administrator NTDSUTIL an einem Domänen-Controller und geben dann folgende Befehle ein:


ldap policies
connections
connect to server <name of the local domain controller>
quit
set maxpagesize to <new maximum value for search results>
commit changes
quit

Vergessen Sie nicht den Befehl commit changes, denn sonst werden die Änderungen nicht wirksam! Der Parameter ist übrigens global für den gesamten ADS Forest und wird nach der normalen ADS-Replikation auf den betreffenden Domänen-Controllern gültig, ohne dass diese neu gebootet werden müssen.


NTDSUtil Screenshot

In unserem Beispiel wird übrigens noch der Befehl show values hinterhergeschickt, um den neu gesetzten Wert zu überprüfen.


Die LDAP-Policies werden bei ADS übrigens direkt in der Configuration Partition des Verzeichnisses gespeichert, und zwar im folgenden Objekt: cn=Default Query Policy,cn=Query-Policies,cn=Directory Service,cn=Windows NT,cn=Services,cn=Configuration,dc=Forest RootDomain. Dieses Objekt hat ein Attribut namens lDAPAdminLimits:


ADSIEdit Screenshot

Wie man sieht, handelt es sich hierbei um ein Attribut des Typs Multivalued String, in dem die Parameter-Werte einfach in lesbarer Form aufgeführt werden.


Maximale Anzahl der Suchergebnisse in Exchange 5.5 konfigurieren


Bei einer mit ADO durchgeführten Verzeichnis-Suche müssen Sie beachten, dass ein Exchange 5.5-Server per default maximal 100 Suchergebnisse zurückliefert! Dies soll verhinden, dass anonyme LDAP-Clients, die standardmäßig über Leserechte im Verzeichnis verfügen, mit groß angelegten LDAP-Suchvorgängen den Server lahmlegen können.


Die maximale Anzahl der zurückgegebenen Sucherergebnisse wird im Exchange-Admin Tool bei der Konfiguration des LDAP-Protokolls (in der Site-Konfiguration oder in den Eigenschaften eines Server-Objektes) festgelegt:


Ex55Admin Screenshot

Beispielsuche im Active Directory


Gesucht wird nach allen Usern, die Exchange-Empfänger sind (der Exchange-Alias ist als Attribut mailNickName vorhanden) und im Adressbuch versteckt sind (Attribut msExchHideFromAddressLists hat den Wert TRUE). Ausgangspunkt der Suche ist die Domäne cerrotorre.de. Von den gefundenen Objekten wird der Anzeigename ausgegeben.


'Sie müssen hier andere Namen und Anmeldedaten aus Ihrer eigene Umgebung angeben!
serverName = "nadrash.cerrotorre.de"
baseStr = "dc=cerrotorre,dc=de"

userName = "philipp@cerrotorre.de"

userPass = "secret"



filterStr = "(&(&(objectclass=user)(mailNickName=*))(msExchHideFromAddressLists=TRUE))"

Set ado = CreateObject("ADODB.Connection")              'Neue ADO Connection erzeugen
ado.Provider = "ADSDSOObject"                           'Die ADSI-Schnittstelle verwenden
ado.Properties("User ID") = userName                    'Credentials angeben - wenn sie diese zwei Zeilen weglassen, ...
ado.Properties("Password") = userPass                   '... wird die aktuellen Anmeldedaten verwendet
ado.Properties("Encrypt Password") = True
ado.Open "ADS-Search"                                   'Beliebigen Namen für die Connection vergeben

Set adoCmd = CreateObject("ADODB.Command")              'Neues ADO-Kommando erzeugen
adoCmd.ActiveConnection = ado                           'Zuordnung zur bestehenden ADO-Connection
adoCmd.Properties("Page Size") = 1000                   'Parameter für Paged Result Suche auf 1000 setzen (=AD Standard)
adoCmd.Properties("Cache Results") = True
adoCmd.CommandText = "<LDAP://" & serverName & "/" & baseStr & ">;" & filterStr & ";ADsPath;subtree"

Set objectList = adoCmd.Execute                         'Suche durchführen

While Not objectList.EOF
    Set user = GetObject(objectList.Fields("ADsPath"))  'mit gefundenen Objekten verbinden

    WScript.Echo user.displayName                       'Anzeigename ausgeben

    objectList.MoveNext                                 'zum nächsten gefundenen Objekt
Wend

Wenn man andere LDAP-Filter nimmt, kann man die Suche entsprechend verändern:


. . .
filterStr = "(&(objectCategory=person)(objectClass=user))"    'alle Benutzer suchen
filterStr = "(objectClass=group)"                             'alle Gruppen suchen
filterStr = "(&(objectCategory=person)(objectClass=contact))" 'alle Kontakte suchen
filterStr = "(msExchangeHomeserverName = " & _
            "/o=MAILOrg/ou=First Administrative Group/cn=Configuration/cn=Servers/cn=KUNGUR"
                                                              'alle PostfachUser auf Server "KUNGUR" suchen
filterStr = "(msExchHideFromAddressLists=TRUE)"               'alle versteckten Empfänger suchen,
filterStr = "(displayName=F*)"                                'alle Empfänger, deren Anzeigename mit F beginnt
. . .

Viele weitere Beispiele für LDAP-Filter finden sie im SelfADSI-Tutorial - Beispiele für LDAP-Filter in ADS-Umgebungen. Wichtige weiterführende Informationen des SelfADSI-Tutorials:

Abschnitt "LDAP Filter".
Abschnitt "Verbindung mit dem Verzeichnis und Objekten herstellen".
Abschnitt "Attribute von ADS Usern"
Abschnitt "Attribute von ADS Gruppen"
Abschnitt "Attribute von ADS Kontakten"


Beispielsuche im Exchange 5.5-Verzeichnis


Gesucht wird nach einem Postfach, dem die SMTP-Adresse "sandra@cerrotorre.de" zugeordnet hat, und zwar als primäre oder sekundäre Adresse (die primäre Adresse ist diejenige, die als Absender-Adresse verwendet wird). Die primäre Adresse wird bei Exchange 5.5 als Attribut mail gespeichert. Die sekundären Adressen stecken im Attribut othermailbox. Ausgangspunkt der Suche ist die Exchange 5.5-Organisation namens CERROMAIL. Von den gefundenen Objekten wird der Anzeigename ausgegeben.

 

'Sie müssen hier andere Namen und Anmeldedaten aus Ihrer eigene Umgebung angeben!
serverName = "kailash.cerrotorre.de"
baseStr = "o=CERROMAIL"

userName = "CERROTORRE\philipp"

userPass = "secret"



filterStr = "(|(mail=sandra@cerrotorre.de)(othermailbox=smtp:sandra@cerrotorre.de))"

Set ado = CreateObject("ADODB.Connection")              'Neue ADO Connection erzeugen
ado.Provider = "ADSDSOObject"                           'Die ADSI-Schnittstelle verwenden
ado.Properties("User ID") = userName                    'Credentials angeben - wenn sie diese zwei Zeilen weglassen, ...
ado.Properties("Password") = userPass                   '... wird die aktuellen Anmeldedaten verwendet
ado.Properties("Encrypt Password") = True
ado.Open "EX55-Search"                                  'Beliebigen Namen für die Connection vergeben

Set adoCmd = CreateObject("ADODB.Command")              'Neues ADO-Kommando erzeugen
adoCmd.ActiveConnection = ado                           'Zuordnung zur bestehenden ADO-Connection
adoCmd.Properties("Page Size") = 99                     'Parameter für Paged Result Suche setzen
adoCmd.Properties("Cache Results") = True
adoCmd.CommandText = "<LDAP://" & serverName & "/" & baseStr & ">;" & filterStr & ";ADsPath;subtree"

Set objectList = adoCmd.Execute                         'Suche durchführen

While Not objectList.EOF
    Set user = GetObject(objectList.Fields("ADsPath"))  'mit gefundenen Objekten verbinden

    WScript.Echo mbx.name                               'Anzeigename ausgeben

    objectList.MoveNext                                 'zum nächsten gefundenen Objekt
Wend

 

Wenn man andere LDAP-Filter nimmt, kann man die Suche entsprechend verändern:


. . .
filterStr = "(objectClass=organizationalPerson)"            'alle Postfächer suchen
filterStr = "(objectClass=groupOfNames)"                    'alle Verteilerlisten suchen
filterStr = "(objectClass=Remote-Address)"                  'alle benutzerdefinierten Empfänger suchen
filterStr = "(Home-MTA=cn=Microsoft MTA,cn=TRANGO,cn=Servers,cn=Configuration,ou=Site1,o=MAIL)"
                                                            'alle MBXs auf Server "TRANGO" in "Site1" suchen
filterStr = "(Hide-From-Address-Book=TRUE)"                 'alle versteckten Empfänger suchen,
                                                            'erfordert Anmeldung als "cn=USER,dc=DOM,cn=admin"
filterStr = "(cn=F*)"                                       'alle Empfänger, deren Anzeigename mit F beginnt
. . .

Viele weitere Beispiele für LDAP-Filter finden sie im SelfADSI-Tutorial - Beispiele für LDAP-Filter in Exchange 5.5-Umgebungen. Wichtige weiterführende Informationen des SelfADSI-Tutorials:

Abschnitt "LDAP Filter".
Abschnitt "Verbindung mit dem Verzeichnis und Objekten herstellen".
Abschnitt "Attribute von Exchange 5.5 Mailboxen"