Вадим Андросов
Делегируем права на перемещение
учетных записей пользователей в Active Directory
Часть 4. Завершение надстройки
В этой части статьи будет завершена разработка основного класса надстройки для Windows Server 2003, позволяющая разделить полномочия перевода пользователя из одного отдела в другой между локальными администраторами подразделений. Также рассмотрим использование регулярных выражений для упрощения обработки строк.
Команда отказа принятия пользователя
Рассмотрим обработчик команды отказа принятия пользователя. Имеется в виду случай, когда администратор организационной единицы-назначения отказывает пользователю в приеме. Подробно данный сценарий описан в первой части статьи [1].
function dispatchDenyCommand(cmd)
dim comment, ou, who
При отказе принять пользователя может быть указана причина, которая сохранится в поле комментария команды. Если его нет – в поле помещается значение «пусто» (empty).
comment = cmd.userMoveComment
if comment = "" then comment = "empty"
Далее подключаемся к объекту менеджера – создателя команды, чтобы проверить, имеет ли он право управлять этой организационной единицей.
set who = getObject(cmd.userMoveExecutor)
Для этого нужно получить экземпляр текущей организационной единицы. Поле userMoveTarget содержит ссылку на перемещаемый объект пользователя. Пользователь ожидает приема, поэтому находится внутри объекта «Стул ожидания», который в свою очередь содержится в объекте «Комната ожидания». То есть текущее подразделение можно получить, поднявшись на три уровня вложенности вверх (первый родительский контейнер – стул, второй – комната ожидания, третий – организационная единица). Это делается с помощью функции получения контейнера на заданное количество уровней выше getAncestor. В качестве первого параметра ей передается исходный объект, второго – количество уровней, на которое нужно подняться.
set ou = getObject(getAncestor(cmd.userMoveTarget, 3))
if canHeManageOU(ou, who) then
С помощью метода canHeManageOU определяем, имеет ли право текущий пользователь «распоряжаться» в этой организационной единице и отдавать подобные команды. Если необходимых прав нет, команда выполнена не будет. Но если выполнение дошло до этого места, – все в порядке.
dim chair, newChair, user
Подключаемся к объекту перемещаемого пользователя и его стула ожидания. Стул – контейнер первого уровня по отношению к ожидающему пользователю, для его получения используем функцию getParent. Она подобна getAncestor, но позволяет получить только непосредственный контейнер, что нам в данном случае и нужно.
set user = getObject(cmd.userMoveTarget)
set chair = getObject(getParent(cmd.userMoveTarget))
Дальше пользователя нужно вернуть в исходное подразделение. Окончательное возвращение – право менеджера этой организационной единицы, поэтому пользователь просто ставится в очередь. Для этого создается так называемый «зеркальный» стул в исходном отделе с помощью функции createMirroredChair.
set newChair = createMirroredChair(chair, cmd)
Затем пользователь перемещается, создается обратная ссылка. Текущий стул ожидания очищается с помощью clearEmptyChair, чтобы вернуть надстройке непротиворечивое состояние.
newChair.moveHere cmd.userMoveTarget, vbNullString
createBackLink getWaitingRoom(ou), "LDAP://" & user.name & "," & newChair.distinguishedName
clearEmptyChair chair, cmd.userMoveTarget
end if
В конце обработанный объект команды удаляется.
ou.delete DENY_COMMAND_CLASS, "CN=" & cmd.cn
end function
Рассмотрим вспомогательные функции. Функция getParent возвращает путь к родительскому контейнеру, получив в качестве параметра путь к дочернему. В ее работе используются регулярные выражения, поэтому особенности ее использования я рассмотрю позже. На ее основе создана функция получения контейнера на заданное количество уровней выше. Вся ее работа сводится к вызову нужное количество раз функции getParent.
Листинг 1. Функция определения предка
function getAncestor(path, levels)
dim i
getAncestor = path
for i = 1 to levels
getAncestor = getParent(getAncestor)
next
end function
Функция создания «зеркального» стула. Используется, когда пользователя необходимо «пересадить» из комнаты ожидания целевой организационной единицы в исходную.
function createMirroredChair(srcChair, denyCmd)
dim chairNumber, room
set room = getWaitingRoom(getObject(srcChair.userMoveFrom))
chairNumber = getNextNumber(room, CHAIR_CLASS)
Функция подключается к организационной единице-источнику (ее путь содержится в свойстве команды userMoveFrom) и создает там стул ожидания, копируя все свойства текущего. Изменяется только метка времени команды.
set createMirroredChair = room.create(CHAIR_CLASS,"CN=chair_" & chairNumber)
createMirroredChair.userMoveFrom = getAncestor(srcChair.ADSPath, 2)
createMirroredChair.userMoveWho = denyCmd.userMoveExecutor
createMirroredChair.userMoveComment = denyCmd.userMoveComment
В первой части статьи [1] для поля со временем операции был выбран тип UTC Coded Time. Необходимые преобразования выполняет функция toUTC (см. Листинг 2).
createMirroredChair.userMoveWhen = toUTC(now)
createMirroredChair.userMoveDisabled = srcChair.userMoveDisabled
createMirroredChair.setInfo
end function
Рассмотрим функцию приведения времени.
Листинг 2. Преобразование времени к типу UTC
Function toUTC(dat)
dim offsetMin
offsetMin = getTimeZoneOffset
toUTC= dateadd("n", offsetMin, dat)
end function
Собственно все преобразование сводится к смещению локального времени с учетом часового пояса. Смещение определяет функция getTimeZoneOffset, читая смещение из реестра (в минутах). То есть, скажем, если Киев находится в часовом поясе «+2», то смещение для получения времени по Гринвичу – минус 120 минут.
Понадобится еще функция обратного преобразования fromUTC. Ее реализация аналогична toUTC за исключением того, что полученное смещение нужно вычесть, а не добавить.
Листинг 3. Вычисление смещения времени для часового пояса
function getTimeZoneOffset()
if timeZoneOffset = "?" then
dim oShell, atb
set oShell = CreateObject("WScript.Shell")
atb ="HKEY_LOCAL_MACHINE\System\CurrentControlSet\" & "Control\TimeZoneInformation\ActiveTimeBias"
timeZoneOffset = oShell.RegRead(atb)
end if
getTimeZoneOffset = timeZoneOffset
end function
Наконец, рассмотрим функцию корректной очистки «опустевшего» стула ожидания. Передается ей два параметра: очищаемый стул и устаревший путь к объекту пользователя на этом стуле.
function clearEmptyChair(chairObj, whoWas)
dim srcOU
set srcOU = getObject(chairObj.userMoveFrom)
Сначала удаляется сам пустой стул ожидания.
chairObj.deleteObject(0)
dim room, li
set room = getWaitingRoom(srcOU)
room.filter = Array(LINK_CLASS
Затем в организационной единице-отправителе находится и удаляется ссылка на отправленный объект, которая теперь указывает в никуда. Так что «подвисших» ссылок не остается.
for each li in room
if li.userMoveLink = whoWas then
li.deleteObject(0)
exit function
end if
next
end function
Подтверждение перемещения
Менеджер также может подтвердить перевод пользователя в свою организационную единицу. Рассмотрим обработчик этой команды.
function dispatchAcceptCommand(cmd)
dim ou, chair, whom, ouFrom, who
По аналогии с прошлой командой сначала требуется подключиться к объектам создателя команды и организационной единице-получателю.
set who = getObject(cmd.userMoveExecutor)
set ou = getObject(getAncestor(cmd.userMoveTarget, 3))
if canHeManageOU(ou, who) then
Далее проверяем, имеет ли право пользователь, создавший эту команду, управлять текущей организационной единицей. Если имеет – выполняем завершение перевода.
set whom = getObject(cmd.userMoveTarget)
set chair = getObject(getParent(cmd.userMoveTarget))
Исходный статус пользователя (включен или нет) нужно восстановить.
whom.accountDisabled =chair.userMoveDisabled
whom.setInfo
После этого пользователь окончательно перемещается, а стул корректно очищается.
ou.moveHere cmd.userMoveTarget, vbNullString
clearEmptyChair chair, cmd.userMoveTarget
end if
ou.delete COMMAND_CLASS, cmd.name
end function
Отмена перемещения
Также перемещение пользователя может быть отменено самим инициатором. Реализация метода никаких принципиальных отличий не имеет. Сначала происходит подключение ко всем объектам – участникам операции: перемещаемый пользователь, стул ожидания, инициатор перемещения, текущая организационная единица. Затем проверяются права создателя команды на ее выполнение. Если результаты проверки положительны, пользователь возвращается в свое подразделение, а вспомогательные объекты (стул, обратная ссылка) удаляются.
Листинг 4. Отмена перемещения
function dispatchRollbackCommand(cmd)
dim userMove, chair, srcPath, srcOU, who
set userMove = getObject(cmd.userMoveTarget)
set chair = getObject(userMove.parent)
set who = getObject(cmd.userMoveExecutor)
set srcOU = getObject(chair.userMoveFrom)
if (chair.class = CHAIR_CLASS) and canHeManageOU(srcOU, who) then
userMove.accountDisabled = chair.userMoveDisabled
userMove.setInfo
srcOU.moveHere userMove.ADSPath, vbNullString
clearEmptyChair chair, cmd.userMoveTarget
end if
srcOU.delete COMMAND_CLASS, cmd.name
end function
Создание команд
Далее опишем функции подготовки команд, обработкой которых мы занимались ранее. Основная логика остается неизменной. Менеджер по персоналу создает объект команды в своей организационной единице. Сервер обрабатывает события появления команд, проверяет их на целостность и допустимость, выполняет и удаляет объект. То есть для пользователя работа серверной части надстройки остается полностью прозрачной. В случае невозможности выполнения команды она просто уничтожается.
Начнем с самой сложной и важной команды начала перемещения пользователя. Все подпрограммы подготовки команд будут также размещены в основном классе надстройки UserMove.Engine.
function move(whomPath, wherePath, comment)
Функции передается три параметра: кого перемещать, куда и с каким комментарием.
dim userMove, fromOU
if comment = "" then comment = "empty"
move = ""
set userMove = getObject(whomPath)
Ссылку на объект текущей организационной единицы можно получить, воспользовавшись свойством «родитель» (parent) перемещаемого объекта пользователя. Ведь пока он находится в своем подразделении.
set fromOU = getObject(userMove.parent)
if not canCurrentManageOU(fromOU) then
Если текущему пользователю не хватает прав для администрирования исходной организационной единицы, функция завершает работу, вернув сообщение об ошибке.
move = "Not enough right"
exit function
end if
Если прав достаточно, происходит окончательное перемещение.
move = transferUserTo(userMove, wherePath, fromOU, comment)
end function
Начало перемещения осуществляется в подпрограмме transferUserTo. Ей передаются следующие параметры: перемещаемый пользователь, путь к организационной единице-назначению, объект исходной организационной единицы и комментарий.
function transferUserTo(userMove, wherePath, fromOU, comment)
dim toOU
transferUserTo = ""
set toOU = getObject(wherePath)
Дальше производится проверка, не может ли менеджер по персоналу исходного отдела управлять еще и целевым. Если может (т.е. имеет достаточно прав в обоих отделах), то осуществляется простое перемещение пользователя сразу в целевую организационную единицу в обход надстройки. Это сделано, чтобы лишить менеджера необходимости сначала начинать перевод пользователя, а потом самому же его и подтверждать.
if canCurrentManageOU(toOU) then
toOU.moveHere userMove.ADSPath, vbNullString
transferUserTo = "User moved"
exit function
end if
Если же прав в целевом подразделении недостаточно, используется метод enqueueHere. Который ставит перемещаемый объект во входящую очередь организационной единицы.
enqueueHere userMove, fromOU, toOU, comment
transferUserTo = "User enqueued"
end function
Функция получает практически те же параметры, что и предыдущая, за исключением того, что целевая организационная единица теперь объект, а не путь к объекту, как раньше. Причина такой замены проста: к моменту вызова функции уже существует объект целевого подразделения. Поэтому лучше и дальше использовать его, чем плодить лишние сущности.
Следующий метод предельно прост. Он создает экземпляр класса команды и инициализирует его поля. Затем перемещаемый объект пользователя помещается внутрь команды. Команда сохраняется в организационной единице, откуда производится перемещение.
Листинг 5. Создание команды перемещения пользователя
function enqueueHere(whomObject, fromOU, toOU, comment)
dim cmd
set cmd = fromOU.create(START_MOVE_COMMAND_CLASS, "CN=cmd_" & START_MOVE_COMMAND & "_" & whomObject.samAccountName)
cmd.userMoveID = START_MOVE_COMMAND
cmd.userMoveExecutor = "LDAP://" & info.userName
cmd.userMoveFrom = fromOU.ADSPath
cmd.userMoveWho = "LDAP://" & info.userName
cmd.userMoveComment = comment
cmd.userMoveWhen = toUTC(now)
cmd.userMoveDisabled = whomObject.accountDisabled
cmd.userMoveTo = toOU.ADSPath
cmd.userMoveTarget = whomObject.ADSPath
cmd.setInfo
whomObject.accountDisabled = true
whomObject.setInfo
cmd.moveHere whomObject.ADSPath, vbNullString
end function
Формирование команды подтверждения перемещения еще проще, так как здесь достаточно проинициализировать основные поля (исполнитель, тип команды, объект) и сохранить ее в организационной единице, куда осуществляется перевод.
Листинг 6. Создание команды подтверждения перемещения
function accept(whomPath)
dim cmd, ou
set ou = getObject(getAncestor(whomPath, 3))
set cmd = ou.create(COMMAND_CLASS, "CN=cmd_" & ACCEPT_COMMAND & "_" & getObject(whomPath).samAccountName)
cmd.userMoveID = ACCEPT_COMMAND
cmd.userMoveExecutor = "LDAP://" & info.userName
cmd.userMoveTarget = whomPath
cmd.setInfo
end function
Команда отказа формируется аналогично, поэтому полный текст ее инициализации приводить не буду. Называется соответствующий метод deny. В качестве параметров ему передается путь к объекту команды и комментарий, содержащий причину отказа. То же касается и создания команды отката. Метод ее создания называется rollback и принимает один параметр – объект воздействия.
Путь к родительскому контейнеру
В ходе описания реализации использовалась функция getParent, которая возвращает путь к родительскому контейнеру на основе пути дочернего объекта.
В чем заключается логика работы. Объекты в Active Directory упорядочены иерархически. На самом верху иерархии находится домен. Например, для домена с именем marclar.ua путь будет выглядеть так: LDAP://DC=marklar, DC=ua. Если в рамках домена существует подразделение с именем department, которое в свою очередь содержит сотрудника ivanov, то путь к объекту пользователя будет LDAP://DN=ivanov,OU=department,DC=marklar,DC=ua. То есть путь указывается от листового элемента дерева к корню. Для пользователей используется идентификатор DN (Distinguished Name или отличительное имя), для организационных единиц – OU (Organizational Unit), для элементов названия домена – DC (Domain Component). Получение пути к родительскому контейнеру заключается в отбрасывании от текущего значения первого элемента (в нашем примере это подстрока DN=ivanov).
Ранее я использовал примитивную версию этой функции [4], которая, однако, вполне пригодна и для этой надстройки. Она находит в пути первую запятую, вырезает подстроку после нее и добавляет в начало название протокола.
Листинг 7. Определение родительского подразделения без использования регулярных выражений
function getParent(path)
getParent = “LDAP://” & right(path, len(path) - instr(1, path, ",", vbTextCompare))
end function
Рассмотрим, как то же самое можно сделать с помощью регулярных выражений. Регулярное выражение позволяет задать правило, по которому можно опознать часть строки. Для этого используется ряд специальных символов. Рассмотрим только элементы, необходимые для понимания материалов статьи. Дополнительную информацию по регулярным выражениям можно без труда найти в MSDN [5].
Логика их использования напоминает маски для файлов, где знак вопроса обозначает один любой символ, а звездочка – любое количество произвольных символов. Регулярные выражения несколько сложнее. Рассмотрим необходимые специальные символы (см. таблицу 1).
Таблица 1. Специальные символы
Символ
|
Описание
|
[]
|
Несколько символов в квадратных скобках. Обозначает один любой из этих символов. Например [ab] обозначает a или b
|
^
|
Отрицание. То есть [^ab] обозначает любой символ, кроме a и b
|
()
|
Круглые скобки используются для группировки условий. Затем есть возможность получения частей строк, соответствующих каждой группе. То есть если требуется выделить несколько элементов строки, следует описание каждого из них взять в скобки. Если нужно просто выделить часть регулярного выражения без необходимости получения подстрок, нужно записать его в следующем виде: (?:выражение)
|
\
|
Обратная косая черта используется для экранирования спецсимволов. Например, если требуется указать круглую скобку именно как символ, следует записать так: \(
|
|
|
Или. Например abc|cde обозначает «подстрока abc или cde»
|
*
|
Обозначает, что выражение, стоящее до звездочки, может повториться 0 и более раз. Например, a* обозначает любое количество идущих подряд букв а (в том числе ни одной).
|
?
|
Говорит о том, что идущее перед ним регулярное выражение может повториться ноль или один раз
|
.
|
Точка. Любой символ, кроме перевода строки
|
Итак, напишем регулярное выражение для выделения пути родительского контейнера. Путь начинается с указания протокола LDAP://, если этой приставки нет, то соответствующая строка называется «Отличительное имя» (Distinguished Name). Чтобы наша функция работала с обоими типами строк, укажем приставку как необязательный элемент:
(?:LDAP\:\\\\)?
С помощью «?:» мы сообщаем анализатору, что строка, соответствующая этому выражению, нам не понадобится, затем следуют символы «:\\». Поскольку каждый из них является еще и специальным, все они экранированы с помощью обратной косой черты. В конце идет знак вопроса, т.е. эта подстрока может встретиться в пути один (ADSPath) или ноль (Distinguished Name) раз.
Далее описываем первый элемент пути:
(?:(?:CN|OU)=[^,]*,)?
Здесь также везде используем группировку без захвата (?:) результата. Значит это буквально следующее. Подстрока, которая начинается с CN или OU, затем следует знак равенства, потом любое количество произвольных букв, кроме запятой, в конце находится запятая. Данный элемент может встретиться в пути один раз или не встретиться вовсе.
Ну и заканчивается выражение остальными символами.
(.*)
То есть любое количество любых символов. Здесь используются просто круглые скобки, потому что эта подстрока нас как раз и интересует.
Перейдем к реализации. Передается функции исходный путь к объекту:
function getParent(path)
dim matches
Сначала задаем шаблон регулярного выражения.
re.pattern = "(?:LDAP\:\/\/)?(?:(?:CN|OU)=[^,]*,)?(.*)"
Затем происходит поиск соответствий.
set matches = re.execute(path)
В конце извлекаем результат. В нашем случае это подстрока, соответствующая выражению (.*). Она и является путем к родительскому контейнеру.
getParent = "LDAP://" & matches(0).submatches(0)
end function
Используя этот подход, реализуем еще одну полезную функцию. Перевода пути к объекту в удобочитаемый для простого пользователя вид. Организация Active Directory подобна файловой системе с ее каталогами и вложенными файлами. В то же время путь здесь задается как бы «наизнанку», начиная не из корня иерархии, а от конкретного элемента.
Напишем функцию, которая представляет путь: LDAP://DN=ivanov,OU=department,DC=marklar,DC=ua в виде Marklar.ua/department/ivanov.
Функции передается путь к объекту в стиле Active Directory:
function ADSPath2Readable(path)
dim matches, i, splitter
Сначала выделяем доменное имя. Оно может состоять из одного и более доменных компонентов через запятую, каждая часть имеет вид DC=<имя доменного компонента (ИДК)>.
re.pattern = "DC=([^,]*)"
set matches = re.execute(path)
ADSPath2Readable = ""
for i = 0 to matches.count – 1
Все совпадения затем собираются в строку вида <ИДК>.<ИДК>…<ИДК>
if i = 0 then
splitter = ""
else
splitter = "."
end if
ADSPath2Readable = ADSPath2Readable & splitter & matches(i).submatches(0)
next
С помощью следующего регулярного выражения выделяются остальные элементы пути – организационные единицы (OU) и остальные объекты (DN). В отличие от доменных компонентов эти элементы требуется собирать в строку в обратном порядке, поэтому массив совпадений перебирается с конца. Между названиями добавляется прямая наклонная линия. Здесь все равно, какой символ использовать, поскольку единственное приложение данной функции – наглядное представление пути.
re.pattern = "(?:CN|OU)=([^,]*)"
set matches = re.execute(path)
for i = matches.count - 1 to 0 step -1
ADSPath2Readable = ADSPath2Readable & "/" & matches(i).submatches(0)
next
end function
Основной класс надстройки
Наконец, можно перечислить все открытые методы основного класса надстройки. Все они были реализованы ранее (см. таблицу 2).
Таблица 2. Интерфейс класса UserMove.Engine
Метод
|
Описание
|
dispatchCommand
|
Обработчик команд менеджеров по персоналу
|
canCurrentManagePath
|
Определяет, имеет ли текущий пользователь достаточное количество прав для управления организационной единицей
|
canHeManagePath
|
Определяет, имеет ли заданный пользователь достаточное количество прав для управления организационной единицей
|
delegateOU
|
Предоставление заданному пользователю прав для управления организационной единицей
|
undelegateOU
|
Лишения пользователя прав управления организационной единицей
|
move
|
Создать команду начала перемещения пользователя
|
accept
|
Создать команду подтверждения перемещения пользователя
|
Deny
|
Создать команду отказа в перемещении пользователя
|
Rollback
|
Создать команду отмены перемещения пользователя
|
getParent
|
Получить путь к родительскому контейнеру
|
ADSPath2Readable
|
Преобразовать ADSI путь к объекту Active Directory в удобочитаемый вид
|
toUTC
|
Перевести местное время во время по Гринвичу
|
fromUTC
|
Получить из времени по Гринвичу местное время
|
Когда класс создан, нужно не забыть зарегистрировать его в системе с помощью контекстного меню проводника «Установить (Register)». Можно сделать то же самое из командной строки:
- Regsvr32 UserMove.Engine.wsc – регистрация компонента;
- Regsvr32 /u UserMove.Engine.wsc – его удаление.
Создание общих ресурсов домена
В конце вкратце хотелось бы рассмотреть вопрос размещения исходных файлов надстройки. Для этого удобнее всего будет воспользоваться механизмом DFS (Distributed File System) [6]. С его помощью можно обращаться к файлам, полностью игнорируя их физическое расположение. То есть, например, для домена marklar.ua можно пользоваться строкой следующего вида для обращения к файлам: \\marklar.ua\files\file.txt. При этом становится не важно, на каком компьютере хранится указанный файл (на самом деле он может быть расположен сразу на нескольких машинах для повышения надежности).
Для начала нужно создать корень DFS. Физически это должна быть обычная разделяемая (shared) папка. Пусть это будет UserMoveSupport. Корень создается с использованием специальной оснастки Distributed File System (см. рис. 1).
Рисунок 1. Создание корня DFS
После выполнения команды «New Root…» будет запущен мастер создания корневого элемента распределенной файловой системы. Тип корня должен быть «Domain Root». Дальше указываются название домена и сервера. Затем вводится имя корня – назовем его UserMoveSupport (см. рис. 2).
Рисунок 2. Имя корня
Когда создание корневого элемента будет завершено, нужно создать так называемые ссылки (команда «New link…», вызываемая из контекстного меню корня). Фактически нужно указать разделяемые (shared) папки, доступ к которым мы хотим обеспечить с помощью DFS (см. рис. 3).
Рисунок 3. Создание ссылки
Для окончательного создания ссылки нужно указать, имя, по которому в дальнейшем нужно будет к ней обращаться (Link name), и путь к разделяемому ресурсу (Path to target (shared folder)). Обратите внимание, что эти имена не обязательно должны совпадать (см. рис. 4).
Рисунок 4. Настройки новой ссылки
Все, теперь к папке можно обращаться без привязки к конкретной рабочей станции, указывая только имя домена.
"\\marklar.ua\UserMoveSupport\exec\"
Организация работы с приложениями
Для надстройки будет создан ряд вспомогательных hta-модулей, которые вызываются из контекстного меню оснастки Active Directory Users and Computers. HTA (HyperText Application) – особый вид приложений с графическим интерфейсом, основанным на HTML, подробно их создание будет рассмотрено в следующей части статьи. Рассмотрим общий механизм реализации такого подхода. В результате должно получиться так, как изображено на рис. 5 (для наглядности контекстные меню надстройки начинаются с приставки UserMove:).
Рисунок 5. Контекстные меню надстройки
Следующая функция добавляет новое контекстное меня в оснастку. Для этого нужно создать специальную запись в Active Directory.
function installMenu(className, id, name, scriptPath, action, locale)
Сначала подключаемся к корневому элементу Active Directory:
Set root= GetObject("LDAP://rootDSE")
Используя корневой элемент, получаем указатель на ветвь с настройками, которая называется configuration NamingContext, далее этот объект будет использоваться при формировании полного пути (ADSPath) необходимой конкретной настройки.
sConfig = root.Get("configurationNamingContext")
В полученной ветви настройки нас интересует раздел DisplaySpecifiers, в котором содержится список элементов – локалей (каждая локализация содержит настройки для определенного языка). В локали вложены элементы с настройками для каждого класса, например, для класса user соответствующий элемент будет называться user-Display. В этот элемент и помещаются специальным образом подготовленные настройки.
sPath = "LDAP://cn=" & className & "-Display,cn=" & locale & ",cn=DisplaySpecifiers," & sConfig
Set obj= GetObject(sPath)
Сначала нужно сформировать строку, состоящую из трех элементов, разделенных запятыми: идентификатора id (произвольное число, позволяющее упорядочивать добавляемые элементы меню, – разделы с меньшими номерами будут предшествовать разделам с большими), текста меню и пути к сценарию, который необходимо будет выполнить при выборе этого раздела.
sValue = id & "," & name & "," & scriptPath
Затем эта строка преобразуется в трехэлементный массив:
vValue = Array(sValue)
Наконец редактируется запись с надстройками. Изменить нужно ее свойство под названием adminContext Menu. Это массив, для которого мы будем использовать две операции (вид операции задается параметром action метода PutEx). Для добавления элемента используется константа ADS_PROPERTY_APPEND (3), для удаления ADS_PROPERTY_DELETE (4). Константы придется определить вручную, подсмотрев их значения в MSDN [4].
obj.PutEx action, "adminContextMenu", vValue
obj.SetInfo
end function
Рассмотрим параметры, передаваемые функции:
- className. Имя класса Active Directory, к которому будет привязываться создаваемый элемент контекстного меню. В этой надстройке – user или organizationalUnit.
- id. Числовой идентификатор, определяющий позицию пункта меню.
- name. Текст меню.
Путь к сценарию, который необходимо выполнить при выборе элемента меню.
- action. Константа, определяющая, будет элемент меню добавлен или удален.
- locale. Локализация. Задается числом в шестнадцатеричной системы счисления. Для английского языка это 409, для русского – 419.
Необходимые настройки определим в виде констант.
Листинг 8. Константы инициализации надстройки
Const ADS_PROPERTY_APPEND = 3
Const ADS_PROPERTY_DELETE = 4
Const LOCALE_ENGLISH = "409"
Const LOCALE_RUSSIAN = "419"
Const prefix = "UserMove: "
Const path = "\\marklar.ua\UserMoveSupport\exec\"
Также создадим ряд функций-оберток:
- installOUMenu – установка раздела меню для организационной единицы;
- installUserMenu – установка раздела меню для пользователя;
- uninstallOUMenu – удаление раздела меню организационной единицы;
- uninstallUserMenu – удаление раздела меню пользователя.
Рассмотрим только одну из этих подпрограмм, все остальные отличаются только передаваемыми в installMenu константами.
Листинг 9. Создание дополнительных пунктов меню
function installOUMenu(id, name, scriptPath, locale)
installMenu "organizationalUnit", id, name, scriptPath, ADS_PROPERTY_APPEND, locale
end function
Всего будет три приложения. Одно будет привязано к объекту пользователя и позволит начать операцию его перемещения (приложение enqueue.hta). И два нужно прикрепить к организационным единицам: просмотр списков входящих (incoming.hta) и исходящих (outcoming.hta) пользователей.
Листинг 10. Создание пунктов меню для английской локали
function installEN
installUserMenu 100, prefix & "Start", path & "enqueue.hta", LOCALE_ENGLISH
installOUMenu 100, prefix & "Outcoming", path & "outcoming.hta", LOCALE_ENGLISH
installOUMenu 110, prefix & "Incoming", path & "incoming.hta", LOCALE_ENGLISH
end Function
Функция installEN устанавливает все необходимые пункты меню для английской локализации. Аналогично создается подпрограмма для удаления этих пунктов uninstallEN а также соответствующие процедуры для русского языка uninstallRU и installRU.
Работу с двумя локализациями необходимо обеспечить, поскольку в противном случае пользователи русских версий Windows новых пунктов меню просто не увидят.
Заключение
Итак, создание основного класса надстройки и дополнительных сценариев завершено. Остается разработать графический интерфейс для ее комфортного использования. Он также будет создаваться средствами WSH. Этому и будет посвящена последняя часть статьи.
- Андросов В. Делегируем права на перемещение учетных записей пользователей в Active Directory. Часть 1. Постановка задачи. //Системный администратор, №3, 2008 г. – С. 16-21.
- Андросов В. Делегируем права на перемещение учетных записей пользователей в Active Directory. Часть 2. Реализация основных функций. //Системный администратор, №4, 2008 г. – С. 24-30.
- Андросов В. Делегируем права на перемещение учетных записей пользователей в Active Directory. Часть 3. Реализуем необходимые операции. //Системный администратор, №5, 2008 г. – С. 30-37.
- Андросов В. Реализуем нестандартные правила управления доступом на основе архитектуры организации в Windows Server 2003. //Системный администратор, №10, 2007 г. – С. 48-58.
- msdn.microsoft.com.
- Чарли Рассел, Шарон Кроуфорд, Джейсон Джеренд. Windows server 2003 +SP1 и R2. Справочник администратора. – М.: Издательство «ЭКОМ», 2006 г. – 1424 с.