В принципе, если с самого начала организации всей этой структуры администратор следит за ее целостностью, ничего особенно неприятного с ней не происходит никогда. Однако бывают случаи, когда разрешения на папки сбиваются вследствие нарушения работы жестких дисков; кроме того, нередки случаи, когда новый администратор получает в наследство от предшественника путаницу с разрешениями доступа, что называется, «исторически сложившуюся», и т. д. Если пользователей (и, соответственно, папок) несколько десятков, то в принципе все можно быстро поправить вручную и спокойно заняться другими делами. Но если пользователей и папок две или три тысячи, то до «других дел» руки дойдут нескоро, при этом существует вероятность, что администратор и вовсе отложит до лучших времен неполадки с правами, решая более актуальные задачи. В общем, как всегда, когда предстоит рутинная работа, возникает желание ее автоматизировать, причем сделать это мы постараемся, задействуя исключительно встроенные в Windows средства, тем более что во многих компаниях использование программного обеспечения независимых поставщиков может быть ограничено.
Исходные условия
Для начала сформулируем исходные условия и определим, что мы хотим иметь в результате и какими средствами будем этого результата добиваться.
Начальные условия — предположим, что у нас есть домен MYCOMPANY, контроллер домена DCONTROLLER и файловый сервер FILESERVER, на диске D: которого располагается папка USERS, а уже в ней находятся папки с «неправильными» разрешениями доступа.
Постановка задачи. Нам предстоит выставить разрешения на саму папку USERS, на все папки, входящие в нее, а также на те папки, которые будут здесь создаваться в дальнейшем. При этом руководствоваться предлагается следующими принципами — к пользовательской папке имеют доступ: сам пользователь, администраторы домена и служба безопасности (группа Security). Если папка принадлежит уже несуществующему пользователю, то доступ к ней ограничивается только администраторами. Иными словами, в первом случае мы хотим получить следующую схему разрешений: Domain Admins — Full Control, SYSTEM — Full Control, Security — Read, CurrentUser — Modify; во втором: Domain Admins — Full Control, SYSTEM — Full Control.
Инструмент — VBScript, доступное средство, использование которого не требует никаких дополнительных усилий, дополнительных процессов установки и т. д.Сразу отметим один важный момент, касающийся самой структуры папок: неважно, назначались ли они пользователям в качестве домашних сразу при создании новых учетных записей или создавались в результате, скажем, политики, перенаправляющей папки My Documents на сервер, они называются соответственно именам пользователей, с которыми те регистрируются в сети. Как результат список папок, находящихся в D:USERS, представляет собой, по сути, список учетных записей домена, т. е. демонстрировать его всем подряд не следует из соображений элементарной безопасности.
В этой связи мы ограничим разрешения рядовых пользователей на папку USERS разрешением Write, лишив их возможности просматривать содержимое корневой папки. Таким образом, результирующий набор разрешений на D:USERS таков: Domain Admins — Full Control, SYSTEM — Full Control, Domain Users — Write, Security — Read, CREATOR OWNER — Modify. Такой подход обеспечивает возможность резервного копирования и правильные разрешения на доступ для будущих пользователей к их индивидуальным папкам. Хочу подчеркнуть, что никоим образом не навязываю именно такой вариант назначения разрешений доступа, если кому-то хочется (или требуется) сделать по-другому. Мы же переходим к следующему шагу.
Алгоритм сценария
А следующий шаг — весьма ответственный. С общей папкой мы разобрались вручную, с индивидуальными папками нам предстоит бороться с использованием «средств малой автоматизации». Для начала необходимо определить, с помощью чего мы будем править разрешения доступа. Варианты возможны разные, мы еще скажем пару слов об этом в конце статьи, пока же возьмем самый простой — еще со времен NT в Windows включена утилита CACLS.EXE, мы же просто будем запускать ее из нашего сценария в соответствии с той логикой, которую мы в него заложим.
Приводить описание утилиты мы, конечно, не будем — просто скажем, что результирующая команда будет выглядеть так:
cacls [имя папки] /t /p [список пользователей и разрешений]Тут есть один нюанс — при запуске из командной строки требуется подтверждение в виде нажатия клавиши Y, что нам, запускающим сценарий на исполнение, недоступно. Вернемся к этому чуть позже, а пока перейдем к логике, в соответствии с которой будем менять разрешения доступа.
Алгоритм сценария — берем папку, проверяем, есть ли в Active Directory учетная запись пользователя с таким именем, если есть, выдаем одни разрешения, если нет — другие (согласно тому заданию, что мы сформулировали в начале), после чего переходим к следующей папке. Для порядка и «красоты» было бы еще неплохо вести файл журнала, в который записывалось бы все, что делается и почему. Теперь, думается, у нас есть все необходимое, чтобы идти дальше, а именно приступить к написанию сценария.
Сценарий действий
Для начала определяем переменные и создаем файл журнала, куда сразу же записываем время начала процесса, (см. фрагмент A листинга 1).
Идем дальше и, как показано во фрагменте B листинга 1, устанавливаем соединение с интересующим нас сервером и указываем корневую папку (в данном случае для упрощения мы напрямую прописываем все имена; чуть позже скажем о том, как сделать эту часть интерактивной).
Теперь нам потребуется создать маленькую функцию, в обязанности которой будет входить проверка наличия конкретного пользователя в Active Directory (см. фрагмент С листинга 1). Конкретного — значит того, чье имя передается функции в качестве аргумента. Запрос адресуется контроллеру домена, чье имя мы опять же вписываем непосредственно в текст сценария. Ну и, наконец, пришло время задействовать наш основной инструмент — CACLS.EXE, оформляя это для удобства в виде подпрограммы (см. фрагмент D листинга 1). Поскольку в зависимости от наличия или отсутствия пользователя нам надо назначать разные наборы разрешений, подпрограмм требуется тоже две — SetPermissions и SetNoUserPermissions. Для обхода упомянутого нюанса с нажатием клавиши Y подставляем соответствующий символ, а по окончании процесса работы над текущей папкой делаем запись в лог-файл, выделяя имя несуществующего пользователя заглавными буквами (просто для наглядности). Тот факт, что мы указываем полное имя ресурса, позволяет нам запускать сценарий с любого компьютера домена.
Остается только проверить имена и назначить разрешения, используя только что созданную функцию и процедуры — см. фрагмент E основного кода листинга 1. Теперь записываем в файл журнала время окончания процесса и закрываем файл, а также в ознаменование победы выводим на экран соответствующую надпись: «Done!»
Сложности именования папок
Теперь давайте посмотрим, какие могут возникнуть затруднения и что можно доделать самостоятельно.
Сложность, надо сказать, была замечена одна — как выяснилось, некоторые администраторы иногда используют в названии учетной записи «неприличные» символы типа «‘» — к примеру, фамилия «Иваньков» записывается как ivan’kov. Работа с папкой в этом случае происходит нормально, а при обращении к Active Directory возникает ошибка «Неверный запрос», и сценарий выдает ошибку и прекращает работу. В подобных случаях можно обойти некорректные имена, слегка усложнив функцию проверки учетной записи пользователя, — будем считать учетные записи с «неправильным» именем существующими, исключив тем самым обращение к контроллеру домена, но оставив процесс назначения разрешений доступа. Делаем в файле журнала запись о неудачном имени учетной записи, и после доработки функция приобретает вид, показанный в листинге 2.
Привносим интерактивность
Поговорку «лучшее — враг хорошего» можно выносить в эпиграф любого сочинения об улучшении какого-либо объекта. Однако в данном случае была предложена весьма простая реализация, и простор для творчества безусловно остается. Вместо того, чтобы вписывать свои названия доменов, серверов и т. д., можно слегка доработать код и получить более универсальное средство. В предложенном варианте (см. листинг 3) администратор должен вводить все имена (домена, контроллера, файлового сервера, корневой папки и лог-файла) непосредственно в процессе работы сценария.
Заключение
Напоследок осталось сказать пару слов о возможных альтернативах предложенному способу. Во-первых, выбранную нами утилиту CACL.EXE можно заменить на имеющийся на сайте Microsoft сценарий XCACLS.VBS, который не требует подтверждения запроса. Однако это, как мне показалось, единственное его достоинство. К тому же вместо одного сценария и встроенной утилиты используется два сценария, что, на наш взгляд, уже не очень удачно. Во-вторых, CACLS.EXE можно не использовать вовсе, работая вместо этого напрямую с атрибутами файлов и папок, отвечающими за разрешения доступа, наследование и т. п. Однако затрагиваемые при этом вопросы лежат вне темы данного материала. По возможности мы вернемся к ним в другой раз.
Дмитрий Хлопцев (dim@col.ru) — системный администратор в компании «Русфинанс».Николай Андрианов (rabalkin@mail.ru, slayer@amdclub.ru) — ведущий системный администратор в компании «Русфинанс», а также администратор ресурса www.amdclub.ru
Листинг 1. Сценарий проверки и назначения разрешений
::Начало фрагмента A
Option Explicit
Dim strComputer, objWMIService, username, intUserName, objWMI, objUsers
Dim fso, fsHandle, MyShell, LogFileName, colSubfolders, objFolder, objComputer
Dim strFolder, strArgument, objShell
LogFileName= «C:userfolders.log»
Set MyShell = CreateObject(«Wscript.Shell»)
Set fso = Wscript.CreateObject(«Scripting.FilesystemObject»)
set fsHandle = fso.OpenTextFile (LogFileName,8,True)
fsHandle.Writeblanklines 1
fsHandle.Writeline «Started at « & Now()
fsHandle.Writeblanklines 1
:: Конец фрагмента А
::Начало фрагмента B
strComputer = «FILESERVER»
Set objWMIService = GetObject(«winmgmts:» _
& «{impersonationLevel=impersonate}!» & strComputer & « ootcimv2»)
Set colSubfolders = objWMIService.ExecQuery _
(«ASSOCIATORS OF {Win32_Directory.Name=’d:users’} « _
& «WHERE AssocClass = Win32_Subdirectory « _
& «ResultRole = PartComponent»)
:: Конец фрагмента B
::Начало фрагмента С
function UserExist(intUserName)
strComputer = « DCONTROLLER «
Set objWMI = GetObject(«winmgmts:» & strComputer & « ootdirectoryLDAP»)
Set objUsers = objWMI.ExecQuery(«SELECT * FROM ds_user where ds_sAMAccountName = « & «‘« & intUserName & «‘ «)
If objUsers.Count = 0 Then
UserExist = 0
Else
UserExist = 1
End If
End function
:: Конец фрагмента С
::Начало фрагмента E
For Each objFolder in colSubfolders
if UserExist(username) = 0 Then
call SetNoUserPermissions(username)
else
call SetPermissions(username)
end if
Next
fsHandle.Writeblanklines 1
fsHandle.Writeline «Completed at « & Now()
fsHandle.close
set MyShell = Nothing
set fso = Nothing
Wscript.Echo «Done!»
:: Конец фрагмента E
::Начало фрагмента D
sub SetPermissions(strFolder)
Set objShell = CreateObject(«Wscript.Shell»)
strArgument = «%COMSPEC% /c Echo Y| cacls FILESERVERd$users» & strFolder & « /t /p BUILTINAdministrators:f
MYCOMPANYSECURITY:c MYCOMPANY» & strFolder & «:c « & chr(34) & «NT AUTHORITYSYSTEM» & chr(34) & «:f»
objShell.Run strArgument, 7, true
fsHandle.Writeline strFolder & « completed at « & Now()
end sub
sub SetNoUserPermissions(strFolder)
Set objShell = CreateObject(«Wscript.Shell»)
strArgument = «%COMSPEC% /c Echo Y| cacls FILESERVERd$users» & strFolder & « /t /p BUILTINAdministrators:f « & chr(34) & «NT AUTHORITYSYSTEM» & chr(34) & «:f»
objShell.Run strArgument, 7, true
fsHandle.Writeline ucase(strFolder) & « completed at « & Now()
end sub
:: Конец фрагмента D
Листинг 2. Доработанная функция проверки
function UserExist(intUserName)
if InStrRev(intUsername,»’») > 0 then
UserExist = 1
fsHandle.Writeline « « & ucase(username) & « — wrong username syntax!!!»
else
strComputer = «DCONTROLLER»
Set objWMI = GetObject(«winmgmts:» & strComputer & « ootdirectoryLDAP»)
Set objUsers = objWMI.ExecQuery(«SELECT * FROM ds_user where ds_sAMAccountName = « & «‘« & intUserName & «‘ «)
If objUsers.Count = 0 Then
UserExist = 0
Else
UserExist = 1
End If
end if
end function
Листинг 3. Первоначальный сценарий с элементами интерактивности
Option Explicit
Dim strComputer, objWMIService, username, intUserName, objWMI, objUsers
Dim fso, fsHandle, MyShell, LogFileName, colSubfolders, objFolder, objComputer
Dim strFolder, strArgument, objShell, strRootFolder
Dim WSHShell, Message, Title, strDC, strDomain
Title = «User Input»
Message = «Enter log file name»
Set WSHShell = WScript.CreateObject(«WScript.Shell»)
LogFileName = InputBox(Message,Title,»c:log.log», 100, 100)
Set MyShell = CreateObject(«Wscript.Shell»)
Set fso = Wscript.CreateObject(«Scripting.FilesystemObject»)
Set fsHandle = fso.OpenTextFile (LogFileName,8,True)
Message = «Enter Domain name»
strDomain = InputBox(Message,Title,»MYCOMPANY», 100, 100)
Message = «Enter DC name»
strDC = InputBox(Message,Title,»DCONTROLLER», 100, 100)
Message = «Enter file server name»
strComputer = InputBox(Message,Title,»FILESERVER», 100, 100)
Message = «Enter root folder without in the end»
strRootFolder = InputBox(Message,Title,»d:users», 100, 100)
fsHandle.Writeline «Started « & Now()
fsHandle.Writeblanklines 1
Set objWMIService = GetObject(«winmgmts:» _
& «{impersonationLevel=impersonate}!» & strComputer & « ootcimv2»)
Set colSubfolders = objWMIService.ExecQuery _
(«ASSOCIATORS OF {Win32_Directory.Name=’» & strRootFolder & «‘} « _
& «WHERE AssocClass = Win32_Subdirectory « _
& «ResultRole = PartComponent»)
For Each objFolder in colSubfolders
username = mid(objFolder.Name,len(strRootFolder)+2)
if UserExist(username) = 0 Then
fsHandle.Writeline « « & ucase(username) & « — no such user. Permissions will be reset.»
call SetNoUserPermissions(username)
else
call SetPermissions(username)
End If
Next
fsHandle.Writeblanklines 1
fsHandle.Writeline «Completed « & Now()
fsHandle.close
set MyShell = Nothing
set fso = Nothing
sub SetPermissions(strFolder)
Set objShell = CreateObject(«Wscript.Shell»)
strArgument = «%COMSPEC% /c Echo Y| cacls » & strComputer & «» & mid(strRootFolder,1,1) & «$» & mid(strRootFolder,3) &
«» & strFolder & « /t /p
BUILTINAdministrators:f « & strDomain & «SECURITY:c « & strDomain & «» & strFolder & «:c « & chr(34) & «NT AUTHORITY
SYSTEM» & chr(34) & «:f»
objShell.Run strArgument, 7, true
fsHandle.Writeline strFolder & « completed at « & Now()
end sub
sub SetNoUserPermissions(strFolder)
Set objShell = CreateObject(«Wscript.Shell»)
strArgument = «%COMSPEC% /c Echo Y| cacls » & strComputer & «» & mid(strRootFolder,1,1) & «$» & mid(strRootFolder,3) &
«» & strFolder & « /t /p
BUILTINAdministrators:f « & strDomain & «SECURITY:c « & chr(34) & «NT AUTHORITYSYSTEM» & chr(34) & «:f»
objShell.Run strArgument, 7, true
fsHandle.Writeline strFolder & « completed at « & Now()
end sub
function UserExist(intUserName)
if InStrRev(intUsername,»’») > 0 then
UserExist = 1
fsHandle.Writeline « « & ucase(username) & « — wrong username syntax!!!»
else
Set objWMI = GetObject(«winmgmts:» & strDC & « ootdirectoryLDAP»)
Set objUsers = objWMI.ExecQuery(«SELECT * FROM ds_user where ds_sAMAccountName = « & «‘« & intUserName & «‘ «)
If objUsers.Count = 0 Then
UserExist = 0
Else
UserExist = 1
End If
end if
end function
Wscript.Echo «Done!»