В первой и второй части я рассказал про основные моменты управления принтерами в 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: