Centrum skryptów - Active Directory

Jak wyszukać jednocześnie w usłudze Active Directory kilka jednostek OU?

Udostępnij na: Facebook

Skrypciarze odpowiadają na Wasze pytania

Cześć Skrypciarze!

Witamy w rubryce TechNet, w której Skrypciarze z firmy Microsoft odpowiadają na częste pytania dotyczące używania skryptów w administracji systemu. Jeśli macie jakieś pytania z tej dziedziny, zachęcamy do wysłania e-maila na adres: scripter@microsoft.com. Nie możemy zagwarantować odpowiedzi na każde otrzymane pytanie, ale staramy się jak możemy.

Jak wyszukać jednocześnie w usłudze Active Directory kilka jednostek OU?

Cześć Skrypciarze! Pytanie

Cześć, Skrypciarze! Moja aplikacja HTA przeszukuje konta użytkowników w usłudze Active Directory, po czym wyświetla ich nazwy. W mojej Active Directory mam 10 jednostek OU, problem w tym, że chcę pobrać użytkowników tylko trzech jednostek. Jak to zrobić?

-- DP

Cześć Skrypciarze! Odpowiedź

Cześć, DP! Nie wiem czy masz podobne spostrzeżenia jak ja, ale bardzo ławo poznać można kiedy dany autor jest leniwy, tudzież znudzony. Nie wiem czy zauważyłeś tę jakże przykrą dla oka tendencję zaczynania każdego tekstu od anegdoty, mądrej sentencji lub zdania typu „mamy dla Ciebie dobrą wiadomość i złą wiadomość”… Najbardziej nużące są te, zaczynające się od anegdoty, nie sądzisz? Ciekawe czy pani Lessing też ma takie spostrzeżenia (Skrypciarze gratulują Nobla, tak swoją drogą!).

A teraz, co do Twojego pytania o przeszukiwanie Active Directory, mamy dobrą wiadomość i złą wiadomość. Zacznijmy od złej. Chcesz więc wyszukać w Active Directory konta użytkownika, ale chcesz pobrać jedynie te, które znajdują się w trzech spośród 10 jednostek OU. Niestety, to niemożliwe – a przynajmniej nie bez mnóstwa zachodu. Jest to spowodowane tym, że Active Directory nie ma właściwości określającej jednostkę OU, w której znajduje się konto użytkownika (czy jakikolwiek inny obiekt). Teoretycznie moglibyśmy napisać skomplikowaną kwerendę wykorzystującą symbole wieloznaczne, która być może mogłaby nawet zadziałać, ale nie możemy użyć takiej oto prostej klauzuli Where:

Where OU='Finance' OR OU='Research' OR OU="Shipping'

To po prostu nie zadziała.

Mamy jednak także dobrą wiadomość. Otóż opracowaliśmy sposób obejścia tego problemu – w tym celu musimy tylko wykonać kilka wyszukiwań. (Nie martw się; wyszukiwania Active Directory – szczególnie te obejmujące tylko jedną jednostkę OU – działają bardzo szybko.) Mamy nawet jeszcze lepszą wiadomość – pokażemy także aplikację HTA, wyświetlającą wszystkie jednostki OU w polu listy i umożliwiającą wybór tych, które chcemy przeszukać, a następnie pobierającą informacje o znajdujących się w nich kontach użytkownika.

<SCRIPT Language="VBScript">



    Const ADS_SCOPE_SUBTREE = 2

    Const ADS_SCOPE_ONELEVEL = 1



    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



    Sub Window_onLoad

        objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 

        objCommand.CommandText = _

            "SELECT Name, ADsPath FROM 'LDAP://dc=fabrikam,dc=com' WHERE " & _

                "objectCategory='organizationalUnit' ORDER By Name"  

        Set objRecordSet = objCommand.Execute



        objRecordSet.MoveFirst



        strHTML = "<select size = '20' name='OUList' style='width:300px'>"



        Do Until objRecordSet.EOF  

            Set objOption = Document.createElement("OPTION")

            objOption.Text = objRecordSet.Fields("Name").Value

            objOption.Value = objRecordSet.Fields("ADsPath").Value

            MyOUs.Add(objOption)

            objRecordSet.MoveNext

         Loop



    End Sub



    Sub SearchForUsers

        For i = 0 to (MyOUs.Options.Length - 1)

            If (MyOUs.Options(i).Selected) Then

                strSearchOU = MyOUs.Options(i).Value 

                objCommand.Properties("Searchscope") = ADS_SCOPE_ONELEVEL 



                objCommand.CommandText = _

                    "SELECT Name FROM '" & strSearchOU & "' WHERE objectCategory='user'"  

                Set objRecordSet = objCommand.Execute



                If objRecordSet.RecordCount > 0 Then

                    objRecordSet.MoveFirst

                    Do Until objRecordSet.EOF

                        strNames = strNames & objRecordSet.Fields("Name").Value & "<BR>"

                        objRecordSet.MoveNext

                    Loop

                End If

            End If

        Next

        UserList.InnerHTML = strNames

    End Sub



</SCRIPT>



