Active Directory (cz.1) – omówienie interfejsu, połączenie, dostÄ™p do danych
Active Directory Service Interfaces (ADSI) jest zestawem obiektów tworzących warstwę abstrakcji, mająca za zadanie udostępnić jednolity zestaw interfejsów dostępu do danych i funkcji różnych usług katalogowych. Zastosowanie ADSI ma za zadanie uniezależnić administratorów i programistów od konkretnego środowiska sieciowego w którym znajduje się i pracuje usługa katalogowa. Interfejs ten umożliwia enumerację i zarządzanie zasobami usługi katalogowej niezależnie od środowiska, o ile interfejs ADSI udostępnia odpowiedni provider dla tego środowiska.
ADSI dostarcza prostych metod dostÄ™pu do usÅ‚ug katalogowych poprzez interfejs COM. Aplikacje wykorzystujÄ…ce interfejs COM można pisać w różnych jÄ™zykach – interfejs ADSI może być oprogramowany na przykÅ‚ad w C/C++, Microsoft Visual Basic czy też w skryptach VB Script, Perl. ADSI dostarcza również interfejsu dostÄ™pu OLE DB, dziÄ™ki czemu możliwy jest dostÄ™p do danych katalogu w sposób podobny jak przy oprogramowaniu baz danych.
Interfejs ADSI jest dostępny domyślnie w systemach operacyjnych Windows 2000/XP/2003, w systemach wcześniejszych dostępny jest po zainstalowaniu w systemie klienta usług katalogowych przeznaczonego dla danego systemu. Na poniższym rysunku przedstawiona została ogólna architektura ADSI.

ADSI umożliwia poprzez zainstalowanych w systemie dostawców (providers) współpracę z różnym środowiskami usług katalogowych. Środowiska te w ramach interfejsów ADSI przedstawione są użytkownikowi w postaci różnych przestrzeni nazw (namespace). Przestrzenie te reprezentują różne rodzaje katalogów zawierających dane, i wyróżniane są przy odwołaniu do nich poprzez odpowiedni przedrostek. I tak:
- Winnt://, umożliwia dostęp do danych systemu i katalogu w zakresie dostępnym w systemach z rodziny Windows NT.
- LDAP://, umożliwia dostęp do danych katalogów LDAP (w tym Active Directory)
- NDS://, umożliwia dostęp do danych Novell Directory Services
- NWCOMPAT://, umożliwia dostęp do danych serwerów Novell Netware
- ADs://, specjalny interfejs umożliwiający między innymi enumeracji wszystkich dostawców zainstalowanych w systemie.
KorzystajÄ…c z ADSI w systemach Windows najczęściej posÅ‚ugiwać siÄ™ bÄ™dziemy odwoÅ‚aniami do przestrzeni WINNT://, oraz LDAP:// sÅ‚użącej do podłączenia siÄ™ do danych katalogu domeny/LDAP (w naszym przypadku Active Directory). Należy tutaj zaznaczyć że korzystajÄ…c z dostawcy WINNT:// możliwy jest również dostÄ™p do danych w katalogu Active Directory, lecz zakres danych i interfejsów ograniczony jest do funkcjonalnoÅ›ci domeny Windows NT. W systemie możemy siÄ™ spotkać z dodatkowymi przestrzeniami nazw które zostaÅ‚y dołączone do interfejsu ADSI poprzez mechanizm dołączania kolejnych dostawców. PrzykÅ‚adem takiego rozszerzenia jest chociażby przestrzeÅ„ IIS:// – umożliwiajÄ…ca dostÄ™p do informacji i zarzÄ…dzanie danymi serwera IIS.
Artykuł ten mający być wprowadzeniem do zastosowania interfejsu ADSI w zarządzaniu obiektami znajdującymi się w ramach usługi katalogowej skupi się na dwóch podstawowych zagadnieniach:
- podłączeniu się do zasobów usługi katalogowej Active Directory z zastosowaniem różnego rodzaju interfejsów
- uzyskaniem dostępu do danych poprzez uzyskane połączenie.
Jako że artykuł ten skierowany jest głównie do administratorów systemów operacyjnych, a oni posługują się najczęściej w codziennej pracy skryptom, przedstawione przykłady tworzone będą z zastosowaniem języka VB Script. Do wykonania ich niezbędny będzie, więc komputer na którym zainstalowany jest interfejs ADSI oraz Windows Scripting Host w wersji minimum 5.1 (wersja 5.6 zalecana).
Podłączenie siÄ™ do źródÅ‚a danych – ADO DB
Pierwszym zadaniem jakie nas czeka jest podłączenie się na uprawnieniach odpowiedniego użytkownika do źródła danych. W tym wypadku możliwe jest podłączenie się na podstawie uwierzytelnienia użytkownika uruchamiającego skrypt lub podanie alternatywnych danych dostępu. Podłączyć możemy się również do gałęzi katalogu w której znajduje się stacja z której uruchamiany jest skrypt lub tez do wyspecyfikowanej gałęzi lub innej domeny katalogu. Wszystkie te parametry określamy poprzez poprawnie skonfigurowany łańcuch połączenia (connection string) oraz wywołanie odpowiedniej funkcji.
W przypadku gdy korzystamy z interfejsu dostÄ™pu do katalogu poprzez OLE DB rzecz wyglÄ…da trochÄ™ inaczej – parametry połączenia takie jak nazwÄ™ użytkownika oraz opcje takie jak na przykÅ‚ad szyfrowanie hasÅ‚a okreÅ›lamy w parametrach wywoÅ‚ania zapytania, zaÅ› gałąź katalogu do której siÄ™ połączymy – bezpoÅ›rednio w zapytaniu. I tak w celu połączenia siÄ™ do katalogu za pomocÄ… interfejsu ADO DB musimy utworzyć obiekt połączenia “ADODB.Connection”. Tworzymy go poprzez:
Set objConnection = CreateObject("ADODB.Connection")
Dla obiektu połączenia konieczne jest wskazanie odpowiedniego dostawcy umożliwiajÄ…cego dostÄ™p do danych katalogu. Dostawca ten (provider) nazwy siÄ™ “ADsDSOObject” i definiujemy go poprzez polecenie:
Set objConnection.Provider = "ADsDSOObject"
Połączenie takie może zostać wykonane na podstawie uwierzytelnienia użytkownika wywołującego skrypt lub z zastosowaniem alternatywnych poświadczeń definiowanych w parametrach obiektu połączenia. Parametry te to:
- “User ID”: nazwa użytkownika
- “Password”: hasÅ‚o do odpowiedniego konta
- “Encrypt Password”: zmienna logiczna determinujÄ…ca czy hasÅ‚o jest szyfrowane, domyÅ›lnie wartość tego parametru to false
- “ADSI Flag”: flagi determinujÄ…ce opcje połączenia, opis tych flag można znaleźć na stronie MSDN.
Opcje te specyfikowane są poprzez ustawienie właściwości obiektu połączenia w następujący sposób:
objConnection.Properties("User ID") = strUser
objConnection.Properties("Password") = strPasswd
W celu uaktywnienia naszego połączenia musimy dokonać operacji otwarcia połączenia:
objConnection.Open "Active Directory Provider"
W przypadku gdy używamy odpowiednich danych uwierzytelnienia otwarcie połączenia przyjmuje formę:
objConnection.Open "Active Directory Provider", strUser, strPasswd
MajÄ…c przygotowane połączenie zajmiemy siÄ™ teraz stworzeniem obiektu polecenia które zostanie wykonane w naszym skrypcie. Obiekt “ADODB.Command” pozwala na przekazanie i wykonanie poprzez aktywne połączenie polecenia w odniesieniu do katalogu LDAP z zastosowaniem ADSI. Tworzymy wiÄ™c obiekt ADODB.Command:
Set objCommand = CreateObject("ADODB.Command")
następnie zaś wskazujemy dla tego połączenia utworzony wcześniej obiekt połączenia jako ten, który ma być zastosowany w celu jego wykonania
Set objCommand.ActiveConnection = objConnection
Dla obiektu polecenia konieczne jest również ustawienie kilku parametrów, w szczególności treści polecenia które zostanie wykonane. Parametry obiektu polecenia ustawiamy analogicznie jak w przypadku obiektu połączenia. Z kilku dostępnych parametrów w przypadku naszego polecenia zostaną ustalone:
- “Page Size”: opcja przydatna przy zapytaniach które zwracajÄ… dużą liczbÄ™ obiektów (powyżej 1000). Standardowo bez ustawienia tej wartoÅ›ci liczba zwracanych rezultatów jest ograniczona do 1000, jeżeli oczekujemy że zwrócone zostanie wiÄ™cej niż 1000 obiektów konieczne jest posÅ‚użenie siÄ™ mechanizmem stronicowania (paging). Wartość tego parametru okresla wielkość pojedynczej “strony” i uruchamia mechanizm stronicowania wyników danych.
- “Timeout”: podany w sekundach czas oczekiwania na odpowiedź serwera do którego zostaÅ‚o skierowane zapytanie
- “Searchscope”: zakres wyszukiwania, możliwe wartoÅ›ci to 0,1,2 (dla wygody definiowane jako staÅ‚e). OkreÅ›lajÄ… zakres jakiego dotyczyć bÄ™dzie zapytanie czyli czy dotyczy ono tylko obiektu zdefiniowanego w zapytaniu (na przykÅ‚ad jednego OU, obiektu), jednego poziomu czy tez caÅ‚ego poddrzewa poczynajÄ…c od wyspecyfikowanego obiektu (czyli na przykÅ‚ad caÅ‚ej gałęzi katalogu poczynajÄ…c od startowej domeny czy też OU). W naszym wypadku chcÄ…c przeszukać całą gałąź domeny użyjemy wartoÅ›ci 2 – zdefiniowanej jako staÅ‚a o nazwie ADS_SCOPE_SUBTREE
- “Cache Results”: wartość logiczna wskazujÄ…ca czy wyniki zapytania majÄ… być zapamiÄ™tywane w “cache” po stronie klienta. DomyÅ›lnÄ… wartoÅ›ciÄ… jest “true” jednak przy wyszukiwaniu wiÄ™kszej liczby obiektów warto tÄ… opcje wyłączyć ustawiajÄ…c jej wartość na “false”.
objCommand.Properties("Page Size") = 1000
objCommand.Properties("Timeout") = 30
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommand.Properties("Cache Results") = False
Opis wszystkich parametrów obiektu połączenia można znaleźć na stronach MSDN.
Po ustawieniu wybranych parametrów połączenia należ skonstruować podstawową rzecz czyli zapytanie które skierujemy do katalogu. Zapytanie do katalogu w przypadku interfejsu ADO DB może być zdefiniowane w jednym z dwóch dialektów:
Umieszczamy je w parametrze “CommandText” obiektu polecenia jako Å‚aÅ„cuch znaków. W treÅ›ci tej specyfikujemy oprócz warunków zapytania również Å›cieżkÄ™ w katalogu zawierajÄ…cÄ… nazwÄ™ domeny itp. Jako że skrypty tworzymy przeważnie aby dziaÅ‚aÅ‚y w różnych Å›rodowiskach warto zadbać o to aby przy przeniesieniu naszego skryptu do innej domeny nie zachodziÅ‚a konieczność jego modyfikacji. W tym celu posÅ‚użymy siÄ™ wÅ‚aÅ›ciwoÅ›ciami zdefiniowanego w LDAP obiektu o nazwie rootDSE, który zawiera informacje o serwerze katalogu, miÄ™dzy innymi takie jak nazwa domeny katalogu zdefiniowana we wÅ‚aÅ›ciwoÅ›ci “defaultNamingContext” tego obiektu. Aby uzyskać do niej dostÄ™p pobierzemy obiekt rootDSE korzystajÄ…c z przestrzeni nazw LDAP:// interfejsu ADSI i odczytamy tÄ… wartość tej wÅ‚aÅ›ciwoÅ›ci:
Set rootDSE = GetObject("LDAP://rootDSE")
strDomain = rootDSE.Get("defaultNamingContext")
Powyższy fragment kodu zakłada że skrypt uruchamiany jest na uprawnieniach użytkownika posiadającego dostęp do serwera katalogu i nie wymaga dodatkowego uwierzytelnienia (opis połączenia z katalogiem w przypadku alternatywnego uwierzytelnia poprzez LDAP:// przedstawiony zostanie w dalszej części artykułu). Pełny opis obiektu rootDSE można znaleźć na stronie MSDN.
Dysponując nazwą domeny możemy przystąpić do konstruowania treści zapytania, jako pierwszy przykład posłużymy się zapytaniem pobierającym listę użytkowników z domeny, z nazwami kont logowania, nazwami DN oraz datą utworzenia konta. Zapytanie to w dialekcie SQL prezentuje się następująco:
strSQLStmt = "SELECT whenCreated, distinguishedName, Name, samAccountName FROM 'LDAP://" & strDomain & "' WHERE objectCategory='Person'"
To samo zapytanie w dialekcie LDAP przedstawia się następująco:
strSQLStmt= "
Pozostaje nam jedynie przypisać treść zapytania do obiekt polecenia i wykonać to polecenie:
objCommand.CommandText = strSQLStmt
Set objResultRecordSet = objCommand.Execute
jako wynik polecenia otrzymujemy obiekt typu RecordSet który zawiera dane uzyskane z katalogu. Obiekt taki pozwala nam na przeglądanie zawartości zwróconych rekordów nawigując po zbiorze rekordów. Poniżej znajduje się przykład kodu który wyświetla na ekranie zawartość rekordów które otrzymaliśmy w wyniku przedstawionego powyżej zapytania:
If NOT objResultRecordSet.BOF and NOT objResultRecordSet.EOF Then
Do Until objResultRecordSet.EOF
Wscript.echo "Created - "& objResultRecordSet.Fields("whenCreated").Value &";Sam="& _
objResultRecordSet.Fields("samAccountName").Value &";Name="& _
objResultRecordSet.Fields("Name").Value & _
";DN="& objResultRecordSet.Fields("distinguishedName").Value
objResultRecordSet.MoveNext
Loop
Else
wscript.echo "Zdefiniowane zapytanie nie zwrocilo zadnych danych"
End If
Przedstawione powyżej przykÅ‚ady pokazywaÅ‚y jak podłączyć siÄ™ do domeny AD – wszystkie wykonywane zapytania bÄ™dÄ… odnosiÅ‚y siÄ™ do tej domeny lub wyspecyfikowanej w zapytaniu jej części. Jeżeli nasza struktura AD skÅ‚ada siÄ™ z wiÄ™cej niż jednej domeny i chcielibyÅ›my wykonać zapytanie obejmujÄ…ce dane z caÅ‚oÅ›ci naszego lasu konieczne jest wykorzystanie dodatkowego mnemonika okreÅ›lajÄ…cego źródÅ‚o danych jakim jest GC:// – pozwalajÄ…cy podłączyć siÄ™ do zasobów Katalogu Globalnego (Globa Catalog). W tym przypadku definicja zapytania wyglÄ…da nastÄ™pujÄ…co:
- dla składni dialektu SQL:
strSQLStmt = "SELECT whenCreated, distinguishedName, Name, samAccountName FROM 'GC://" & strDomain & "' WHERE objectCategory='Person'"
- dla składni dialektu LDAP:
strSQLStmt= "
Aby pokazać, że dla każdego z dostÄ™pnych w ramach ADSI “dostawców” metody dostÄ™pu do danych przedstawiam poniżej “szkolny” przykÅ‚ad zastosowania interfejsu ADSI do dostÄ™pu do zasobów przestrzeni nazewniczej ADs:// – jak to zostaÅ‚o wspomniane na poczÄ…tku, umożliwia ona dostÄ™p do informacji o samym interfejsie ADSI. Poniżej przedstawiona zostaÅ‚a metoda wylistowania dostÄ™pnych w systemie “dostawców” dla interfejsu ADSI:
Set objProvider = GetObject("ADs:")
For Each Provider In objProvider
Wscript.Echo Provider.Name
Next
W wyniku działania tego skryptu w standardowej instalacji interfejsu ADSI uzyskamy poniższy wynik:
WinNT: przedstawiający listę dostawców obecnych w systemie.
NWCOMPAT:
NDS:
LDAP:
IIS:
Modyfikacja danych obiektu
Przedstawione powyżej metody dostępu do katalogu i wybrania interesujących nas obiektów są przydatne nie tylko przy tworzeniu różnego rodzaju zestawień ale mogą również posłużyć do wybrania interesującego nas zakresu obiektów i zmodyfikowania odpowiedniego atrybutu tychże. Jako przykład takiego zastosowania przedstawiony zostanie poniżej skrypt modyfikujący ścieżkę do katalogu domowego użytkowników. Skorzystamy tutaj z dostępu do zawartości katalogu poprzez ADO DB w celu wybrania interesującego nas zakresu obiektów, a następnie z metody GetObject w celu podłączenia się do odpowiedniego obiektu i zmodyfikowania jego danych.
Zadanie to przeÅ›ledźmy na podstawie hipotetycznego zadania zmodyfikowania wartoÅ›ci parametru okreÅ›lajÄ…cego Å›cieżkÄ™ folderu domowego oraz litery dysku do której jest on mapowany przy logowaniu dla użytkowników w wybranym OU (na przykÅ‚ad o nazwie “Ksiegowosc”).
'w celu pewnej uniwersalizacji skryptu sciezke dostepu do OU (bez domeny)
'umiescimy w zmiennej - jej wartosc mozna przekazac jako parametr wywolania skryptu
strOUPath = "OU=Ksiegowosc"
Set rootDSE = GetObject("LDAP://rootDSE")
strDomain = rootDSE.Get("defaultNamingContext")
'okreslamy pelna sciezkke OU na ktorym chcemy wykonac operacje
strADPath = strOUPath & "," & strDomain
W celu wybrania odpowiednich danych wykonamy zapytanie do katalogu poprzez ADODB, wybierając z OU określonego utworzoną powyżej ścieżką obiekty użytkowników. Ponieważ w dalszej części skryptu będziemy odwoływać się do tych obiektów z zastosowaniem wartość która jest nam potrzebny to atrybut DN obiektu.
strSQLStmt = "SELECT samAccountName, distinguishedName FROM 'LDAP://" & strADPath & "' WHERE objectCategory='Person' and objectClass='user'"
Tak skonstruowane zapytanie do katalogu wykonujemy w sposób przedstawiony wcześniej, przy omawianiu dostępu do danych katalogu. Zmiana w stosunku do poprzedniego skryptu pojawia się w momencie iteracji po danych zawartych w zwróconym zestawie rekordów. W poprzednim przypadku po prostu listowaliśmy je na ekranie, teraz chcemy zrobić coś więcej czyli zmodyfikować pewne atrybuty tych obiektów. Wynikiem naszego zapytania są nazwy DN tychże obiektów, które posłużą nam jako ścieżka odwołania do nich. Dostęp do obiektu uzyskujemy poprzez wywołanie metody GetObject, która pobiera obiekt na podstawie podanej ścieżki dostępu (w naszym przypadku nazwy DN pobranej we wcześniejszym zapytaniu). Metoda ta próbuje uzyskać dostęp do obiektu na podstawie poświadczeń bezpieczeństwa użytkownika wywołującego skrypt.
'proba pobrania obiektu uzytkownika poprzez wartosc jego atrybutu DN
Set objUser = GetObject (objResultRecordSet.Fields("distinguishedName").Value)
'obsluga mozliwego bledu polaczenia
If Err.Number Then
Wscript.echo "blad dostepu do obiektu: " & & _
objResultRecordSet.Fields("distinguishedName").Value
Else
'tutaj znajdzie sie obsluga zmiany wartosci atrybutow obiektu
End If
W przypadku gdybyÅ›my chcieli uzyskać dostÄ™p do danych na podstawie alternatywnego zestawu poÅ›wiadczeÅ„, bÄ™dÄ…cych na przykÅ‚ad parametrem wywoÅ‚ania skryptu (co znacznie wpÅ‚ywa na jego uniwersalność) konieczne jest posÅ‚użenie siÄ™ innÄ… metodÄ… “OpenDSObject” (IADsOpenDSObject::OpenDSObject. Metoda ta pozwala na okreÅ›lenie danych uwierzytelnienia przy połączeniu do katalogu. Powyższy fragment kodu przedstawia siÄ™ wtedy nastÄ™pujÄ…co:
'pobietramy obiekt typu IADsOpenDSObject
Set dsObject = GetObject("LDAP:")
'Podlaczenie sie do wybranego obiektu
objDomain = dsObject.OpenDSObject("LDAP://" & objResultRecordSet.Fields("distinguishedName").Value
,strUserName,strPass, ADS_SECURE_AUTHENTICATION)
'obsluga mozliwego bledu polaczenia
If Err.Number Then
Wscript.echo "Blad dostepu do obiektu: " & objResultRecordSet.Fields("distinguishedName").Value
Else
'tutaj znajdzie sie obsluga zmiany wartosci atrybutow obiektu
End If
Po uzyskaniu dostępu do obiektu możemy przystąpić do modyfikacji jego atrybutów. Modyfikacji tej dokonujemy poprzez wywołanie metody Put wywołanej w stosunku do pobranego obiektu:
With objUser
.Put "homeDrive", "Y:"
.Put "homeDirectory",strServerFS & "\Users\" & objResultRecordSet.Fields("samAccountName").Value
End With
W przypadku gdy wartość którą będziemy modyfikować będzie składała się z kilku oddzielnych wartości lub łańcuchów (na przykład kilku adresów e-mail przypisanych użytkownikowi w miejsce metody Put konieczne jest zastosowanie metody PutEx. Tak więc całość kodu iterującego po wynikach naszego zapytania i zmieniającego wartość wybranych przez nas parametrów przedstawia się następująco:
If NOT objResultRecordSet.BOF and NOT objResultRecordSet.EOF Then
Do Until objResultSet.EOF
Set objUser = GetObject (objResultRecordSet.Fields("distinguishedName").Value)
If Err.Number Then
Wscript.echo "Blad dostepu do obiektu: " & objResultRecordSet.Fields("distinguishedName").Value
Else
With objUser
.Put "homeDrive", "Y:"
.Put "homeDirectory","Server\Users\" & objResultRecordSet.Fields("samAccountName").Value
.SetInfo
End With
End If
objResultRecordSet.MoveNext
Loop
Else
wscript.echo "Zdefiniowane zapytanie nie zwrocilo zadnych danych"
End If
Ten sam efekt możemy uzyskać w inny sposób, poprzez pobranie obiektu OU z wykorzystaniem metody GetObject lub OpenDSObject, następnie zaś filtrując wszystkie inne obiekty niż obiekty użytkowników i wykonując dla nich określone zmiany. Realizacja tego zadania w skrypcie korzystając z tej metody wygląda następująco:
'tworzymy sciezke dostepu do OU w katalogu
strOUPath = "OU=Ksiegowowsc"
Set rootDSE = GetObject("LDAP://rootDSE")
strDomain = rootDSE.Get("defaultNamingContext")
strADPath = strOUPath & "," & strDomain
'Pobranie obiektu OU
Set objOU = GetObject("LDAP://" & strADPath )
'Nakladamy na wybrany obiekt filtr w celu uzyskania tylko interesujacych nas obiektow
objOU.Filter = Array("user")
For each objUser in objOU
'Dodatkowe sprawdzenie klasy obiektu
If objUser.Class = "user"Then
'Modyfikacja danych obiektu
objUser.Put "homeDrive", "Y:"
'ustalamy nazwe katalogu domowego na podstawie wartosci samAccountName pobranej z danych uzytkownika
objUser.Put "homeDirectory","\\Server\Users\" & objUser.Get ( "samAccountName")
'zapisujemy dane uzytkownika
objUser.SetInfo
End If
Next
wscript.quit 0
Więcej zasobów na WWW dotyczących interfejsu ADSI
- Platform SDK\ Active Directory Service Interfaces
- PrzykÅ‚ady skryptów – ADSI Scriptomatic
- PrzykÅ‚ady skryptów z książki “Active Directory Cookbook”
Przykłady skryptów
Poniżej przedstawione zostały pełne kody skryptów które omawiane były w ramach tego artykułu:
Modyfikacja danych użytkownika:

