Tales from the Script

Mai 2005

Veröffentlicht: 19. Mai 2005
SG090301

Von The Scripting Guys

Eine Liste aller bisher erschienenen "Tales from the Script"-Kolumnen finden Sie hier.

*

Hey, wo ist mein Drucker? Die Suche in Active Directory mit Skripten (Teil 2)

Selbst wenn wir sie nicht finden, lernen wir mehr durch die Suche nach einer Antwort als durch die Antwort selbst.-- Lloyd Alexander

Lloyd Alexander schreibt Kinderbücher - es ist also klar, dass er nicht für den typischen IT-Experten oder Systemadministrator sprechen kann. Und möglicherweise stimmt es auch, dass es in einem Kinderbuch wirklich eine ganz tolle Sache ist, nach einer Antwort zu suchen und sie nicht zu finden. Bei Systemadministratoren ist das jedoch für gewöhnlich etwas anders:

"Haben Sie die Liste der Drucker aus der Finanzabteilung rausgesucht?"

"Nein, leider habe ich sie nicht gefunden. Ich glaube aber, dass ich aus der Suche danach selbst ein paar für mich sehr interessante Erkenntnisse gezogen habe."

Eine der interessanten Erkenntnisse dürfte wohl gewesen sein, dass es in der echten Welt normalerweise besser ist, eine Antwort zu finden, als eine Antwort nicht zu finden. (Wir Scripting-Guys können dies übrigens aus eigener leidvoller Erfahrung bestätigen.)

Glücklicherweise finden sich viele der benötigten Antworten in Active Directory - alles, was wir zu tun brauchen, ist zu fragen. Active Directory wird uns mit Freude weiter helfen. Und genau darum geht es diesen Monat in Tales from the Script (und ja, im Gegensatz zur landläufigen Meinung hat diese Kolumne manchmal durchaus einen Sinn). In Teil 1 dieser zweiteiligen Serie haben wir die grundlegenden Prinzipien des Suchens in Active Directory besprochen. In Teil 2 werden wir uns nun praktisch mit der Suche in Active Directory beschäftigen: Wir zeigen Ihnen, wie Sie Ihre eigenen SQL-Abfragen schreiben, die Ihnen genau die Informationen zurückliefern, die Sie suchen. Nicht mehr und nicht weniger.

Anmerkung: Es gibt noch eine zweite Syntax, die Sie für Active Directory-Suchen nutzen können: die LDAP-Syntax. Wir sind jedoch der Meinung, dass die SQL-Syntax weitaus einfacher zu verstehen ist, und daher konzentrieren wir uns auf diese. Wenn Sie sich für die LDAP-Syntax interessieren, nehmen Sie sich eine Stunde Ihrer wertvollen Zeit und schauen Sie sich den Scripting Guys-Webcast (englischsprachig) zum Thema "Suchen in Active Directory" an. Oder auch nicht. Letztendlich können sie mehr lernen, indem Sie den Webcast nicht anschauen als wenn Sie ihn anschauen … oder wie war das gleich?

Die grundlegenden Teile einer SQL-Abfrage

Einer der Scripting Guys kann sich dunkel an einen Film erinnern, in dem ein junger Mann eine junge Frau trifft und sie um einen Kuss bittet. Sie gibt ihm drauf hin prompt eine Ohrfeige. Der Freund des jungen Mannes geht zur gleichen Frau und bittet ebenfalls um einen Kuss - und bekommt ihn sofort. Warum hatte er Erfolg, während der erste Mann keinen hatte? Nun, es geht nicht darum, was man fragt - sondern darum, wie man fragt.

Mit Active Directory ist es das gleiche. Active Directory wird Ihnen keine runterhauen, wenn Sie falsch fragen (auch wenn dieses Feature für zukünftige Versionen bereits geplant ist), aber wenn Sie die falschen Fragen stellen, können Sie darauf wetten, dass Active Directory die falschen Informationen zurückgibt (wenn Sie überhaupt Informationen zurückerhalten). Was bedeutet das für uns? Es bedeutet, dass das Geheimnis einer erfolgreichen Active Directory-Suche nicht nur darin liegt, die richtigen Fragen zu stellen, sondern auch darin, diese auf die richtige Art zu stellen. Und das bedeutet, dass Sie Ihre SQL-Abfragen sorgfältig zusammenstellen müssen.

In diesem Artikel werden wir die Geheimnisse und die Nuancen von SQL-Abfragen erklären. Hierzu teilen wir eine typische SQL-Abfrage in drei Teile auf. Sie sehen diese drei Teile im folgenden Diagramm:

SQL Query

Wir werden diese drei Teile jetzt einzeln besprechen:

Die zurückzugebenden Attribute

Der Startpunkt für die Suche

Die optionale WHERE-Bedingung

Lassen Sie uns loslegen.

Die zurückzugebenden Attribute

Teil 1 unserer SQL-Abfrage ist ziemlich einfach: Alles, was Sie machen müssen, ist, die Namen der Attribute anzugeben, die Sie zurückerhalten möchten. Sie möchten zum Beispiel die Namen aller Objekte in Active Directory zurückerhalten? Dann benutzen Sie die folgende Abfrage (keine Angst, wir zeigen Ihnen gleich, wie Sie nur bestimmte Daten einer bestimmten Kategorie - beispielsweise Benutzerkonten - zurückerhalten):

"SELECT NAME FROM 'LDAP://dc=fabrikam,dc=com'"

Wie steht es mit den ADsPath für alle Objekte in Active Directory? Kein Problem:

"SELECT ADsPath FROM 'LDAP://dc=fabrikam,dc=com'"

Einfach, hm? Was aber, wenn Sie mehrere Attribute zurückerhalten möchten - wenn Sie zum Beispiel alle Attribute haben möchten? In diesem Fall werden sie wahrscheinlich eine Abfrage wie die folgende nutzen:

"SELECT * FROM 'LDAP://dc=fabrikam,dc=com'"

Klingt irgendwie komisch für Sie? Lesen Sie weiter …

Mehrere Attribute zurückgeben: Die komische Sache mit SELECT * FROM

Wenn Sie über Erfahrung mit SQL-Abfragen verfügen (inklusive dem Schreiben von WMI-Abfragen, bei denen es sich nur um eine Untermenge von SQL handelt), dann kennen Sie sich ohne Zweifel mit SELECT * FROM aus und wissen, dass Sie so alle Datensätze oder alle Eigenschaften eines Objekts auswählen können. Im Fall von WMI wählt die folgende Abfrage zum Beispiel alle Eigenschaften aller Instanzen der Klasse Win32_Process aus:

"SELECT * FROM Win32_Process"

Wenn Sie sich mit SELECT * FROM auskennen, dann werden Sie früher oder später Active Directory-Skripte wie das folgende schreiben (wahrscheinlich glauben Sie nämlich, dass Sie einen schnellen und bequemen Weg gefunden haben, alle Eigenschaften eines Objekts oder eine Gruppe von Objekten abzufragen):

Const ADS_SCOPE_SUBTREE = 2

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand =   CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection

objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 

objCommand.CommandText = _
    "SELECT * FROM 'LDAP://dc=fabrikam,dc=com' WHERE " _
        & "objectCategory='user'"  
Set objRecordSet = objCommand.Execute

objRecordSet.MoveFirst
Do Until objRecordSet.EOF
    Wscript.Echo objRecordSet.Fields("Name").Value
    objRecordSet.MoveNext
Loop

Wir würden erwarten, dass dieses Skript alle Attribute aller Benutzerkonten der Domäne zurückgibt. Außerdem würden wir erwarten, dass das Skript brav jeweils den Wert des Attributs Name jedes Benutzerkontos ausgibt. Hier gibt es jedoch ein Problem: Statt die Namen aller Benutzer zurückzubekommen, erhalten Sie die folgende Fehlermeldung:

ADODB.Recordset: Item cannot be found in the collection corresponding to the requested name or ordinal.

Huh? Wir haben doch nur versucht, den Namen abzufragen. Soll das etwa heißen, dass Name kein gültiges Active Directory-Attribut ist?

Kein Panik: Name ist eindeutig gültig. Das Problem ist, dass unsere Abfrage gar nicht das Attribut Name zurückgibt. Und darum kann dieses Attribut natürlich auch nicht im Recordset gefunden werden. Aber warum wird das Attribut nicht zurückgegeben? Wir haben doch SELECT * FROM verwendet.

Genau das ist das Problem. Mit ADSI arbeitet SELECT * FROM nicht so, wie Sie es erwarten. Mit WMI gibt SELECT * FROM alle Eigenschaften eines Objekts zurück. Wenn Sie allerdings Active Directory abfragen, dann gibt SELECT * FROM nur das Attribut AdsPath zurück. Das war's. Ein Skript, das das Attribut ADsPath für alle Benutzerkonten zurückgibt, funktioniert dann auch wie erwartet:

Const ADS_SCOPE_SUBTREE = 2

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand =   CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection

objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 

objCommand.CommandText = _
    "SELECT * FROM 'LDAP://dc=fabrikam,dc=com' WHERE " _
        & "objectCategory='user'"  
Set objRecordSet = objCommand.Execute

objRecordSet.MoveFirst
Do Until objRecordSet.EOF
    Wscript.Echo objRecordSet.Fields("ADsPath").Value
    objRecordSet.MoveNext
Loop

Ersetzten Sie ADsPath im Echo-Statement durch irgendein anderes Attribut, und das Skript wird wieder fehlschlagen. Garantiert.

Wir können nicht mit Sicherheit sagen, warum SELECT * so arbeitet. Aber vielleicht hat es auch etwas damit zu tun, dass Sie wahrscheinlich gar nicht alle Eigenschaften aller Objekte abfragen wollen. Wenn Sie Microsoft Exchange installiert haben (und wer hat das nicht?), hat jedes Benutzerkonto ca. 300 Attribute. Bei 300 Attributen pro Benutzer, und sagen wir 10.000 Benutzern, erreichen Sie sonst genau zwei Dinge: Sie belasten Ihren Domänencontroller (schließlich braucht es Systemressourcen, um diese Informationsmenge zusammenzustellen), und Sie schicken Tonnen von Daten durch Ihr Netzwerk. Und das alles, damit Sie die Namen aller Benutzer ausgeben können? Das SELECT * FROM nicht wie erwartet arbeitet, ist tatsächlich sogar gut. Es hält Sie davon ab, sich drastische Netzwerkprobleme einzufangen. Und das ist eine begrüßenswerte Sache. Fakt ist jedoch: Auch wenn wir nichts gegen AdsPath haben - es ist wohl kaum das einzige Attribute, das wir jemals in einer Suche abfragen wollen. Wir möchten mindestens den Vornamen, den Nachnamen und die Telefonnummer der Benutzer haben. Wir haben ja bereits gesehen, dass wir einzelne Attribute abfragen können. Wenn wir mehrere Attribute abfragen möchten und SELECT * FROM nicht funktioniert, heißt das dann, dass wir eine Abfrage für jedes Attribut brauchen?

Entspannen Sie sich - so mühsam wird es dann doch nicht. Wenn Sie mehr als ein Attribut zurückgeben möchten, dann müssen Sie einfach alle gewünschten Attribute mit in das SELECT-Statement aufnehmen. Und zwar getrennt durch Kommas. Die folgende Abfrage gibt zum Beispiel drei Attribute zurück: givenName, SN und telephoneNumber - und zwar für alle Benutzer der Domäne fabrikam.com.

"SELECT givenName, SN, telephoneNumber " _
    & "FROM 'LDAP://dc=fabrikam,dc=com' WHERE objectCategory='user'"

Anmerkung: Moment mal, meine lieben Scripting Guys! Wir wollten doch den Vornamen und den Nachnamen. Und jetzt kommt Ihr mit Attributen wie givenName und SN. (SN?) Woher das?

Also: Active Directory verwendet nicht immer ganz klare Namen. Wenn wir zum Beispiel nach dem Attribute "LastName" suchen (Nachname), verwendet Active Directory für dieses Attribut die Bezeichnung "SN" (Surname). Das gleiche gilt für den Vornamen. "FirstName" wäre logisch. Active Directory verwendet jedoch die Bezeichnung "givenName". Woher wir das wissen? Tja, ein Weg, um so was herauszubekommen, ist, sich das Active Directory Schema (englischsprachig) anzuschauen. Wenn Sie diesen Weg gehen, dann achten Sie darauf, den LDAP-Display-Namen für die Attribute zu verwenden (der ldapDisplayName einer Klasse ist der Name, den wir in unseren Skripten verwenden). Ein Beispiel:

ldapDisplayName

Ok, wo waren wir stehen geblieben? Ach ja: Mehrere Attribute in einer Abfrage zurückgeben. Sie möchten zusätzlich zum Vornamen, Nachnamen und zur Telefonnummer den Titel und die Abteilung des Benutzers? Fügen Sie die Attribute einfach dem SELECT-Statement hinzu (egal an welcher Stelle - die Reihenfolge spielt keine Rolle):

"SELECT givenName, SN, telephoneNumber, title, Department " _
    & "FROM 'LDAP://dc=fabrikam,dc=com' WHERE objectCategory='user'"

Sehen Sie, wie einfach das geht? Wenn Sie den Wert eines Attributes benötigen, müssen Sie einfach nur sicherstellen, dass Sie es mit in Ihre SELECT-Abfrage aufnehmen.

O.K., guter Einwand: Das ist einfach, solange Sie nur ein paar Attribute brauchen. Aber was, wenn Sie 47 Attribute benötigen? Oder 93? Oder (möge es nie so weit kommen) wenn Sie tatsächlich alle Attribute aller Benutzer brauchen? Müssen Sie tatsächlich 300 Attributnamen in ein gigantisches SELECT-Statement eintragen, das von hier bis Katmandu reicht?

Nein. Ein viel besserer Weg ist es, nur den ADsPath für den jeweiligen Benutzer abzufragen. Wenn Sie den ADsPath haben, können Sie eine Bindung an das jeweilige Benutzerkonto erstellen und so viele Attribute ausgeben, wie Sie möchten. Theoretisch mag dieser Weg ein klein wenig langsamer sein, als alle Werte gleichzeitig zurückzuerhalten, aber in Wirklichkeit wird er Ihnen schneller vorkommen - er ist einfach viel weniger ressourcenintensiv. Als nächstes sehen Sie ein Skript, das nur den ADsPath für alle Benutzer in fabrikam.com zurückgibt. Dann verwendet das Skript den Wert von ADsPath, um eine Bindung an die einzelnen Benutzerkonten einzurichten und einige Eigenschaften zurückzugeben. Keine dieser Eigenschaften wurde vorher in der Abfrage angegeben.

Const ADS_SCOPE_SUBTREE = 2

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand =   CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection

objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 

objCommand.CommandText = _
    "SELECT ADsPath FROM 'LDAP://dc=fabrikam,dc=com' WHERE " _
        & "objectCategory='user'"  
Set objRecordSet = objCommand.Execute

objRecordSet.MoveFirst
Do Until objRecordSet.EOF
    strPath = objRecordSet.Fields("ADsPath").Value
    Set objUser = GetObject(strPath)
    Wscript.Echo objUser.givenName
    Wscript.Echo objUser.initials
    Wscript.Echo objUser.SN
    Wscript.Echo objUser.organization
    Wscript.Echo objUser.department
    Wscript.Echo objUser.title
    Wscript.Echo objUser.telephoneNumber
    objRecordSet.MoveNext
