Иван Коробко
PowerShell. Поиск объектов в каталоге Active Directory
Большинство операций в каталоге Active Directory сводится к поиску объектов по различным критериям. Рассмотрим подробно процедуру с помощью библиотек .NET Framework на PowerShell.
Поиск объектов в Active Directory осуществляется с помощью класса .NET FrameWork System.Directory Services.DirectorySearcher и одного из методов: FindAll() или FindOne() в зависимости от условий поиска. Сценарий поиска можно условно разделить на несколько логических частей:
- доступ к контейнеру;
- фильтр поиска;
- атрибуты поиска;
- область поиска;
- сортировка элементов;
- поиск объектов.
Доступ к контейнеру
Для поиска объекта необходимо определить точку входа – путь к контейнеру, в котором будет осуществляться поиск. Для выполнения этого условия необходимо создать объект DirectorySearcher:
$path = "LDAP://OU=WorkSpace,DC=Island,DC=ru"
$obj = New-Object DirectoryServices.DirectorySearcher($Path)
Созданный объект содержит ряд свойств и методов. Полный отображения списка свойств и методов, поддерживаемых объектом, необходимо использовать командлетом Get-Member или его псевдонимом (gm):
$obj | Get-Member
Примечание: командлет – это обычная команда, выполняющая определённое действие с указанным объектом. Любой командлет состоит из глагола и существительного, разделенных дефисом. Глагол указывает, какое действие будет производиться, а существительное указывает объект, над которым будет производиться это действие. Командлеты поддерживают сокращения. Полный список сокращений можно получить с помощью команды:
Get-Alias | Format-Table -Property Definition, Name -Autosize
Для осуществления процедуры поиска необходимо задать несколько параметров:
- Фильтр поиска (обязательный параметр). С помощью него формируется поисковый запрос.
- Список атрибутов. Задается для сокращения времени поиска.
- Область поиска. Определяет глубину поиска.
- Сортировка. Позволяет получить результат в нужном виде.
Фильтр поиска
При написании фильтра поиска используются выражения, строящиеся по определенным правилам, а именно (см. таблицу 1):
- каждое выражение должно быть заключено в скобках;
- в выражениях допускается использование операторов сравнения: «<», «<=», «=», «>=» и «>»;
- допускаются составные выражения, образуемые с помощью префиксных операторов «&», «|», «!».
Любой фильтр обычно состоит из двух логических частей. С помощью одной части определяют тип искомых объектов, в другой – искомое значение. Наличие фильтра необязательно.
Таблица 1. Операторы фильтра поиска
Оператор
|
Значение
|
Оператор
|
Значение
|
=
|
Эквивалентно
|
&
|
И
|
~=
|
Примерно равно
|
|
|
ИЛИ
|
<=
|
Меньше или равно
|
!
|
НЕ
|
>=
|
Больше или равно
|
*
|
ВСЕ
|
Тип объекта определяется последним значением массива objectClass. Например, для учетной записи пользователя objectClass = person, для группы – group, для контейнера – organizationalUnit.
Приведу несколько характерных примеров фильтров:
- (objectClass=*). Все объекты имеют принадлежность к одному из классов. Данный фильтр вернёт все объекты в области поиска;
- &((objectClass=person)(!objectClass=computer)). Будут возвращены все объекты, принадлежащие к классу Person и не принадлежащие к классу computer. Такими объектами являются учетные записи пользователей;
Будут возвращены все объекты с описанием test и именем, начинающимся со слова User, а также объект Group1.
Встречаются случаи, в которых необходимо указывать служебные символы в качестве значений, такие как звездочку, скобку и др. Чтобы реализовать эту возможность, вместо символа необходимо указать соответствующее ему кодовое значение, приведенное в таблице 2.
Таблица 2. Зарезервированные имена, используемые в фильтрах поиска
Символ
|
Значение
|
*
|
\2a
|
(
|
\28
|
)
|
\29
|
\
|
\5c
|
NUL
|
\00
|
/
|
\2f
|
Атрибуты объекта
Для ускорения процедуры поиска используется свойство PropertiesToLoad. Используя метод Add()формируется массив полей, значения которых загружаются в память во время поиска. Например: PropertiesToLoad.Add("cn").
Область поиска
Область поиска задается с помощью свойства SearchScope:
- SearchScope.Base или 0 – поиск осуществляется по корневому объекту, указанному в первой части запроса. Всегда возвращается либо один объект, либо пустой набор объектов. Эта область поиска чаще всего используется для проверки существования объекта, указанного в запросе.
- SearchScope.OneLevel или 1 – поиск осуществляется в пределах указанного контейнера, указанного в первой части запроса. Поиск по вложенным объектам более низких уровней не производится. В поиск также не попадет и сам объект-контейнер.
- SearchScope.SubTree или 2 – поиск осуществляется по всем вложенным объектам. В поиск при этом не попадает сам объект-контейнер. Эта область поиска задана по умолчанию.
Сортировка элементов
Сортировка элементов осуществляется с помощью метода Sort, поддерживающего два свойства: PropertyName и Direction. С помощью свойства PropertyName указывается название поля, по которому будет осуществляться сортировка, а с помощью Direction – направление. В случае Sort.Direction = 0 или Sort.Direction = "Ascending" осуществляется упорядочивание от «А» до «Я», при Sort.Direction = 1 или Sort.Direction = «Descending» – в обратном порядке.
Поиск объктов
В зависимости от конкретного случая для выполнения процедуры поиска после задания всех условий осуществляется вызов одного из методов поиска:
- FindOne() – возвращает только один/первый объект;
- FindAll() – возвращают коллекцию (массив) объектов, элементы которой удовлетворяют заданным критериям поиска.
Приведу по одному примеру на каждый из методов.
Каталог Active Directory устроен так, что имя учетной записи группы или пользователя должны быть уникальны. Например, если необходимо определить список членов группы и если имя группы точно известно, то разумнее всего использовать метод FindOne(). В листинге 1 приведен пример определения списка пользователей, являющихся членами группы Print Managers. Поскольку результат поиска единственный элемент, то управлять сортировкой выводимых значений не имеет смысла.
Замечание: комментарии в листинге на языке Power Shell начинаются с символа решетки (#).
Листинг 1. Поиск объекта с помощью метода FindOne()
# Получение доступа к корню домена
$Search = New-Object DirectoryServices.DirectorySearcher("LDAP://DC=Island,DC=ru")
# Критерий поиска — отображаемое имя группы
$Search.Filter = "(&(cn=Print Managers))"
# Поиск по всему каталогу Active Directory
$Search.SearchScope = 2
# Выполнение поиска
$result = $Search.FindOne()
# Получение доступа к группе с помощью относительного пути
$obj = $result.GetDirectoryEntry()
# Чтение элементов массива
$obj.psbase.properties.member | % {
$member = [ADSI]( "LDAP://"+$_)
Write-Host $member.Name}
Рассмотрим подробно алгоритм работы сценария. На первом этапе определяется контейнер, в котором будет осуществляться поиск. Затем осуществляется настройка параметров поиска: фильтр, глубина и др. Исходя из поставленной задачи можно составить несколько вариантов фильтра.
Предпочтение стоит отдать самому короткому, поскольку это, с одной стороны, упростит скорость поиска, а с другой – сделает ваш листинг более понятным. После того как все необходимые параметры заданы, осуществляется вызов метода FindOne(). Результат поиска в данном случае присваивается объекту $result.
Для получения доступа к указанному объекту используют метод GetDirectoryEntry(). Обратите внимание, что при его вызове относительный составной путь (distinguishedName) найденного объекта не указывается, поскольку он подставляется автоматически.
По своему действию строка:
$obj = $result.GetDirectoryEntry()
эквивалентна строке:
$obj=[ADSI]("LDAP://"+ $result.distinguishedName)
На завершающем этапе осуществляется чтение значений указанного атрибута. PowerShell представляет значения атрибутов в зависимости от их типа: в строку (числа, строки), в массив (массив). Получение доступа к данным осуществляется по шаблону:
$data = $obj.psbase.properties.СВОЙСТВО
где «СВОЙСТВО» – имя атрибута. Таким образом, для чтения строковых и числовых характеристик следует использовать шаблон 1, для массивов – шаблон 2.
Шаблон 1. Чтение строковых и числовых атрибутов
# Атрибуты name, primarygroup, description и т.д.
$data = $obj.psbase.properties.СВОЙСТВО
write-host $data
Шаблон 2. Чтение массивов
# Атрибуты member, memberOf и т.д.
$data = $obj.psbase.properties.СВОЙСТВО
ForEach ($item in $data) {
Write-Host $item}
Для чтения массивов используется оператор ForEach, который в отличие от других языков программирования в PowerShell имеет сокращенную форму записи (см. шаблон 3). Конструкция «ForEach ($element in $array){}» заменяется конструкцией «$array | % {}». В этом случае в цикле обращение к текущему элементу массива осуществляется с помощью $_.
Шаблон 3. Чтение массивов (сокращенная запись)
# Атрибуты member, memberOf и т.д.
$obj.psbase.properties.СВОЙСТВО | % {
Write-Host $_}
Метод поиска FindAll() используется в тех случаях, когда необходимо найти все объекты, удовлетворяющие указанным критериям, например, все учетные записи пользователей в указанном контейнере или выключенные учетные записи и др. Приведем пример поиска всех учетных записей пользователей (см. листинг 2), находящихся в указанном контейнере, имена которых начинаются с буквы «А».
Листинг 2. Поиск объекта с помощью метода FindAll()
# Получение доступа к корню домена
$Search = New-Object DirectoryServices.DirectorySearcher("LDAP://DC=Island,DC=ru")
# Критерий поиска — отображаемое имя группы
$Search.Filter = "(&(objectClass=User)(cn=A*)(!objectClass=Computer))"
# Поиск по всему каталогу Active Directory
$Search.SearchScope = 2
# Сортировка результатов поиска
$Search.Sort.Direction = 1
$Search. Sort.PropertyName="sAMAccountName"
# Выполнение поиска
$Search.FindAll() | % {
# Получение доступа к группе с помощью относительного пути
$obj = $_.GetDirectoryEntry()
# Чтение данных
Write-Host $obj.samaccountname}
Исходя из поставленной задачи отметим, что фильтр поиска состоит из двух частей. В первой из них выбираются учетные записи пользователей (см. таблицу 3):
(&(objectClass=User)(!(objectClass=Computer)))
Вторая часть – из отобранных учетных записей выбрать начинающиеся с буквы «А». Фамилия пользователя хранится в значении атрибута sn (second name), поэтому фильтр будет выглядеть следующим образом:
(&(cn=А*))
Объединим два фильтра в один, получим:
(&(objectClass=User)(cn=A*)(!objectClass=Computer))
Таблица 3. Взаимосвязь типов объектов Active Directory и значений параметра objectClass
Комментарий
|
Тип объекта
|
Значение objectClass
|
Фрагмент поискового запроса
|
Учетная запись компьютера
|
Computer
|
Top
Person
OrganizationalPerson
User
Computer
|
(&(objectClass=Computer))
|
Группа безопасности
|
Group
|
Top
Group
|
(&(objectClass=Group))
|
Папка дерева каталогов Active Directory
|
OU
|
Top
OrganizationalUnit
|
(&(objectClass=OrganizationalUnit))
|
Опубликованный в Active Directory сетевой принтер
|
Printer
|
Top
Leaf
ConnectionPoint
PrintQueue
|
(&(objectClass=PrintQueue))
|
Опубликованная в Active Directory сетевая папка
|
Shared Folder
|
Top
Leaf
ConnectionPoint
Volume
|
(&(objectClass=Volume))
|
Учетная запись пользователя, совместимая с доменами Windows NT
|
User
|
Top
Person
OrganizationalPerson
User
|
(&(objectClass=User)(!(objectClass=Computer)))
|
Заключение
Уже на стадии поиска можно осуществлять сортировку значений выбранного поля в указанном направлении. Составлять запрос стало гораздо проще по сравнению с VBScript. Если раньше для составления запроса требовались хотя бы минимальные знания по SQL, то сейчас достаточно знать названия полей. С выходом Windows Server 2008 узнать поля стало еще легче: утилита ADSI Edit теперь входит в комплект операционной системы и устанавливается по умолчанию.