Часть 2
Иван Коробко
В предыдущей статье был описан процесс установки и настройки сетевого
принтера в домене Windows. Эта статья посвящена созданию сценария регистрации
пользователей в сети, который будет автоматически управлять подключением и
отключением сетевых принтеров.
Сценарий регистрации пользователей в сети
Основной задачей скрипта является автоматизация процессов, связанных с
подключением рабочих станций к сети, уменьшения затрат на администрирование и
конфигурирование рабочих станций.
Перечислим некоторые задачи,
которые могут быть решены с помощью сценария загрузки:
n Инвентаризация. Включает в себя сбор информации
о регистрирующемся в сети пользователе, рабочей станции и формирование файла
отчета.
n Автоматическое подключение сетевых ресурсов:
принтеров и дисков.
n Автоматическое конфигурирование рабочих
станций.
n Обеспечение интерактивности работы скрипта. В
ходе выполнения скрипта на экране отображается соответствующая информация.
После его выполнения пользователь может ознакомиться с подключенными к нему
ресурсами, информацией о его рабочей станции и т. д.
Существует несколько языков, предназначенных для
создания сценариев загрузки. Среди них стандартными являются сценарии на базе
командной строки (файлы с расширением BAT, PIF), Windows Script Host (WSH), Microsoft
Java Script (JScript), Microsoft Visual Basic Script Edition (VBScript).
Существуют также языки, специально разработанные для создания скриптов, такие
как KIXtart, AutoIT, CLRScript, WinBatch.
Из всех этих языков рекомендуется
остановить свой выбор на языке KIXtart (http://kixtart.org),
поскольку, обладая огромными возможностями, он распространяется бесплатно. Это
интерпретируемый язык программирования, разработанный в 1991 году для создания
сценариев загрузки. Простота, скорость и отсутствие конкурентов быстро сделали
его популярным среди администраторов. KIXtart является бесплатным и
поставляется вместе с Microsoft Resoure Kit. В настоящее время используется KIXtart
ver 4.2х, который поддерживается Microsoft Windows Server 2003, XP, 2000, NT
3.x/4.x, 95, 98, Millennium. KIXtart 4.21 поддерживает 48 команд, 56
макросов-функций, 100 функций и обладает следующим функционалом:
n вывод информации в виде диалоговых сообщений;
n подключение сетевых ресурсов;
n чтение информации из входного потока;
n расширенная поддержка редактирования реестра;
n поддержка INI-файлов;
n расширенная поддержка операций со строками и
массивами;
n сбор информации о пользователе и рабочей
станции;
n поддержка OLE-объектов;
n операции с файлами и каталогами;
n создание ярлыков Windows.
Необходимо отметить, что сценарии, созданные на VBScript,
Jscript, могут быть легко переписаны под KIXtart.
В данной статье рассмотрен лишь небольшой фрагмент
скрипта, обеспечивающий автоматизированное управление сетевыми принтерами.
Рассмотрим основные принципы его работы.
Соглашение об именах
Имя должно содержать как можно больше информации о принтере, при этом быть
удобным для использования. На рабочих станциях под управлением операционной
системы Windows пользователь имеет дело с двумя именами – именем принтера и его
сетевым именем. Имя принтера – это имя, назначаемое принтеру во время
установки. Его длина ограничивается 220 символами. Сетевое имя назначается
принтеру для использования устройства в сети. Максимальная длина сетевого имени
составляет 80 символов, хотя его не рекомендуется делать длиннее 8 символов для
обеспечения совместимости с клиентами MS-DOS и Windows 3.x. Существует еще одно
ограничение: некоторые приложения не могут работать с принтерами, у которых
полное составное имя (имя компьютера, объединенное с сетевым именем принтера по
шаблону \\Server\ ShareName) длиннее 31 символа.
Чаще всего имя, назначаемое принтеру,
представляет собой реальное имя принтера с порядковым номером, если есть
несколько принтеров одинаковой модели, например, HP LaserJet 1200 (1), HP LaserJet
2300 (2). Такой способ именования рекомендуется использовать в небольших
организациях. В крупных корпорациях принцип именования принтеров может быть
другим. Например, названия могут быть даны по именам отделов и офисов, где
территориально находится принтер, скажем, «Краснодар ОКС» или «Ростов АТС-34».
Сетевое имя, как отмечено ранее, должно быть
более коротким, но при этом не должно терять смысловой нагрузки. Оно чаще всего
представляет собой общую характеристику принтера, например HP1200_1, HP2300_2.
Предварительная настройка
принтера и AD
Работа сценария строится на анализе и обработке данных, содержащихся в Active
Directory и пользовательском реестре. На основе полученных данных
осуществляется подключение и отключение принтера в зависимости от членства
пользователя в соответствующих группах безопасности. Для того чтобы сценарий
мог считывать и сопоставлять полученные данные из Active Directory, необходимо
одновременное выполнение нескольких условий.
Первое условие – названия принтеров, групп
безопасности должны удовлетворять соглашению об именах.
Второе – сетевые принтеры, подключением и
отключением которых должен управлять скрипт, должны быть опубликованы в Active Directory.
Третье – названия групп безопасности должны
строиться в соответствии со следующим шаблоном: название одной из групп, члены
которой могут только выводить задания на печать, образуется добавлением к
сетевому имени принтера через дефис слова Print, например, «HP2300_1 – Print».
Название другой группы строится аналогично, с той разницей, что слово «Print» заменяют
на словосочетание «Print Managers». Члены этой группы могут управлять очередью
печати и принтером. Таким образом, принтеру с сетевым именем «HP1200_1»
соответствуют следующие названия групп: имя первой группы «HP1200_1 – Print»,
второй «HP1200_1 – Print Managers» (см. рис. 1).
Четвертое – должны быть определены параметры
безопасности принтера. В свойствах принтера (см. рис. 2) на сервере печати во
вкладке «Security» (безопасность) должна быть удалена группа «Everyone» (в
противном случае скрипт будет подключать этот принтер всем пользователям сети)
и добавлены две группы безопасности, соответствующие данному принтеру:
«HP1200_1 – Print» и «HP1200_1 – Print Managers». Для группы «HP1200_1 – Print»
должен быть установлен в разделе «Permissions» (разрешения) флажок напротив
свойства «Print» (см. рис. 2), а для группы «HP1200_1 – Print Managers» –
флажки напротив «Print» (печать) и «Manage Documents» (управление документами).
Ставить флажок напротив «Manage Printers» не рекомендуется, поскольку
управление принтерами подразумевает возможность изменять настройки принтера,
удалять его. По мнению автора, такими привилегиями может обладать только
системный администратор.

Рисунок 1

Рисунок 2
Итак, работа сценария строится на анализе данных,
содержащихся в Active Directory и пользовательском реестре. Его работу можно
разбить на три этапа.
На первом этапе формируется список принтеров,
которые должны быть подключены пользователю. На втором этапе – список сетевых
принтеров, уже установленных на рабочей станции пользователя.Наконец, на
третьем – осуществляется приведение этих списков в соответствие.
Этап 1. Формирование списка
принтеров, которые необходимо подключить пользователю
Определение имени текущего
домена
Имя текущего домена определяется с помощью функции GetObject(). Используя
функцию GetObject(), осуществляется чтение корня пространства имен, в данном
случае текущего домена.
Пример определения текущего домена:
$rootDSE_ = GetObject("LDAP://RootDSE")
$domain_ =
"LDAP://" + $rootDSE_.Get("defaultNamingContext")
Переменная domain_ имеет вид «dc=microsoft,dc=com»,
если домен «microsoft.com».
Имя текущего домена, полученного с помощью
провайдера WinNT, нельзя использовать, поскольку с помощью него можно получить
только сокращенное имя домена (в данном случае «microsoft»). В том случае если
все же указано сокращенное имя домена в строке с SQL-запросом, то при
выполнении скрипта произойдет ошибка. В сообщении о ней будет сказано, что по
указанному пути база не обнаружена, поэтому установить с ней соединение
невозможно.
Построение запроса SQL
Запрос SQL используется для осуществления процедуры поиска объектов при
заданном типе объекта. В общем случае запрос SQL выглядит следующим образом:
SELECT поле_1, поле_2, …, поле_n
FROM “LDAP://dc=домен_1,dc=домен_2…,domen_n” WHERE objectClass=’тип_объекта’
В разделе SELECT указываются поля, по которым
идет выборка. Поля перечисляются через запятую, «пробелы» после запятой
обязательны. Полный список полей объектов AD можно получить с помощью утилиты
ADSI Edit, которая размещается в дистрибутиве Microsoft Windows 2000 в
директории /Support/Tools (см. статью «Программное управление ADSI: LDAP» в
журнале «Системный Администратор», №3(16) март 2004 г.).
В разделе FROM указывается путь к объекту. В
данном случае известен только домен. При описании данного раздела пробелы не
допускаются.
В разделе WHERE указывается тип объекта, к
которому адресован запрос. Данное поле является фильтром. Провайдер LDAP
поддерживает несколько типов объектов, которые в запросе SQL определяются
переменной objectClass: PrintQueue – массив принтеров, опубликованных в AD; Group
– группы, созданные в AD; User – пользователи, созданные в AD; Computer –
массив компьютеров, зарегистрированных в AD. Пример использования запроса SQL
см. в разделе «Поиск опубликованных принтеров в AD».
Поиск опубликованных принтеров
в AD
Поиск объектов в Active Directory с помощью провайдера LDAP реализуется
через ADODB-соединение. После создания соединения формируется SQL-запрос и
осуществляется поиск по заданным критериям. Результатом поиска является массив,
который содержит значения полей, которые указаны в параметре SELECT
SQL-запроса. Затем происходит вывод данных на экран. В приведенном примере
осуществляется поиск всех опубликованных принтеров в текущем домене и вывод на
экран названия принтера, его сетевого имени (ShareName):
$strADSQuery = "SELECT shortservername,
portname,
servername, printername, printsharename,
location, description FROM '" +$domain_+"' WHERE objectClass='printQueue'"
$objConnection = CreateObject("ADODB.Connection")
$objCommand = CreateObject("ADODB.Command")
$objConnection.CommandTimeout
= 120
$objConnection.Provider =
"ADsDSOObject"
$objConnection.Open ("Active
Directory Provider")
$objCommand.ActiveConnection
= $objConnection
$objCommand.CommandText = $strADSQuery
$st = $objCommand.Execute
$st.Movefirst
$i=0
Do
$server_enum=""
$name_enum=""
$shares_enum=""
$description_enum=""
$server_enum = $St.Fields("shortservername").Value
$name_enum = $St.Fields("printername").Value
$shares=$St.Fields("printsharename").Value
for each $share in
$shares
$shares_enum = $shares_enum
+ $share
next
$descrs=$St.Fields("description").Value
for each $desc in
$descrs
$description_enum
= $description_enum + $desc
Next
$st.MoveNext
$temp="Название
принтера: " & $name_enum & chr(13) & "Путь к принтеру:
" & "\\" & $server_enum & "\" & $shares_enum
& chr(13) & "Описание: " & $description_enum.
MessageBox($temp,"Характеристики
принтера",0,0)
$temp=""
Until $st.EOF
В Active Directory объектом класса printQueue
является принтер. Этот объект имеет свойства, значение которых может быть двух
типов: строкой и массивом. В приведенном примере поле, содержащее название
принтера, является строковой переменной, а сетевое имя принтера – массивом.
Ниже приведена таблица, содержащая названия и описания
часто используемых полей, соответствующий им тип и формат данных:
Таблица 1
|
Поле
|
Описание
|
Тип
|
Пример
|
|
Description
|
Описание принтера
|
Array
|
Принтер формата А4
|
|
Location
|
Физическое место
размещения принтера
|
String
|
2 эт., 10 комн.
|
|
PrinterName
|
То же, что и Name
|
String
|
PrinterName
|
|
PrintShareName
|
Имя принтера для
подключение
|
Array
|
Printer
|
|
ServerName
|
Полное имя сервера,
к которому подключен принтер
|
String
|
Server.Domain.Ru
|
|
ShortServerName
|
Краткое имя cервера
|
String
|
Server
|
Формирование массива
После того как в Active Directory найден очередной опубликованный принтер и
прочитаны его свойства, для него формируется UNC-путь (\\server\sharename).
Затем осуществляется попытка подключить пользователю принтер и считывать код
функции, производящей подключение.
Если функция подключения возвращает код ошибки 0
(подключение к принтеру прошло успешно), то пользователь является членом одной
из двух групп безопасности, перечисленных в свойствах принтера на сервере
печати. Для тех принтеров, на которые пользователь имеет право печатать (как
минимум), формируется массив, например $access_array[$i]. Формат элементов
массива следующий: «,,server,printername», где server – короткое имя сервера, printername
– локальное имя принтера.
$path_enum_connect =
"\\" + $server_enum + "\" + $shares_enum
$connect_flag = addprinterconnection(
$path_enum_connect )
if $connect_flag=0
$path_full
=",," + $server_enum + "," + $name_enum
$print_sysinfo=$print_sysinfo+$shares_enum+":"+$description_enum+chr(13)
$access_array[$i]
= lcase($path_full)
$i=$i+1
Endif
Этап 2. Формирование списка
сетевых принтеров, подключенных пользователю
Процесс определения подключенных пользователю сетевых принтеров основан на
анализе ветви HKCU локального реестра (см. рис.3).

Рисунок 3
С помощью функции ENUM осуществляется чтение
названий папок, содержащих в себе короткое имя сервера и полное имя принтера.
На основе полученной информации формируется массив, элементами которого
являются строки, имеющие следующий формат: «,,server,printername», где server –
короткое имя сервера, printername – локальное имя принтера. Для удобства
сравнения обоих массивов (подключенных принтеров и принтеров, на которые
пользователь имеет права) необходимо, чтобы форматы элементов массивов
совпадали. Формат элементов продиктован особенностью построения реестра Windows
2000 (см. рис. 3).
$Index=0
DO
$connected_array[$index]= lcase(ENUMKEY("HKEY_CURRENT_USER\Printers\Connections\",
$Index))
$Index = $Index + 1
UNTIL Len($Group) =0
Необходимо отметить, что после формирования
второго массива между ними соблюдаются следующее неравенство: М2 і М1, где М2 –
массив, элементами которого являются названия подключенных принтеров, M1 –
принтеров, на которые пользователь имеет права. На третьем, заключительном,
этапе добиваются выполнения следующего условия: М1 = М2.
Этап 3. Приведение созданных
списков принтеров в соответствие
Сопоставление массивов М1 и М2 осуществляется с помощью функции ASCAN. В том
случае если функция возвращает значение -1, то элемент, найденный в одном
массиве, не является элементом другого. Поэтому принтер, соответствующий этому
элементу, должен быть отключен.
for $i=0 to ubound($connected_array)
$flag_p=0
$flag_p=Ascan($access_array,$connected_array[$i])
if $flag_p=-1
………..
endif
next
Удаление принтера осуществляется с помощью
соответствующей функции, параметром которой является UNC-путь принтера. Для
того чтобы сформировать этот путь, осуществляется анализ ветви HKLM (см. рис.
4):
if $flag_p=-1
$group=$connected_array[$i]
$name_=right($group, len($group)-instrrev($group,","))
$server_=right(left($group,len($group)-len($name_)-1),
len(left($group,len($group)-len($name_)-1))-2)
$share_=readvalue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\Print\Providers\LanMan Print Services\Servers\"+$server_+"\Printers\
"+$name_,"Share Name")
$disconnect_
="\\"+$server_+"\"+$share_
$r=DelPrinterConnection( $disconnect_
)
endif
Полный синтаксис скрипта приведен в «Приложении
1».

Рисунок 4
Внедрение скрипта в эксплуатацию
Скрипт выполняется каждый раз при регистрации пользователя в сети, если он
указан в разделе Profile свойствах пользователя (см. рис. 5) службы Active Directory:
Users and Computers.

Рисунок 5
Описанный скрипт не будет работать под Windows 9x,
поскольку Windows 2k и Windows 9x обладают различными системами печати. Однако,
поскольку данный скрипт является частью большого скрипта, рассмотрим случай, в
котором в сети присутствуют рабочие станции под управлением и Windows 9x, и Windows
2k. Следует отметить, что для работы KIXtart под Windows 9х необходимо наличие
нескольких файлов на жестком диске рабочей станции в каталоге Windows\System.
Для автоматизации установки KIXtart
на рабочих станциях домена предлагается следующее: в папку Netlogon поместить
файлы:
n KIX32.EXE;
n SCRIPT.KIX;
n START.BAT;
n Подкаталог Win9x, содержит файлы KX16.DLL и
KX32.DLL.
В ходе выполнения файла START.BAT определяется
тип операционной системы, установленной на рабочей станции, и запускает скрипт.
В зависимости от версии ОС происходит копирование файлов, необходимых для
поддержки KIX этой операционной системой.
Листинг файла start.bat
@ECHO OFF
if c:\%os%==c:\ goto win9x
if not c:\%os%==c:\ goto winnt
:winnt
start /wait Kix32.exe Script.kix
goto kix
:win9x
copy %0\..\win9x\*.dll
c:\windows\system /y
%0\..\Kix32.exe
%0\..\Script.kix
goto kix
:kix
@echo End Of Batch File
Пояснения к синтаксису файла
START.BAT:
n Принцип определения операционной системы
основан на том, что в Windows 9x отсутствует переменная окружения %os%; в Windows
2k переменная %os%=WindowsNT.
n С помощью строки «start /wait Kix32.exe
Script.kix2 добиваются последовательной загрузки – сначала скрипт, затем
рабочий стол и т. д., а не одновременной. То есть во время выполнения скрипта
многозадачность «отключается».
n Это делается для того, чтобы до окончания
действия скрипта дальнейшая загрузка операционной системы не производилась.
n Для корректной работы Windows 9x в качестве
пути к файлу необходимо указывать «%0\..\filename.ext\». Windows XP не воспринимает
относительного пути «%0/./», поэтому для Windows семейства 2k необходимо
указать только имя файла, который находится в папке Netlogon.
На время выполнения скрипта
необходимо скрыть CMD-панель, в которой выполняется cкрипт, и приостановить
загрузку рабочего стола до окончания всего скрипта. Этого результата добиваются
с помощью групповой политики, распространяющейся на домен («Default Domain Controllers
Policy»). В разделе групповой политики «User Configuration» необходимо
соответственно включить «Run legacy logon script synhronously» (запускать
сценарий загрузки синхронно) и «Run legasy script hidden» (запускать сценарий
скрыто). Для этого необходимо проделать следующее:
n Зарегистрироваться на сервере с помощью учетной
записи, имеющей административные права.
n Загрузить в Active Directory Users and Computers («Start –> Programs –>
Administrative Tools») и войти в свойства контроллера домена (см. рис. 6).

Рисунок 6
n Перейти во вкладку «Group Policy» и загрузить «Default
Dоmain Policy» (см. рис. 7).

Рисунок 7
n В загруженной групповой политике (Default Domain
Policy) необходимо в «User Configuration» (настройках пользователя) войти в «Administrative
Templates» (административные настройки). Там выбрать раздел «System» (система),
вкладку «logon/logoff» (войти/выйти) и включить ранее оговоренные политики (см.
рис. 8).

Рисунок 8
Приложение 1
Листинг сценария загрузки script.kix
$rootDSE_ = GetObject("LDAP://RootDSE")
$domain_ =
"LDAP://" + $rootDSE_.Get("defaultNamingContext")
$strADSQuery = "SELECT shortservername,
portname, servername, printername, printsharename, location, description FROM
'" +$domain_+"' WHERE objectClass='printQueue'"
$objConnection = CreateObject("ADODB.Connection")
$objCommand = CreateObject("ADODB.Command")
$objConnection.CommandTimeout
= 120
$objConnection.Provider =
"ADsDSOObject"
$objConnection.Open ("Active
Directory Provider")
$objCommand.ActiveConnection
= $objConnection
$objCommand.CommandText = $strADSQuery
$st = $objCommand.Execute
$st.Movefirst
dim $access_array[200]
dim $connected_array[200]
$i=0
Do
$server_enum=""
$shares_enum=""
$description_enum=""
$server_enum = $St.Fields("shortservername").Value
$name_enum = $St.Fields("printername").Value
$shares=$St.Fields("printsharename").Value
for each $share in
$shares
$shares_enum = $shares_enum
+ $share
next
$descrs=$St.Fields("description").Value
for each $desc in
$descrs
$description_enum
= $description_enum + $desc
next
$path_enum_connect =
"\\" + $server_enum + "\" + $shares_enum
$connect_flag = addprinterconnection(
$path_enum_connect )
if $connect_flag=0
$path_full
=",," + $server_enum + "," + $name_enum
$print_sysinfo=$print_sysinfo+$shares_enum+":"+$description_enum+chr(13)
$access_array[$i]
= lcase($path_full)
$i=$i+1
endif
$st.MoveNext
Until $st.EOF
$Index=0
DO
$connected_array[$index]= lcase(ENUMKEY("HKEY_CURRENT_USER\Printers\Connections\",
$Index))
$Index = $Index + 1
UNTIL Len($Group) =0
if $i=0
redim PRESERVE $access_array[0]
else
redim PRESERVE $access_array[$i-1]
endif
if $index=1
redim PRESERVE $connected_array[0]
else
redim PRESERVE $connected_array[$index-2]
endif
for $i=0 to ubound($connected_array)
$flag_p=0
$flag_p=Ascan($access_array,$connected_array[$i])
if $flag_p=-1
$group=$connected_array[$i]
$name_=right($group,
len($group)-instrrev($group,","))
$server_=right(left($group,len($group)-len($name_)-1),len(left($group,len($group)-len($name_)-1))-2)
$share_=readvalue("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\Print\Providers\LanMan Print Services\Servers\"+$server_+"\Printers\
"+$name_,"Share Name")
$disconnect_
="\\"+$server_+"\"+$share_
$r=DelPrinterConnection( $disconnect_
)
endif
next
MessageBox("Подключение
сетевых принтеров завершено","",0,0)