Используем ADSI для чтения обновляемых значений из таблицы Excel
В тех компаниях, которые используют Active Directory (AD) для хранения информации о пользователях, часто возникают проблемы, связанные с необходимостью обновления информации для большой группы людей. Например, если некоторое подразделение компании в полном составе куда-либо переезжает, то возникает задача обновления информации в AD об адресах и телефонах сотрудников данного подразделения. В таких случаях обычно пользуются файлами в формате LDIF (Lightweight Directory Access Protocol (LDAP) Data Interchange Format) совместно с утилитой ldifde.exe либо разрабатывают специальные сценарии, использующие функциональность ADSI (Active Directory Service Interfaces). Однако создавать LDIF–файлы для выполнения подобных обновлений – дело непростое, поскольку при формировании таких файлов может потребоваться большое количество манипуляций со строковыми данными. Решение, построенное на базе специальных сценариев ADSI, позволяет лучше контролировать процесс внесения изменений – в этом случае можно создавать удалять или изменять объекты любым, наиболее удобным для вас способом (например, можно изменять формат номера телефона, изменять регистр символов со строчных на прописные и т.д.) – но при этом необходимо найти способ извлечения обновленной информации и ее чтения внутри сценариев.
Для того чтобы упростить процесс, можно создать сценарий на VBScript, который через интерфейс ADSI обновляет атрибуты объектов AD, а исходные данные берет из файла формата CSV или таблицы Excel (как это описано во врезке "Применение Excel для анализа входных файлов"). Мною был разработан пример сценария, updateattribs.vbs, который вы можете адаптировать к своей инфраструктуре. Данный сценарий тестировался на системе Windows 2000 Service Pack 3 (SP3) совместно с Excel 2002 и Excel 2000, и он будет работать с любой версией Windows 2000.
Формирование требований к сценарию
При запуске сценария Updateattribs.vbs ему должны быть переданы два параметра: имя входного файла и имя выходного файла. Входной файл должен иметь формат, позволяющий открывать его с помощью Excel, а первая его строка должна содержать наименования каждого из обновляемых атрибутов. Порядок этих атрибутов может быть произвольным, единственное требование состоит в том, что в первом столбце должны размещаться атрибуты, соответствующие именам DN (distinguished name) подлежащих изменению объектов AD. В сценарии имя DN используется для идентификации соответствующего объекта. Строки данного файла должны содержать описания объектов (по одной строке на каждый объект), как показано на Рис. 1. Выходной файл представляет собой журнал, в который записываются все внесенные изменения по каждому из объектов AD, описания ошибок, имевших место в процессе выполнения, а также краткий отчет о ходе работы сценария.
Написание сценария
Команда запуска сценария имеет следующий синтаксис:
updateattribs.vbs[/v]
Необязательный параметр /v предписывает сценарию выполнять вывод данных в подробном (verbose) виде. При использовании данного параметра сценарий будет выводить с помощью команд Wscript.Echo большой объем информации (причем, даже если ключ /v не используется, тем не менее, объем выводимых данных будет значителен), поэтому его лучше запускать через сервер сценариев CScript. Если же запустить сценарий с помощью WScript, то это приведет к необходимости многократно нажимать на кнопку OK в появляющихся окнах сообщений.
В начале сценария Updateattribs.vbs (как видно из Листинга 1) производится чтение переданных ему параметров. Затем (Листинг 2) выполняется подключение к корневому объекту Root Directory Service Entry (rootDSE), а соответствующая ссылка записывается в переменную dso. Данный подход, рассматриваемый многими как наилучшая практика построения запросов LDAP через ADSI, является принципиальным для обеспечения производительности сценария. Каждый раз, когда сценарий подключается к объекту AD посредством функции GetObject, внутренние механизмы данной функции связываются с сервером LDAP. Эти операции связывания, выполняемые в ходе работы сценария, могут негативным образом повлиять на его производительность. Если же мы сохраняем ссылку на первоначальный вызов GetObject, то в этом случае отпадает необходимость в дополнительных операциях связывания при выполнении последующих вызовов GetObject, что исключает их влияние на производительность сценария.
После того как заданный файл выходных данных был открыт в режиме записи (Write), сценарий открывает в Excel соответствующий входной файл, что иллюстрирует фрагмент кода с меткой A на Листинге 3. Данный код создает скрытый экземпляр объекта приложения Excel Application. Код, обозначенный меткой B, осуществляет выбор диапазона таблицы, содержащего данные. Выбор полного диапазона позволяет использовать в сценарии свойства range.Columns.count и range.Rows.count для определения нужного количества строк и столбцов. Затем сценарий копирует данные в первую строку (т.е. заголовок) и сохраняет ее в виде массива с именем attributeNames.
Далее большая часть сценария занимается построчной обработкой данных таблицы и, при необходимости, выполняет обновление объектов AD. Пример данной части сценария приведен на листинге 4. Исходя из соображений производительности сценарий обновляет только те атрибуты объектов AD, значения которых отличаются от существующих.
Сначала сценарий, используя свойство Cell объекта Range, получает доступ к листу Excel и извлекает имя DN первого объекта AD. Затем предпринимается попытка получить ссылку на этот объект путем вызова функции GetObject для данного DN и сохранить эту ссылку в переменной ADObject. Если ссылка была получена успешно, сценарий с помощью свойства Cell объекта Range считывает обновленные значения каждого из атрибутов в массив attributeNames, используя для указания положения каждой из ячеек переменные i (строка) и j (столбец). Для извлечения существующих значений атрибутов из AD используется функция Get объекта IADs, ссылка на которую хранится в переменной ADObject. К каждому набору значений применяется функция LCase, которая выполняет преобразование букв обоих строк в нижний регистр, после чего эти значения сравниваются для выявления совпадений. Если значения различаются, тогда сценарий с помощью метода Put объекта IADs обновляет соответствующие атрибуты, извлекая имя и значение атрибута из данных таблицы и передавая их методу в качестве параметров. После обновления параметров объекта AD сценарий использует метод SetInfo для принятия внесенных изменений, как показано во фрагменте кода, приведенном ниже:
If (changeMade = True) Then ADObject.SetInfo End If
После того как сценарий обработает данные каждой из строк таблицы, будет выведен краткий отчет о количестве обработанных строк, обновленных объектов AD, обновленных атрибутов и имевших место ошибках. Затем сценарий закрывает все входные и выходные файлы и запущенный экземпляр объекта Excel Application. Данный код "зачистки" показан на Листинге 5.
Сам сценарий Updateattribs.vbs достаточно прост, однако для его успешного использования следует учитывать несколько важных моментов:
- При работе с многозначным (multivalued) атрибутом объекта метод Put обновляет только самое первое значение, поэтому данный сценарий не следует использовать в тех случаях, когда необходимо обновлять большое количество многозначных атрибутов. Соответственно, прежде чем использовать данный сценарий, необходимо проанализировать, какие именно атрибуты вы собираетесь при помощи него обновлять, и не приведет ли к возникновению проблем описанное выше ограничение.
- В Updateattribs.vbs применяется сравнение строковых значений, которое может не срабатывать для двоичных (binary) параметров, поэтому в данном применении этот сценарий использоваться не может. Более того, значительная часть (если не все) из доступных в AD двоичных параметров (таких как глобально уникальные идентификаторы (GUID) или пароли) не должны модифицироваться данным способом.
- Прежде чем запускать сценарий, необходимо закрыть все запущенные копии Excel и не открывать Excel во время работы сценария.
- Сценарий должен запускаться с помощью сервера сценариев Cscript.exe.
- При запуске сценария не забудьте полностью задать путь к входному файлу (если в описании пути имеются пробелы, то оно должно быть заключено в кавычки (")).
- Проверьте правильность задания имен DN во входном файле. Для извлечения данных параметров из AD можно использовать программы просмотра LDAP (LDAP browser), такие как LDP, ADSI Edit или PADLE-LDAP Explorer (http://www.scripthorizon.com).
- По возможности, сначала проверьте функционирование сценария в тестовой среде и убедитесь, что он необходимым образом обновляет требуемые атрибуты, и только потом запускайте его в промышленную эксплуатацию.
Сила и мощь технологии сценариев
Процедура обновления атрибутов объектов AD не должна быть утомительным и подверженным ошибкам процессом. Поэтому в следующий раз, когда начальник вручит вам таблицу со списком из 1000 адресов и номеров телефонов сотрудников и попросит обновить эти данные в AD, вы можете с уверенностью сказать, что через минуту все будет готово.
Листинг 1. Код для считывания заданных параметров
Set oArgs = Wscript.Arguments
infile = oArgs(0)
outfile = oArgs(1)
verbose = false
If oArgs.Count > 2 Then
If LCase(oArgs(2)) = "/v" Then
' Пользователю требуется подробный вывод данных.
verbose = true
End If
End If
Листинг 2. Код связи с сервером LDAP
Set dso = GetObject("LDAP://RootDSE")
If Err.Number <> 0 Then
WScript.Echo "Невозможно подключиться к: " & "LDAP://RootDSE"
WScript.Quit 1
End If
Листинг 3. Код открытия входного файла и выбора данных
‘ BEGIN CALLOUT A
Set appExcel = CreateObject("Excel.Application")
Set workbook = appExcel.WorkBooks.open(infile)
appExcel.visible = false
workbook.activate
‘ END CALLOUT A
‘ BEGIN CALLOUT B
BEGIN COMMENT
' Выбирается полностью весь диапазон, в котором содержатся данные.
END COMMENT
Set range = appExcel.ActiveCell.CurrentRegion
‘ END CALLOUT B
Листинг 4. Код обновления объектов AD
' Построчная обработка данных.
For i = 2 to range.Rows.Count
Dim numUpdates
Dim outputString
Dim ADObject
Dim j
Dim attrValFromWkSht, attrVal
' Сброс счетчика количества обновлений,
' выполненных для данного объекта.
numUpdates = 0
outputString = ""
numObjects = numObjects + 1
Err.Clear
changeMade = False
' Получаем отличительное имя (DN) объекта,
'которое должно быть заменено значением из таблицы.
' (предполагается, что атрибут DN содержится в первом столбце).
dn = range.cells(i,1).value
If verbose = true Then
WScript.Echo "Подключение к: " & dn
End If
' Получаем ссылку на объект.
Set ADObject = GetObject("LDAP://" & dn)
If Err.Number <> 0 Then
' Неудачная попытка; по указанному пути объект не существует.
If verbose = true Then
WScript.Echo "Ошибка: " & dn &
" не существует!"
End If fsOut.WriteLine " Ошибка: " & dn
& " не существует!"
numErrors = numErrors + 1
Else
' Связь установлена успешно, объект существует.
For j = 1 to range.Columns.Count - 1
attrValFromWkSht = range.cells(i,j+1).value
Err.Clear
' Get value for each attribute.
attrVal = ADObject.Get(attributeNames(j))
If Err.Number = 0 Then
' Проверяем, отличается ли значение, заданное в таблице
' от значения, имеющегося в AD.
If (LCase(CStr(attrValFromWkSht))
<> LCase(CStr(attrVal))) Then
' обновляем значение.
ADObject.put attributeNames(j),CStr(attrValFromWkSht)
outputString = outputString & "ОБНОВЛЕНО
значение: " & dn & ": " & _
attributeNames(j) & " : с " &
attrVal & " на " & _
attrValFromWkSht & vbCrLf
numUpdates = numUpdates + 1
changeMade = True
End If
Else
' Атрибут имеет пустое значение, обновляем его.
ADObject.put attributeNames(j),attrValFromWkSht
outputString = outputString & " ОБНОВЛЕНО значение::
" & dn & ": " & _
attributeNames(j) & " : с [пустого]
на " & attrValFromWkSht & vbCrLf
numUpdates = numUpdates + 1
changeMade = True
End If
Next
Err.Clear
If (changeMade = True) Then
' Commit the changes.
ADObject.setInfo
If Err.Number = 0 Then
' Все изменения были внесены успешно.
If verbose = true Then
WScript.Echo "Обновлено " &
numUpdates & " значения(й) для " & dn
End If
numUpdatedObjects = numUpdatedObjects + 1
numUpdatedAttribs = numUpdatedAttribs + numUpdates
fsOut.Write outputString
Else
' Невозможно принять внесенные изменения;
'вывод сообщения об ошибке.
If verbose = true Then
WScript.Echo "ОШИБКА обновления " & dn
End If
fsOut.WriteLine " ОШИБКА обновления " & dn
numErrors = numErrors + 1
End If
Else
If verbose = true Then
WScript.Echo "Для " & dn &" изменений нет"
End If
End If
End If
Next
Листинг 5. Код закрытия экземпляра объекта Excel
appExcel.Quit
set appExcel = nothing
fsOut.Close
set fsOut = nothing
set dso = nothing
Применение Excel для анализа входных файлов
Главное преимущество использования файлов формата CSV (comma-separated value – значения, разделяемые запятой) для создания списков атрибутов объектов Active Directory (AD) и тех их значений, которые требуется модифицировать, состоит в том, что файлы такого формата могут генерироваться большинством используемых средств экспорта данных. Более того, в первой строке мы можем легко задавать имена атрибутов и рассматривать ее как строку заголовка, а затем указывать в последующих строках соответствующие значения в легко читаемом формате, имеющем форму таблицы. Но основная проблема при использовании файлов CSV заключается в разделении различных полей. По определению в формате CSV в качестве символа разделения полей используется запятая, соответственно, если данные каких-либо полей также содержат запятые, то в сценарии это приведет к проблемам при попытке непосредственной обработки таких данных из файлов CSV.
При решении подобных проблем огромным подспорьем является приложение Microsoft Excel. Действительно, вместо того чтобы использовать для обработки строковых данных сценарии Windows Shell или VBScript, мы можем просто использовать в сценарии объект Excel Application, с помощью которого открыть файл CSV и обработать содержащиеся в нем данные. Можно также использовать в качестве входного файла таблицу Excel, а также импортировать данные из базы или из любого другого формата, поддерживаемого Excel.