<body>

    <select size="10" name="MyOUs" style="width:400" multiple></select><p>

    <input type="button" value="Get Users" onClick="SearchForUsers"><p>

    <div id="UserList"></div>

</body>

Dość długaśne, przyznaję się bez bicia, ale bez obaw, zaraz wytłumaczę jak działa ten przerażający skrypt.

Uwaga: Nie będziemy oczywiście tłumaczyć wszystkiego, na przykład podstaw wyszukiwania w usłudze Active Directory; jeżeli macie więc z tym nadal problemy, to radzimy zajrzeć do artykułu z serii Tales From the Script, zatytułowanego Dude, Where’s My Printer (j.ang.) Nie będziemy również tłumaczyć, jak tworzy się dynamiczne pole listy, które wyświetla jednostki OU, ale to nie z lenistwa tudzież znużenia. To zostało już raz napisane w archiwalnym artykule, który omawia szczegółowo cały proces tworzenia dynamicznych pól listy.

Spójrzmy najpierw na listę obiektów HTML-owych naszej aplikacji HTA:

<select size="10" name="MyOUs" style="width:400" multiple></select><p>

<input type="button" value="Get Users" onClick="SearchForUsers"><p>

<div id="UserList"></div>

Jak widać, nasza aplikacja składa się z trzech obiektów:

  • pola listy wielokrotnego wyboru MyOUs. Skąd wiemy, że jest to pole wielokrotnego wyboru? Takie rzeczy się po prostu wie… no dobra, przyznajemy się, w sumie to poznaliśmy po parametrze multiple. Czym jest takie pole listy wielokrotnego wyboru? To po prostu pole listy, w którym można wybierać wielokrotnie (stąd nazwa). Możemy klikać ile nam się żywnie podoba, i uruchomi to podprogram dla każdego wybranego elementu (za chwilę omówimy to bardziej szczegółowo).
  • przycisk Get Users. Jeżeli klikniemy na ten przycisk, uruchamiamy podprogram SearchForUsers, który wyszukuje użytkowników w wybranej jednostce OU. Podstawowym założeniem jest, że najpierw wybieramy żądane jednostki OU z listy, następnie klikamy na przycisk Get Sers i uruchamiamy tym samym podprogram, który pobierze informację o kontach w wybranej jednostce OU, po czym używa… (proszę przejść do następnego punktu)
  • Obszat <DIV> z identyfikatorem UserList. <DIV> to nazwany obszar w HTA, obszar, którego zawartością można manipulować programistycznie. Jak widać, nasz <DIV> nie ma tak właściwie żadnej zawartości, ale wszystko do czasu. Tak, to właśnie tutaj podprogram SearchForUsers zapisuje pobrane nazwy użytkowników.

Teraz czas na omówienie skryptu:

Na pewno zauważyliście, że naszą HTA rozpoczęliśmy tagiem <SCRIPT>, po czym dodaliśmy dwa następujące wiersze kodu; kodu, którego nie umieściliśmy w podprogramie:

Const ADS_SCOPE_SUBTREE = 2

Const ADS_SCOPE_ONELEVEL = 1



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

Dlaczego tych poleceń nie dodaliśmy do podprogramu? Z prostej przyczyny, definiujemy tu bowiem parę stałych: (ADS_SCOPE_SUBTREE oraz ADS_SCOPE_ONELEVEL); tworzymy parę obiektów (ADODB.Connection and ADODB.Command); a następnie konfigurujemy właściwości tych obiektów. Chodzi nam o to, by zarówno stałe, jak i obiekty były globalne, tj. chcemy, żeby były dostępne dla wszystkich podprogramów HTA. Najłatwiej jest to zrobić umieszczając kod w <SCRIPT>, a nie wewnątrz podprogramu, co też zrobiliśmy.

Uwaga: Polecenia tego typu omówione są szerzej w artykułach Tales From the Script (j.ang.).

Mamy teraz dwa podprogramy – Window_OnLoad and SearchForUsers. Pierwsza nazwa jest nieprzypadkowa – każdy podprogram o nazwie Window_OnLoad uruchamia się bowiem automatycznie za każdym razem gdy HTA jest otwierana lub odświeżana. Dzięki temu, że umieściliśmy nasz kod do pobierania nazw OU w podprogramie o nazwie Window_OnLoad, mamy gwarancję, że za każdym razem, gdy uruchamiać będziemy HTA, nasze pole listy będzie zamieszkane przez wszystkie jednostki OU w usłudze Active Directory.

Nie będziemy omawiać szerzej kwestii sposobu pobieranie nazw jednostek OU. Skupmy się raczej na użytej przez nas kwerendzie SQL:

objCommand.CommandText = _

    "SELECT Name, ADsPath FROM 'LDAP://dc=fabrikam,dc=com' WHERE " & _

        "objectCategory='organizationalUnit' ORDER By Name"

Chcemy, aby skrypt zwrócił wartości dwóch atrybutów (Name oraz ADsPath) dla wszystkich obiektów w domenie (fabrikam.com), które mają kategorię objectCategory równą organizationalUnit. (Dodatkowo sortujemy wszystkie obiekty alfabetycznie pod względem nazwy – Name.) Skąd wiemy, że zwrócone zostały wszystkie jednostki OU w domenie? Stąd, że przed uruchomieniem kwerendy ustawiamy zakres Searchscope na ADS_SCOPE_SUBTREE:

objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE

Nasz skrypt przeszukuje nie tylko wybrany kontener (fabrikam.com), oprócz tego przeszukuje również wszystkie kontenery niższego rzędu (np. wszystkie jednostki oraz podjednostki OU). Przeszukiwanie zaczynamy od katalogu głównego usługi Active Directory, dzięki temu skrypt przejdzie przez całą usługę katalogową i zwróci wszystkie jednostki OU.

Gdy dysponujemy już zestawem rekordów, zawierającym wszystkie nazwy jednostek OU, używamy poniższego wiersza kodu w celu dodania każdej jednostki OU do pola listy:

Do Until objRecordSet.EOF  

    Set objOption = Document.createElement("OPTION")

    objOption.Text = objRecordSet.Fields("Name").Value

    objOption.Value = objRecordSet.Fields("ADsPath").Value

    MyOUs.Add(objOption)

    objRecordSet.MoveNext

Loop

Dla każdego rekordu (OU) w zestawie tworzymy wystąpienie obiektu Option. Nadajemy nazwie OU (OU Name) właściwość Text (taka etykieta widnieje polu listy), natomiast obiektowi ADsPath właściwość Value (informację, której chcemy użyć w podprogramie SearchForUsers). Po wykonaniu powyższego wywołujemy metodę Add i dodajemy tę nową opcję do pola listy.

Trochę dużo pracy z tym wszystkim, nie? Wszystko po to, aby uruchomić naszą HTA. Zanim jednak to zrobimy, musimy określić, jakie jednostki OU chcemy przeszukać. Najprościej będzie to zrobić przy użyciu poniższego wiersza kodu:

For i = 0 to (MyOUs.Options.Length - 1)

    If (MyOUs.Options(i).Selected) Then

        strSearchOU = MyOUs.Options(i).Value

Uruchamiamy tu pętlę For Next, która biegnie od 0 do Length w polu listy, minus 1. Dlaczego 0? Ponieważ w kolekcji VBScript pierwszy element ma zawsze numer 0. Dlaczego zatem minus 1? Załóżmy, ze wybraliśmy trzy elementy z pola. Pierwszy ma numer porządkowy 0, następny 1, trzeci 2. Numer ostatniego elementu w kolekcji jest zawsze o 1 mniejszy od całkowitej liczby wszystkich elementów, które znajdują się w tablicy. Tak to już działa i nie ma na to rady.

Dla każdego elementu w polu listy sprawdzamy, czy właściwość Selected ma wartość True; jeśli tak, oznacza to, że dana jednostka OU została zaznaczona i wymaga przeszukania. Aby wykonać to wyszukiwanie, pobieramy wartość właściwości Value (czyli ścieżkę ADsPath do jednostki OU) i przechowujemy ją w zmiennej o nazwie strSearchOU.

Mamy jeszcze dwa zadania do wykonania. Po pierwsze, musimy ustawić wartość SearchScope na ADS_SCOPE_ONELEVEL:

objCommand.Properties("Searchscope") = ADS_SCOPE_ONELEVEL

Dlaczego? Ponieważ ta stała instruuje skrypt, by wyszukiwał tylko w określonej jednostce OU, z pominięciem jej jednostek podrzędnych. Ograniczamy w ten sposób wyszukiwanie do jednej OU.

Po drugie, musimy zdefiniować naszą kwerendę SQL:

objCommand.CommandText = _

    "SELECT Name FROM '" & strSearchOU & "' WHERE objectCategory='user'"

W tej kwerendzie po prostu przeszukujemy wybraną jednostkę OU, pobierając wartość Name każdego obiektu, którego właściwość objectCategroy to user. Jak się może domyślacie, uzyskamy w ten sposób zestaw rekordów zawierające informacje o wszystkich użytkownikach w danej jednostce OU. Następnie dla każdego użytkownika w tym zestawie rekordów dodajemy nazwę użytkownika i tag <BR> (HTML-owski odpowiednik znaku powrotu karetki):

strNames = strNames & objRecordSet.Fields("Name").Value & "<BR>"

Co się stanie, jeśli zaznaczymy więcej niż jedną jednostkę OU w polu listy? Nic wielkiego – po zakończeniu działania na pierwszej OU, pętla przejdzie na drugą.

Po zakończeniu, używamy poniższego wiersza kodu, by zapisać nazwy użytkownika w obszarze <DIV>:

UserList.InnerHTML = strNames

I to powinno wystarczyć. Zła wiadomość jest taka, że nasza aplikacja HTA nie jest szczególnie estetyczna. Trzeba ją będzie zmodyfikować, aby wyświetlała informacje w bardziej spójny sposób. (Można o tym poczytać np. tutaj (j.ang.)) Mamy jeszcze dobrą wiadomość – to wszystko na dziś!

 Do początku strony Do początku strony

Centrum skryptów - Active Directory