В первой и второй части я рассказал про основные моменты управления принтерами в 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 интерпретируются так):
- One of the parameters to the call is not correct.
один из параметров вызова указан неверно. Давайте попробуем проанализировать, что мы сформировали и сравним с данными, которые получили методом 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, но пока это почти единственный способ добиться результата - приходится с этим работать. Хотя, на первый взгляд может показаться, что там всё страшно и ужасно, но на самом деле, если разобрать предметно вопрос и поупражняться, всё оказывается вполне понятным, хоть и не совсем логичным и не всегда это хочет работать как положено :)