Loop

Wenn wir die Abfrage ausführen, erhalten wir ein Recordset zurück, in dem sich die AdsPath-Attribute der Benutzer in fabrikam.com befinden. Wir durchlaufen das Recordset und nehmen den ADsPath für den ersten Benutzer. Diesen speichern wir in einer Variablen mit dem Namen strPath. Dann verwenden wir den Wert in dieser Variablen, um eine Bindung an das entsprechende Benutzerkonto einzurichten. Das passiert in diesen zwei Codezeilen:

strPath = objRecordSet.Fields("ADsPath").Value
Set objUser = GetObject(strPath)

Wenn die Bindung eingerichtet ist, können wir den Wert jedes beliebigen Attributs dieses Kontos ausgeben. Dann beginnen wir die Schleife von vorn und machen das gleiche für den nächsten Benutzer, dessen ADsPath im Recordset steht.

Zufälligerweise ist dies genau der gleiche Prozess, den Sie verwenden, wenn Sie einen Wert mit Hilfe eines Suchskriptes ändern wollen. Der ADO-Provider für Active Directory ist nur lesbar. Er unterstützt keine UPDATE-Abfragen oder irgendwelche anderen Abfragetypen, die Daten verändern. Das folgende Skript fragt alle Benutzer der Abteilung "Accounting" ab und ändert den Namen der Abteilung in "Finance". Beachten Sie, dass die Abfrage wieder nur das Attribut ADsPath zurückgibt, und dann mit diesem Attribut eine Bindung zu den einzelnen Konten erstellt wird.

Const ADS_SCOPE_SUBTREE = 2

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand =   CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection

objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 

objCommand.CommandText = _
    "SELECT ADsPath FROM 'LDAP://dc=fabrikam,dc=com' WHERE " _
        & "objectCategory='user' AND Department='Accounting'"  
Set objRecordSet = objCommand.Execute

objRecordSet.MoveFirst
Do Until objRecordSet.EOF
    strPath = objRecordSet.Fields("ADsPath").Value
    Set objUser = GetObject(strPath)
    objUser.Department = "Finance"
    objUser.SetInfo
objRecordSet.MoveNext
Loop

Der Startpunkt für die Suche

Einer der wirklichen Vorteile von Active Directory-Suchen ist, dass Sie das gesamte Active Directory durchsuchen können. Sie brauchen eine Liste mit allen Benutzern, allen Computer oder allen Druckern? Sie müssen nur den Domänenstamm als Startpunkt für die Suche angeben.

Diejenigen unter Ihnen, die sich mit WMI auskennen, sind es gewohnt, Abfragen wie die folgende zu schreiben (diese Abfrage wählt eine Eigenschaft (Name) einer WMI-Klasse (Win32_Process) aus):

"SELECT Name FROM Win32_Process"

Beim Durchsuchen von Active Directory verwenden Sie eine gleiche Syntax: Sie ersetzen einfach die WMI-Eigenschaft durch ein ADSI-Attribut und die WMI-Klasse durch den ADsPath des Punktes, an dem die Suche starten soll. Die folgende Abfrage gibt zum Beispiel das Attribut Name für alle Objekte in fabrikam.com zurück (wir gehen davon aus, dass der Suchbereich den Standardwert ADS_SCOPE_SUBTREE hat):

"SELECT Name FROM 'LDAP://dc=fabrikam,dc=com'"

Woher wissen wir, dass diese Abfrage die gesamte Domäne durchsucht? Ganz einfach: Wir haben den ADsPath des Domänenstamms als Startpunkt angegeben:

'LDAP://dc=fabrikam,dc=com'

Da wir den standardmäßigen Suchbereich verwenden, durchsucht unser Skript nicht nur den Domänenstamm, sondern auch alle OUs oder Container, die sich im Domänenstamm befinden - und natürlich alle Untercontainer.

Wo wir gerade davon sprechen: Beachten Sie die Hochkommas links und rechts vom ADsPath. Wenn Sie die vergessen, schlägt Ihr Skript fehl.

Anmerkung: Was, wenn Sie alle Ihre Domänen durchsuchen wollen? Tja, wenn wir davon ausgehen, dass die von Ihnen gesuchten Attribute an den globalen Katalog repliziert werden, können Sie sich einfach mit dem GC:-Provider (Global Catalog) statt mit dem LDAP:-Provider verbinden:

'GC://dc=fabrikam,dc=com'

Eine gesamte Domäne zu durchsuchen, mag zwar sehr nützlich sein, aber es wird Momente geben, in denen Sie etwas zielgerichteter suchen möchten. Vielleicht möchten Sie die zurückgegebenen Daten zum Beispiel auf die Benutzer der OU "Finance" (und die Benutzer der untergeordneten OUs von "Finance") einschränken. In diesem Fall müssen Sie nur einen anderen Startpunkt für Ihre Suche wählen. Statt als Startpunkt den Domänenstamm festzulegen, beginnen Sie in der OU, die Sie interessiert. Das folgende SQL-Statement gibt zum Beispiel den ADsPath für alle Benutzer der OU "Finance" zurück (und für die Benutzer aller Unter-OUs von "Finance"). Wie Sie sehen, haben wir statt des Domänestamms den ADsPath der OU "Finance" verwendet:

