В первой и второй части я рассказал про основные моменты управления принтерами в PowerShell и теперь хочу поговорить о правах на принтеры. Т.к. принтеры управляются с помощью классов WMI, то управление правами доступа к ним будет превращаться в очередную эпохальную эпопею, которую я исследовал при изучении безопасности Share Permissions (вот ссылка на эти статьи в моём прежнем блоге: http://vpodans.spaces.live.com/lists/cns!BB1419A2CFC1E008!178). Однако, с принтерами оказалось всё печальней :( Мне так и не удалось заставить работать метод SetSecurityDescriptor. Итак, я расскажу о своих кратких исследованиях и в чём же мы имеем проблему.
Для чтения прав доступа принтера потребуется метод GetSecurityDescriptor:
[System32] $a=(gwmi win32_printer -filter "name='cutepdf writer'").getsecuritydescriptor()
[System32] $a
__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 2
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
Descriptor : System.Management.ManagementBaseObject
ReturnValue : 0
[System32] $a.Descriptor | fl [a-z]*
ControlFlags : 32780
DACL : {System.Management.ManagementBaseObject, System.Management.ManagementBaseObject,System.Mana
gement.ManagementBaseObject, System.Management.ManagementBaseObject...}
Group : System.Management.ManagementBaseObject
Owner : System.Management.ManagementBaseObject
SACL :
TIME_CREATED :
[System32] $a.descriptor.dacl[0] | fl [a-z]*
AccessMask : 983052
AceFlags : 0
AceType : 0
GuidInheritedObjectType :
GuidObjectType :
TIME_CREATED :
Trustee : System.Management.ManagementBaseObject
[System32] $a.descriptor.dacl[0].trustee | fl [a-z]*
Domain : Thor
Name : Admin
SID : {1, 5, 0, 0...}
SidLength : 28
SIDString : S-1-5-21-3020384060-3247076327-363933757-1000
TIME_CREATED :
[System32]
Как видите, метод GetSecurityDescriptor вернул нам единственный параметр - Descriptor. Заглянув в Descriptor, нам нужно было найти параметр DACL, который уже содержит все пермишены. Подробнее материал изложен тут: WMI Security Descriptor Objects.
Примечание: методы GetSecurityDescriptor и SetSecurityDescriptor доступны только в ОС начиная от Windows Vista/Windows Server 2008 и выше.
Структура DACL содержит в себе как права доступа (AccessMask), так и сведения о пользователе, который имеет указаную AccessMask маску доступа (Trustee). Я попробовал создать идентичную структуру SecurityDescriptor (как описано в ссылке: WMI Security Descriptor Objects) и получил вот такой скрипт:
# задаём пользователя, которому хотим предоставить доступ $user = "everyone" # объявляем необходимые классы, которые описывают дескриптор безопасности $SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance() $ace = ([WMIClass] "Win32_Ace").CreateInstance() $Trustee = ([WMIClass] "Win32_Trustee").CreateInstance() # преобразовываем имя пользователя в строковый SID и массив байтов для Win32_Trustee $SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier]) [byte[]] $SIDArray = ,0 * $SID.BinaryLength $SID.GetBinaryForm($SIDArray,0) # заполняем необходимыми данными класс Win32_Trustee $Trustee.Name = $user $Trustee.SID = $SIDArray # заполняем поля класса Win32_Ace, которые описывают права доступа и заворачиваем # пользователя в лице класса Win32_Trustee # AccessMask в контексте принтера может принимать значения: # 524288 - Take ownership # 131072 - read permissions # 262144 - change permissions # 983088 - manage documents # 983052 - manage printers # 131080 - print + read permissions $ace.AccessMask = 983052 $ace.AceType = 0 $ace.AceFlags = 0 $ace.Trustee = $Trustee # заворачиваем полученный объект Win32_Ace в параметр DACL класса Win32_SecurityDescriptor $SD.DACL = $ace # получаем объект принтера, с которым собираемся работать $Printer = gwmi win32_printer -filter "name='CutePDF Writer'" # получаем свойства метода SetSecurityDescriptor, чтобы в соответствии с ними # завернуть туда SecurityDescriptor $inParams = $Printer.psbase.GetMethodParameters("SetSecurityDescriptor") # заворачиваем SecurityDescriptor в параметр Descriptor метода SetSecurityDescriptor $inParams.Descriptor = $SD # применяем метод SetSecurityDescriptor $Printer.SetSecurityDescriptor($inParams)
И попробуем его запустить:
[System32] $user = "everyone"
[System32] $SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance()
[System32] $ace = ([WMIClass] "Win32_Ace").CreateInstance()
[System32] $Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()
[System32] $SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier])
[System32] [byte[]] $SIDArray = ,0 * $SID.BinaryLength[System32] $SID.GetBinaryForm($SIDArray,0)
[System32] $Trustee.Name = $user
[System32] $Trustee.SID = $SIDArray
[System32] $ace.AccessMask = 983052
[System32] $ace.AceType = 0
[System32] $ace.AceFlags = 0
[System32] $ace.Trustee = $Trustee
[System32] $SD.DACL = $ace
[System32] $Printer = gwmi win32_printer -filter "name='CutePDF Writer'"
[System32] $inParams = $Printer.psbase.GetMethodParameters("SetSecurityDescriptor")
[System32] $inParams.Descriptor = $SD
[System32] $Printer.SetSecurityDescriptor($inParams)
__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT :
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ReturnValue : 2147749896
[System32]
Как видно, ни одна строка не вернула ошибок, а последняя строка вернула значение 2147749896. Прогулявшись по MSDN нашёл описание этой ошибки: Win32SDToSDDL Method of the Win32_SecurityDescriptorHelper Class (практически везде данное значение в контексте SecurityDescriptor интерпретируются так):
один из параметров вызова указан неверно. Давайте попробуем проанализировать, что мы сформировали и сравним с данными, которые получили методом GetSecurityDescriptor:
[System32] $inparams
__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 1
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
Descriptor : System.Management.ManagementBaseObject
[System32] $inparams.Descriptor | fl [a-z]* ControlFlags :
DACL : {System.Management.ManagementBaseObject}
Group :
Owner :
SACL :
TIME_CREATED :
[System32] $inparams.Descriptor.dacl[0] | fl [a-z]*
AccessMask : 983052
AceFlags : 0
AceType : 0
GuidInheritedObjectType :
GuidObjectType :
TIME_CREATED :
Trustee : System.Management.ManagementBaseObject
[System32] $inparams.Descriptor.dacl[0].trustee | fl [a-z]*
Domain :
Name : everyone
SID : {1, 1, 0, 0...}
SidLength :
SIDString :
TIME_CREATED :
[System32]
Если сравнивать с первым листингом, где мы получали текущий SecurityDescriptor, то мы сохранили структуру SecurityDescriptor и в соответствующие поля записали нужные данные. При этом я также пытался для Trustee записать строковый SIDString и SIDLength:
$Trustee.SIDString = $SID.Value $Trustee.SIDLength = $SID.BinaryLength
и получил уже такой Trustee:
[System32] $inparams.Descriptor.dacl[0].trustee | fl [a-z]*
Domain :
Name : everyone
SID : {1, 1, 0, 0...}
SidLength : 12
SIDString : S-1-1-0
TIME_CREATED :
[System32]
От этого результат не изменился. Попробовал вызвать несколько иначе метод SetSecurityDescriptor:
$Printer.psbase.invokemethod("SetSecurityDescriptor", $inParams, $null)
и всё по старому. Я попытался нагуглить этот вопрос поисковой фразой "setsecuritydescriptor win32_printer powershell" (да-да, каюсь, я пользуюсь гуглем, как это ни прискорбно). И он мне выдал меньше одной страницы и 2 вменяемые ссылки, одна из которых ведёт на MSDN, а вторая на какой-то французский сайт (в котором я без переводчика плохо понимаю, а жаль) - http://powershell-scripting.com/index.php?option=com_joomlaboard&Itemid=76&func=view&id=2292&catid=6, но человек там творил что-то страшное и я понял, что там помощи не ждать.
Вобщем, этот вопрос пока остаётся открытым. Что ему не нравится, я пока не могу понять. Управление SecurityDescriptor в WMI - это не самая удачная модель управления безопасностью в WMI, но пока это почти единственный способ добиться результата - приходится с этим работать. Хотя, на первый взгляд может показаться, что там всё страшно и ужасно, но на самом деле, если разобрать предметно вопрос и поупражняться, всё оказывается вполне понятным, хоть и не совсем логичным и не всегда это хочет работать как положено :)
Приветствую Вадим. Мне было необходимо менять Owner и DACL для redirected принтеров терминальной фермы 2008R2. Статья была очень полезна, как начало от чего отталкиваться. Огромное спасибо. SetSecurityDescriptor() удалось заставить работать. Ваш код ============================================= $SD.DACL = $ace # получаем объект принтера, с которым собираемся работать $Printer = gwmi win32_printer -filter "name='CutePDF Writer'" # получаем свойства метода SetSecurityDescriptor, чтобы в соответствии с ними # завернуть туда SecurityDescriptor $inParams = $Printer.psbase.GetMethodParameters("SetSecurityDescriptor") # заворачиваем SecurityDescriptor в параметр Descriptor метода SetSecurityDescriptor $inParams.Descriptor = $SD # применяем метод SetSecurityDescriptor $Printer.SetSecurityDescriptor($inParams) ============================================= я переделал следующим образом (идея была почерпнута отсюда http://social.technet.microsoft.com/Forums/en-US/winserverpowershell/thread/0add30ad-5ac8-4e5e-8a9c-394a6b49279a) $SD.DACL += $ace # добавляем, их у нас может быть много $SD.ControlFlags="0x4" # set control flag $prnFilter="name="+$printerName $wPrinter = gwmi win32_printer -filter $prnFilter $wPrinter.psbase.Scope.Options.EnablePrivileges = $true # enable SeRestorePrivilege (for Windows Vista and Windows Server 2008 not neccessary if running in privileged mode) $wPrinter.SetSecurityDescriptor($SD) # Write new SecurityDescriptor to Printer SetSecurityDescriptor($SD) при этом перезапишет DACL объекта. Я не разобрался как сделать так чтобы DACL не перезаписывался, а обновлялся. Поэтому поэтому при внесении изменений нужно сформировать $SD.DACL на основе уже имеющихся записей, получив их с помощью $wPrinter.GetSecurityDescriptor()
Comments: