понедельник, 2 ноября 2009 г.

Неординарная работа с Active Directory



Организации с уже давно сформировавшейся инфраструктурой сети и доменов AD все чаще сталкиваються с необходимостью получать данные о различных обьектах из Active Directory,
будь-то простое определение пользователей членов некоторой группы безопастности или же полное интегрирование уже существующей системы управления персоналом с доменом. Эта статья предназначена прежде всего для облегчения работы системных администраторов предприятий, благодаря автоматизации рутинных задач с помощью скриптовых (и не только) языков программирования.



Начнем с основ

Active Directory представляет собой LDAP-совместимую реализацию Службы Каталогов, основное предназначение которой – предоставить централизированное средство управления всеми елементами определенной организации подключенными к сети, а так же хранить подробную информацию о них. Каждый елемент Службы Каталогов принято называть обьектом, а информация о нем храниться в виде набора атрибутов.
Ограничение на количество обьектов накладываеться только движком БД(Microsoft Jet Blue) который для своей работы использует АД. Теоретически эти ограничения составляют: 16Тб на размер базы данных и около 1млрд обьектов Службы Каталога. Пока что этот лимит небыл превышен и вряд ли будет в обозримом будущем…
Для удобной работы с такими большимы количествами обьектов АД организовывает их в логические группы, создавая таким образом иерархическую структуру. Обьекты способные содержать в себе другие обьекты называют контейнерами.

Оснастика администрирования ActiveDirectory(Оснастика администратора ActiveDirectory)

Обьект можно однозначно идентифицировать по его имени. АД поддерживает такие форматы именования обьектов как: UNC, URL и LDAP. Каждый обьект имеет составное имя (Distinguished name, DN) и именно его мы будем использовать для манипулирования данными Службы Каталогов. Атрибуты составного имени:
  • DC – класс обьекта домена
  • OU – имя организационной единицы (подразделения)
  • CN – общее имя
Пример составного имени: CN=Ivanov I.A,OU=Маркетинг,DC=microsoft,DC=com
Каждому обьекту службы каталогов также присваиваеться уникальный идентификатор (GUID/UUID) – уникальная строка длиной в 128 бит, которая используеться большей частью для внутренних нужд Active Directory. Пример:

{1acc40ef-d1e1-4b0e-94d6-ef1894b3545a}

Физически вся информация об объектах AD храниться в файле
%systemroot%\NTDS\NTDS.DIT
Вся работа с ним осуществляеться системным агентом каталога(DSA), именно он выстраивает иерархию обьектов и предоставляет возможность работы этими данными приложениям извне используя протокол LDAP(Lightweight Directory Access Protocol). Для этого необходимо сформировать LDAP URL. Делаеться это достаточно просто, он имеет вид: LDAP://server/ + DN, где server – IP адрес или DNS имя сервера на котором располагаеться служба каталогов, а DN - уже знакомое нам составное имя. Пример ldap url:
LDAP://dc.microsoft.com/CN=IvanovI.A,OU=Маркетинг,DC=microsoft,DC=com

Приступаем


Имея все эти сведения можем уже попробовать написать простенькое приложение основная задача которого – соединиться со службой каталогов и забрать какие-то данные, пускай это будут сведения обо всех основных контроллерах домена предприятия.
Для реализации вышепоставленной задачи нам понадобиться:
  • Visual C# (мы начнем с него, можно и Express версии)
  • Реальный или виртуальный контроллер домена
  • Учетная запись с правами не ниже администратора домена
Создавать мы будем пока что консольное приложение, но потом легко будет подключить и GUI.
Создаем новый проект:

Сразу идем в References и подключаем System.DirectoryServices



Теперь подключаем пространство имен:
using System.DirectoryServices;
Теперь пара слов для чего мы все это проделали. Основным средством взаимодействия нашего приложения с Active Directory являеться обьект System.DirectoryServices. Располагаеться он во внешней библиотеке System.DirectoryServices.dll и чтобы его использовать нужно подключиться эту самую бибиотеку. После всех этих действий мы можем забыть о том что это внешний компонент и работать с ним как с родным.
Из пространства имен System.DirectoryServices нас прежде всего интересуют два класса: DirectoryEntry и DirectorySearcher. MSDN нам говорит что первый из них инкапсулирует в себе узел или обьект иерархии Active Directory, а второй служит для выполнения запросов к Active Directory Domain Services. Используя эти два класса можно делать с обьектами Службы каталога все что угодно.
using System; using System.Collections.Generic; using System.Linq; using System.DirectoryServices; using System.Text; namespace adcmd { class Program { static void Main(string[] args) { DirectoryEntry entry = new DirectoryEntry("LDAP://Toaster", "admin", "123"); foreach (DirectoryEntry child in entry.Children) Console.WriteLine(String.Format("{0}: ({1})", child.Name, child.Path)); } } }
Вот такой простенький код покажет нам ВСЕ обьекты AD первого уровня вложенности, с их путями в формате LDAP URL. Ключевое свойство здесь entry.Children, это коллекция и содержит она все дочерние елементы обьекта entry. Таким образом, мы можем добраться до обьектов любого уровня вложенности, ведь у child тоже есть поле Children. К родительскому же обьекту можно обратиться используя свойство Parent.
Далее неплохо было бы научиться работать с атрибутами обьектов. Делаеться это через свойство экземпляра класса DirectoryEntry Properties. Давайте посмотрим все атрибуты корневого елемента каталога:
using System; using System.Collections.Generic; using System.Linq; using System.DirectoryServices; using System.Text; namespace adcmd { class Program { static void Main(string[] args) { DirectoryEntry entry = new DirectoryEntry("LDAP://Toaster", "admin", "123"); foreach (string prop in entry.Properties.PropertyNames) Console.WriteLine(String.Format("{0}: {1}", prop, entry.Properties[prop].Value.ToString())); } } }
Вот что получилось у меня:

Итак, мы уже немного поднаторели в выборке данных, приступаем к реализации основной задачи. Как вы помните, нужно выбрать всех пользователей (вывести фамилию и инициалы) которые принадлежат определенной группе безопастности.
Алгоритм работы предельно прост:
  • Соединяемся со службой каталога;
  • вибираем всех пользователей;
  • проходимся по всех и смотрим атрибут MemberOf. Говоря на языке С# он представляет собой коллекцию обьектов типа String с именами всех груп безопастности, в которых состоит пользователь. Тоесть то что доктор прописал!
  • перебираем эту коллекцию и если находим соответствие – выводим на экран значение другого атрибута, а именно displayUsername, в нем содержиться фамилия и инициалы пользователя, указанные при введении его в AD.
Создаем новый проект, или же модицируем ранее созданный. Нам понадобяться те же библиотеки.
Код
using System; using System.Collections.Generic; using System.Linq; using System.DirectoryServices; using System.Text; namespace adcmd { class Program { static void Main(string[] args) { String FML; // здесь будем хранить инициалы String OU; // а здесь отдел /* Соединяемся с Active Directory */ DirectoryEntry entry = new DirectoryEntry("LDAP://internal.Toaster", "admin", "123"); /* Создаем обьект для поиска * DirectorySearcher mySearcher = new DirectorySearcher(entry); /* Настраиваем фильтр поиска (только пользователи) */ mySearcher.Filter = "(&(objectClass=user)(objectCategory=person))"; /* Сортировка по убыванию */ mySearcher.Sort = new SortOption("displayName", SortDirection.Ascending); /* Проходимся по всех найденных обьектах */ foreach (SearchResult resEnt in mySearcher.FindAll()) { /* Получаем DirectoryEntry для каждого обьекта AD */ DirectoryEntry de = resEnt.GetDirectoryEntry(); /* Если пользователь состоит в какой-то группе безопастности и имеет имя... */ if (de.Properties["MemberOf"].Value != null && de.Properties["displayName"].Value != null) { /* ...начинаем просмотр всех групп */ for (int i = 0; i < style="color: rgb(163, 21, 21);">"MemberOf"].Count; i++) /* Этот пользователь член нужной группы? */ if (de.Properties["MemberOf"][i].ToString().Contains("Бухгалтерия")) { /* Да, извлекаем Фамилию и инициалы */ FML = de.Properties["displayName"][0].ToString(); /* А теперь извлекаем OU(отдел) пользователя*/ OU = de.Parent.Path.ToString(); /* Чистим данные от ненужного мусора */ OU = OU.Replace("LDAP://internal.Toaster/OU=", ""); OU = OU.Replace(",DC=Toaster", ""); /* Выводим искомую информацию в форматированном виде. */ Console.WriteLine(String.Format("{0}\t{1}", FML, OU)); } } } Console.WriteLine("All done."); Console.ReadKey(); } } }
Ну вот и все, используя эти знания можно легко переписать получившийся код под любую задачу или же сделать все необходимые параметры изменяемыми, хардкодить не хорошо. А можно сделать эдакий универсальный менеджер для облегчения работы с доменом, стандартные оснастики довольно убоги. Вобщем поле для экспериментов довольно большое, дерзайте! За сим позвольте откланяться.

3 комментария:

  1. Этот комментарий был удален автором.

    ОтветитьУдалить
  2. Очень полезная статейка. Респект! :)

    ОтветитьУдалить
  3. После открытия своего интернет магазина придумал вести бухгалтерию тоже через интернет, так как это намного дешевле и удобнее чем вести учет в 1С или экселе, да и отчетность в электронном виде в налоговую лучше отправлять по электронной почте, чем заносить и толпится в очередях, поэтому подобрал себе онлайн сервис «Моё дело», мне он и по цене, и по функционалу подходит

    ОтветитьУдалить