"SELECT ADsPath FROM 'LDAP://ou=Finance,dc=fabrikam,dc=com' WHERE " _
    & "objectCategory='user'"

Nehmen wir einmal an, die OU "Finance" hätte keine Unter-OUs. In diesem Fall bräuchten Sie kein Suchskript. Sie können einfach eine Bindung an die OU einrichten und alle Benutzer ausgeben. Wenn die OU jedoch Unter-OUs hat, dann wird die Suchmöglichkeit zu einem Geschenk Gottes: Andernfalls müssten Sie eine rekursive Funktion schreiben, um alle Unter-OUs abzufragen.

Wie sieht es aber aus, wenn Sie tatsächlich nur eine OU durchsuchen und alle Unter-OUs ignorieren möchten? Stellen wir uns zum Beispiel vor, Sie benötigen eine Liste aller Benutzer der OU "Finance", die nicht Mitglieder der Abteilung "Finance" sind. Die ist möglicherweise mit einer Suche viel schneller zu erledigen, als eine Bindung an alle Benutzerkonten einzurichten - uns zwar obwohl es sich nur um eine OU handelt.

Wir verwenden in diesem Fall weiterhin LDAP://ou=Finance,dc=fabrikam,dc=com. Zusätzlich setzen wir aber den Suchbereich auf ADS_SCOPE_BASE. Auf diese Weise wird unser Script nur den Zielcontainer durchsuchen. Das vollständige Skript sieht so aus:

Const ADS_SCOPE_BASE = 0

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand =   CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection

objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_BASE 

objCommand.CommandText = _
    "SELECT Name FROM 'LDAP://ou=Finance,dc=fabrikam,dc=com' WHERE " _
        & "objectCategory='user'"  
Set objRecordSet = objCommand.Execute

objRecordSet.MoveFirst
Do Until objRecordSet.EOF
    Wscript.Echo objRecordSet.Fields("Name").Value
    objRecordSet.MoveNext
Loop

Scripting Guys-Tipp: Wenn Sie eine zielgerichtete Suche durchführen, dann bedenken Sie, dass die Container "Users" und "Computers" keine OUs sind. Um diese beiden Container als Startpunkt zu verwenden, müssen Sie eine Abfrage wie die folgende verwenden (sie nutzt cn statt ou im ADsPath):

"SELECT Name FROM 'LDAP://cn=Users,dc=fabrikam,dc=com' WHERE " _& "objectCategory='user'"

Die optionale WHERE-Bedingung

Sie haben Recht: Zu sagen, die WHERE-Bedingung sei optional, ist in etwa so, wie zu sagen, Wasser wäre optional. Auch wenn Sie (zumindest eine Weile) ohne Wasser überleben können - es dauert nicht lange, bis das "optional" recht dringend wird. Genauso ist es mit der WHERE-Bedingung im Fall von Active Directory. Nehmen wir zum Beispiel das folgende SQL-Statement, das den Namen aller Objekte in Active Directory zurückgibt:

"SELECT ADsPath FROM 'LDAP://dc=fabrikam,dc=com'"

Sehen Sie? Keine WHERE-Bedingung. Und die Abfrage funktioniert wunderbar. Optional.

Natürlich wird es nicht lange dauern, und Sie haben die Nase voll davon, alle Objekte in Active Directory abzufragen. Besonders dann, wenn Sie nach einer Liste aller Benutzer aus der Abteilung "Finance" oder nach allen Farbdruckern suchen. Sie werden ziemlich schnell feststellen, dass eine zielgerichtetere Suche effizienter ist. Und genau das ist der Moment, in dem die WHERE-Bedingung ins Spiel kommt.

Die Suche nach Objektklassen und -kategorien

Da wir Objektklassen und -kategorien schon in Teil 1 dieser Serie besprochen haben, werden wir uns mit den Konzepten nicht lang aufhalten. Die Verwendung von Klassen oder Kategorien in Ihren Suchskripten wird es Ihnen ermöglichen, zum Beispiel nur die Drucker aus der Abteilung "Finance" und nicht auch die Drucker aller anderen Abteilungen abzufragen. (Tipp: Wenn Sie keine Ahnung haben, wovon wir hier eigentlich reden, sollten Sie Teil 1 lesen.)

Also, wie geben Sie alle Drucker einer Domäne zurück? Sie verwenden eine Abfrage wie die folgende:

"SELECT Name FROM 'LDAP://cn=Users,dc=fabrikam,dc=com' WHERE " _
        & "objectCategory='printQueue'"

Das einzige, was wir hier machen, ist die Angabe von objectCategory. Dabei handelt es sich um die Objektart, die zurückgegeben werden soll. Die Bezeichnungen einiger häufig verwendeten Objektkategorien lauten:

computer

contact

group

inetOrgPerson

organizationalUnit

printQueue (in Active Directory veröffentlichte Drucker)

site

siteLink

subnet

user

volume (in Active Directory veröffentlichte Freigaben)

Die Verwendung von AND und OR in Abfragen

Um ehrlich zu sein, es gibt keinen Grund, aus dem Sie Active Directory durchsuchen müssen. Wenn Sie zum Beispiel nach einem Benutzer mit dem sAMAccountName "kenmyer" suchen, müssen Sie nicht zwingend Active Directory durchsuchen. Stattdessen könnten Sie ein rekursives Skript schreiben, das alle Namen aus allen Containern in Active Directory abruft und prüft, ob das Attribut sAMAccountName den Wert "kenmyer" hat. Das wäre extrem langsam und extrem mühsam, aber es würde funktionieren.

Aber wer will schon etwas extrem Langsames und Mühsames? Wir wollen etwas Schnelles und Effizientes. Und ein hervorragender Weg, so etwas zu erreichen, ist, sehr spezifische Active Directory-Abfragen zu schreiben. Sie möchten eine Liste mit allen Benutzern, bei denen das Attribut sAMAccountName den Wert "kenmyer" hat? Verwenden Sie diesen Code:

"SELECT Name FROM 'LDAP://cn=Users,dc=fabrikam,dc=com' WHERE " _
    & "objectCategory='user' AND sAMAccountName = 'kenmyer'"

Sehen wir uns an, was wir hier machen. Wir haben zwei unterschiedliche Kriterien in unserer WHERE-Bedingung: Wir wollen eine Liste aller Objekte bei denen objectCategory "user" ist und bei denen sAMAccountName "kenmyer" ist. Für die zurückgegebenen Daten müssen beide Bedingungen zutreffen. Und wir verwenden hierzu ein AND in unserer WHERE-Bedingung. Wie würde die Bedingung aussehen, wenn wir alle Benutzer der Abteilung "Finance" mit dem Titel "Accountant" haben möchten? Die Abfrage würde so aussehen:

"SELECT Name FROM 'LDAP://cn=Users,dc=fabrikam,dc=com' WHERE " _
    & "objectCategory='user' AND department = 'Finance' AND title = 'Accountant'"

Erkennen Sie das Schema? Wir könnten weitere Bedingungen einfügen. Jede neue Bedingung würde einfach mit einem AND angehängt.

Natürlich mag es sein, dass wir manchmal etwas "pauschaler" sein wollen. Nehmen wir beispielsweise an, wir möchten eine Liste haben mit allen Benutzern, die zur Abteilung "Accounts Payable" oder zur Abteilung "Accounts Receivable" gehören. In diesem Fall können wir kein AND verwenden - dann müssten ja beide Bedingungen gleichzeitig zutreffen. Stattdessen wollen wir aber, dass es reicht, wenn nur eine der Bedingungen zutrifft. Daher verwenden wir einfach OR:

"SELECT Name FROM 'LDAP://cn=Users,dc=fabrikam,dc=com' WHERE " _
    & "department='Accounts Payable' OR department='Accounts Receivable'"

Mit dieser Abfrage erhalten wir alle Objekte, die entweder zu "Accounts Payable" oder zu "Accounts Receivable" gehören.

Warum "alle Objekte" und nicht "alle Benutzer"? Tja, um das erste Beispiel zum OR so einfach wie möglich zu halten, haben wir den Teil objectCategory=’user’ einfach weggelassen. Wir fügen ihn aber jetzt wieder hinzu. Sehen Sie sich den Aufbau der WHERE-Bedingung genau an. Wir verwenden jetzt AND und OR:

"SELECT Name FROM 'LDAP://cn=Users,dc=fabrikam,dc=com' WHERE " _
    & "objectCategory='user' AND " _
        & "(department='Accounts Payable' OR department='Accounts Receivable')"

Sie haben hoffentlich bemerkt, dass wir die beiden Bedingungen durch Klammern getrennt haben: Wir möchten nur die Objekte, bei denen es sich um Benutzer handelt und die entweder Mitglieder von "Accounts Payable" oder "Accounts Receivable" sind. Wenn Sie AND und OR in einer WHERE-Bedingung verwenden, dann verhindern Klammern, dass Sie unerwartete Ergebnisse erhalten. Wenn wir die Klammern aus der letzten Abfrage entfernen, ist diese zum Beispiel nur schwer zu entziffern:

"SELECT Name FROM 'LDAP://cn=Users,dc=fabrikam,dc=com' WHERE " _
    & "objectCategory='user' AND " _
        & "department='Accounts Payable' OR department='Accounts Receivable'"

Diese Abfrage könnte auch so gelesen werden: Alle Benutzer, die zu "Accounts Payable" gehören, oder alles (nicht nur Benutzer), was zu "Accounts Receivable" gehört. Und das ist wohl nicht das, was wir wollen. Der beste Weg, um solche Fehler auszuschließen, sind eben Klammern.

Hier ein weiteres Beispiel: Es gibt Benutzer oder Computer zurück, die zur Abteilung "Accounts Payable" oder "Accounts Receivable" gehören. Achten Sie wieder auf die Klammern.

"SELECT Name FROM 'LDAP://cn=Users,dc=fabrikam,dc=com' WHERE " _
    & "(objectCategory='user' OR objectCategory='computer') AND " _
        & "(department='Accounts Payable' OR department='Accounts Receivable')"

Angeben von Werten in einer WHERE-Bedingung

Jedes Mal, wenn Sie eine WHERE-Bedingung erstellen, müssen Sie mindestens zwei Dinge wissen: den Namen des Attributes (einleuchtend) und den Datentyp des Attributs. Active Directory verwendet unterschiedliche Datentypen. Einige enthalten Zeichenketten, andere Zahlen oder Boolean-Werte. Sie müssen wissen, welcher Datentyp von einem Attribut verwendet wird - denn dieser steuert die Syntax Ihrer WHERE-Bedingung.

Wie wir das meinen? Sehen Sie sich diese einfache WHERE-Bedingung an:

WHERE objectCategory='user' AND department='Finance'

Beachten Sie, dass die Werte für objectCategory (‘user’) und department (‘Finance’) von Hochkommas umgeben sind. Das liegt daran, dass objectCategory und department Zeichenketten enthalten. Wenn Sie mit solchen Attributen arbeiten, müssen die entsprechenden Werte ganz einfach in Hochkommas eingeschlossen werden. Sie möchten eine Liste mit allen Benutzern mit dem Nachnamen (SN) "Smith"? Dann sieht Ihre WHERE-Bedingung so aus:

WHERE objectCategory='user' AND SN='Smith'

Wenn Sie mit Attributen arbeiten, die numerische Werte oder Boolean-Werte enthalten, sieht das anders aus. Wenn Sie zum Beispiel nach allen globalen Gruppen einer Domäne suchen, müssen Sie nach Gruppen mit dem Wert 2 im Attribut groupType suchen. Die WHERE-Bedingung würde so aussehen:

WHERE objectCategory='group' AND groupType=2

Gesehen? Keine Hochkommas beim Wert 2. Das gleiche gilt für die Boolean-Werte. Eine Liste mit allen Farbdruckern? In diesem Fall muss das Attribut printColor den Wert TRUE haben:

WHERE objectCategory='printQueue' AND printColor=TRUE

Auch hier wieder keine Hochkommas. Hochkommas werden nur bei Zeichenketten verwendet.

Geständnis: O.K., wir waren etwas unehrlich: Wenn Sie mit Datumswerten oder Bitmasken arbeiten, kann das ganze etwas komplizierter werden. Da es sich aber hier um einen Einführungsartikel handelt, tun wir einfach so, als würden diese anderen Attributtypen gar nicht existieren. Wir werden uns in einem zukünftigen Artikel damit beschäftigen.

Variablen in WHERE-Bedingungen verwenden

Wir haben schon befürchtet, dass jemand danach fragen wird. In allen Beispielen haben wir bis jetzt alle Werte fest eingetragen - zum Beispiel so wie den Abteilungsnamen im folgenden Skript:

WHERE objectCategory='user' AND department='Finance'

Zum Lernen recht das sicher aus. Aber wie wir alle wissen, hat Lernen nur selten etwas mit dem echten Leben zu tun. Wir haben bis jetzt Skripte erstellt, die nur nach einer Sache suchen. Im echten Leben brauchen Sie aber wahrscheinlich Skripte, die nach allen möglichen Abteilungen suchen. Und möglicherweise wollen Sie den Abteilungsnamen als Kommandozeilenargument angeben. Wir möchten jetzt hier nicht über Kommandozeilenargumente sprechen (wenn Sie mehr darüber wissen möchten, legen wir Ihnen unseren Artikel dazu ans Herz: Gute Argumente beim Scripting). Lassen Sie uns einfach annehmen, dass Sie es (auf welche Art auch immer) geschafft haben, den Namen der gesuchten Abteilung in einer Variable zu speichern. Nennen wir diese Variable strDepartment. Wie verwenden Sie diese Variable also jetzt in Ihrer WHERE-Bedingung? So wird es nicht gehen:

WHERE objectCategory='user' AND department='strDepartment'

Warum nicht? Na, in diesem Fall wird das Skript nach einer Abteilung mit dem Namen strDepartment suchen (und wahrscheinlich nichts zurückgeben - es sei denn, in Ihrem Unternehmen werden tatsächlich dermaßen eigenartige Abteilungsnamen verwendet). Auch dies wird nicht funktionieren:

WHERE objectCategory='user' AND department=strDepartment

Warum? Denken Sie daran, das Attribut "department" speichert eine Zeichenkette. Sie müssen also den gesuchten Wert in Hochkommas einfassen. In der Abfrage gibt es aber keine Hochkommas. Und genau darum werden Sie diese nette Fehlermeldung erhalten:

Provider: One or more errors occurred during processing of command.

Bemerkenswert hilfreich, oder? Die Meldung sagt uns, dass das Skript keine Ahnung hat, was department=strDepartment bedeuten soll. Und daraufhin gibt es einfach auf.

Wir wollen Sie aber nicht weiter auf die Folter spannen. Die folgende WHERE-Bedingung funktioniert:

WHERE objectCategory='user' AND department='" & strDepartment & "'"

Ob Sie es glauben oder nicht, so ist es richtig. Wir erwarten, dass "department" mit einer Zeichenkette verglichen wird. Und eine Zeichenkette muss mit einem Hochkomma anfangen. Und genau das kommt eben bei der oben gezeigten Bedingung heraus:

WHERE objectCategory='user' AND department='

Hätten wir den Wert fest eingetragen, käme nun der Abteilungsname. Wir verwenden aber eine Variable. Und diese wird nun mit dem & angehängt. Wenn wir einmal annehmen, dass in der Variable der Wert "Finance" steht, dann kommt Folgendes heraus:

WHERE objectCategory='user' AND department='Finance

Jetzt brauchen wir nur noch das abschließende Hochkomma anhängen (& "'") und die Abfrage ist vollständig.

Wenn Sie dieser Logik nicht gleich folgen können, machen Sie sich keine Sorgen. Eine deutlich detailliertere Erklärung dazu finden Sie im Microsoft Windows 2000 - Scripting-Handbuch Dokument unter der Überschrift "Zeichenketten als Variablen".

Bei numerischen Werten oder Boolean-Werten gehen Sie ganz genauso vor. Sie lassen nur einfach die Hochkommas weg:

WHERE objectCategory='printQueue' AND printColor=" & blnColor & "

Operatoren in WHERE-Bedingungen

Bis jetzt haben wir nur SQL-Abfragen verwendet, die den Gleich-Operator in der WHERE-Bedingung verwendet haben. Zum Beispiel:

WHERE objectCategory='user' AND department='Finance'

Sehr nützlich, wenn Sie alle Benutzer aus der Abteilung "Finance" suchen. Aber was, wenn Sie alle Benutzer suchen, die nicht zu dieser Abteilung gehören? Das dürfte Ihnen mit dem Gleich-Operator schwer fallen.

Und genau darum können Sie in WHERE-Bedingungen auch alle folgenden Operatoren verwenden:

OperatorBeschreibung

=

Gleich

<>

Ungleich

>

Größer als

<

Kleiner als

>=

Größer oder gleich

<=

Kleiner oder gleich

Sie wollen also eine Liste mit allen Benutzern, die nicht der Abteilung "Finance" angehören? Die entsprechende WHERE-Bedingung würde so aussehen:

WHERE objectCategory='user' AND department<>'Finance'

Die Verwendung von Wildcards in WHERE-Bedingungen

Wenn Sie jetzt denken "Mensch, wenn die jetzt noch mehr oben drauf packen, dann verliere ich den Verstand", dann sollten Sie nun besser aufhören zu lesen. Aber wir versprechen Ihnen, die Wildcards sind das Letzte, was für heute hinzukommt. Und glücklicherweise wird dieser Abschnitt auch recht kurz. Es gibt nämlich nur ein Wildcards-Zeichen, mit dem Sie sich befassen müssen: der Stern (*). Dieser steht ganz einfach für "alles".

Nehmen wir beispielsweise an, Sie müssen nach einem Benutzer suchen, dessen Nachname … an dessen Nachnamen Sie sich nicht mehr erinnern können. Hieß er Smith? Oder Smythe? Oder Smithson? Auf jeden Fall irgendwie so oder ähnlich. Was jetzt?

In diesem Fall verwenden Sie das Wildcard-Zeichen, um nach allen Benutzern zu suchen, bei denen das Attribut SN mit den Buchstaben "SM" beginnt:

WHERE objectCategory='user' AND SN='Sm*'

Hierbei ist die Position des Wildcard-Zeichens wichtig. Wenn wir die zurückgegebenen Daten zum Beispiel auf alle Namen mit "Smith" am Anfang beschränken wollen, müsste die Abfrage so aussehen:

WHERE objectCategory='user' AND SN='Smith*'

Außerdem können Sie das Wildcard-Zeichen natürlich auch am Anfang der Zeichenkette verwenden. Die folgende Abfrage gibt alle Namen zurück, die auf "SON" enden (Smithson, Johnson, Thompson, usw.):

WHERE objectCategory='user' AND SN='*son'

Und natürlich können Sie das Zeichen auch irgendwo in der Mitte einfügen. Sie sollten aber wissen, dass dies zu ernsthaften Leistungseinbrüchen auf dem Domänencontroller führen kann - wir empfehlen es also nicht.

Auch wenn Sie feststellen möchten, ob etwas überhaupt vorhanden ist, ist das Wildecard-Zeichen sehr nützlich. Die folgende Abfrage sucht zum Beispiel nach allen Benutzern, bei denen eine Telefonnummer hinterlegt ist - egal, wie diese Telefonnummer auch immer lauten mag:

WHERE objectCategory='user' AND telephoneNumber='*'

Solche Abfragen können sehr nützlich sein. Stellen Sie sich vor, Sie möchten feststellen, bei welchen Benutzern noch kein Titel eingetragen ist:

WHERE objectCategory='user' AND title<>'*'

Beachten Sie, dass wir diesmal den Operator <> (ungleich) verwendet haben.

Na, war doch gar nicht so schlimm, oder?

Jetzt wissen wir alles, was es zu Active Directory-Suchen zu wissen gibt. Stimmt’s?

Na ja, nicht wirklich. Sie haben jetzt genügend Informationen, um die meisten Abfragen durchführen zu können. Das bedeutet jedoch nicht, dass wir über wirklich alles gesprochen haben. Wir haben nichts über Bitmasken oder Multivalue-Attribute gesagt - um nur mal zwei Beispiele zu nennen. Wenn Sie sich für mehr interessieren, dann schreiben Sie uns unter scripter@microsoft.com (bitte nur in englischer Sprache). Wenn es genügend Interessenten gibt, schauen wir, was sich machen lässt.


**
In diesem Beitrag
**