Итак, как я и обещал, я вернулся к вопросу изменения ACL принтеров в PowerShell. В первой части (Странности метода SetSecurityDescriptor класса Win32_Printer) я изложил проблематику вопроса. В конечном итоге я сегодня смог найти решение, которое оказалось не совсем понятным, но относительно предсказуемым.
Вернёмся снова к документации MSDN: SetSecurityDescriptor Method of the Win32_Printer Class
Это, конечно же, было моим попустительством, что не указал флаг управления SE_DACL_PRESENT и не включил привилегии SeSecurityPrivilege ("умение читать - первое умение системного администратора" (c) Peter.G). Понимание этого факта пришло после очередного прочтения поста о смене владельца папки (Смена владельца папки или файла в PowerShell (часть 2)). Что касается флагов управления, то выложу здесь значения флагов:
- SE_OWNER_DEFAULTED = 0x0001
- SE_GROUP_DEFAULTED = 0x0002
- SE_DACL_PRESENT = 0x0004
- SE_DACL_DEFAULTED = 0x0008
- SE_SACL_PRESENT = 0x0010
- SE_SACL_DEFAULTED = 0x0020
- SE_DACL_AUTO_INHERIT_REQ = 0x0100
- SE_SACL_AUTO_INHERIT_REQ =0x0200
- SE_DACL_AUTO_INHERITED = 0x0400
- SE_SACL_AUTO_INHERITED = 0x0800
- SE_DACL_PROTECTED = 0x1000
- SE_SACL_PROTECTED = 0x2000
- SE_RM_CONTROL_VALID = 0x4000
- SE_SELF_RELATIVE = 0x8000
Здесь я выделил жирным тот флаг, который нам нужен.
Добавим эту строчку к скрипту и добавим включение привилегий. И мы должны будем получить примерно такой скрипт:
$user = "everyone"
$SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance()
$ace = ([WMIClass] "Win32_Ace").CreateInstance()
$Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()
$SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier])
[byte[]] $SIDArray = ,0 * $SID.BinaryLength
$SID.GetBinaryForm($SIDArray,0)
$Trustee.Name = $user
$Trustee.SID = $SIDArray
$ace.AccessMask = 393224
$ace.AceType = 0
$ace.AceFlags = 0
$ace.Trustee = $Trustee
$SD.DACL = $ace
$SD.ControlFlags = 0x0004
$Printer = gwmi win32_printer -filter "name='CutePDF Writer'"
$Printer.psbase.Scope.Options.EnablePrivileges = $true
$inParams = $Printer.psbase.GetMethodParameters("SetSecurityDescriptor")
$inParams.Descriptor = $SD
$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 = 393224
[System32] $ace.AceType = 0
[System32] $ace.AceFlags = 0
[System32] $ace.Trustee = $Trustee
[System32] $SD.DACL = $ace
[System32] $SD.ControlFlags = 0x0004
[System32] $Printer = gwmi win32_printer -filter "name='CutePDF Writer'"
[System32] $Printer.psbase.Scope.Options.EnablePrivileges = $true
[System32] $inParams = $Printer.psbase.GetMethodParameters("SetSecurityDescriptor")
[System32] $inParams.Descriptor = $SD
[System32] $Printer.SetSecurityDescriptor($inParams)
__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 1
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ReturnValue : 2147749896
[System32]
И снова мы получаем ошибку, которая указывает, что какой-то параметр вызова неверный. Снова посмотрев предыдущую статью, я стал понимать в чём тут дело. А дело в том, что метод SetSecurityDescriptor в этом классе не принимает параметр Descriptor (хотя он есть в выдаче команды GetSecurityDescriptor) и в MSDN чётко указано:
uint32 SetSecurityDescriptor(
[in] Win32_SecurityDescriptor Descriptor
);
При этом, в методе SetShareInfo класса Win32_Share параметр Access я указывал и всё проходило на "ура". Здесь мы сталкиваемся с отсутствием единого формата использования метода SetSecurityDescriptor (отсутствие единого формата параметров я уже указывал в первой части) для различных WMI классов. И если один приём работает для одного класса, то, увы, далеко не гарантия, что этот же приём сработает для другого. Поэтому выкинем 2 предпоследние строчки из скрипта и в последней строке вместо аргумента $inParams заменим на $SD:
[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 = 393224
[System32] $ace.AceType = 0
[System32] $ace.AceFlags = 0
[System32] $ace.Trustee = $Trustee
[System32] $SD.DACL = $ace
[System32] $SD.ControlFlags = 0x0004
[System32] $Printer = gwmi win32_printer -filter "name='CutePDF Writer'"
[System32] $Printer.psbase.Scope.Options.EnablePrivileges = $true
[System32] $Printer.SetSecurityDescriptor($SD)
__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 1
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ReturnValue : 0
[System32]
Вуаля! Мы получили ReturnValue=0, что означает успешное выполнение команды. Давайте убедимся, что группа Everyone имеет маску доступа 393224 (это ReadPermissions, ChangePermissions и Print). Для этого снова вызовем метод GetSecurityDescriptor для нового образца класса Win32_Printer:
[System32] $a = (gwmi win32_Printer -filter "name='cutepdf writer'").getsecuritydescriptor()
[System32] $a.descriptor.dacl[0]
__GENUS : 2
__CLASS : Win32_ACE
__SUPERCLASS : __ACE
__DYNASTY : __SecurityRelatedClass
__RELPATH :
__PROPERTY_COUNT : 7
__DERIVATION : {__ACE, __SecurityRelatedClass}
__SERVER :
__NAMESPACE :
__PATH :
AccessMask : 393224
AceFlags : 0
AceType : 0
GuidInheritedObjectType :
GuidObjectType :
TIME_CREATED :
Trustee : System.Management.ManagementBaseObject
[System32] $a.descriptor.dacl[0].trustee
__GENUS : 2
__CLASS : Win32_Trustee
__SUPERCLASS : __Trustee
__DYNASTY : __SecurityRelatedClass
__RELPATH :
__PROPERTY_COUNT : 6
__DERIVATION : {__Trustee, __SecurityRelatedClass}
__SERVER :
__NAMESPACE :
__PATH :
Domain :
Name : Everyone
SID : {1, 1, 0, 0...}
SidLength : 12
SIDString : S-1-1-0
TIME_CREATED :
[System32]
Вот теперь мы видим нашу маску доступа (AccessMask=393224) в корне DACL и нашего уважаемого everyone в Trustee. 
Примечание: данный скрипт удаляет все имеющиеся ACE из ACL принтера и записывает новый единственный ACE. Не забывайте об этом.
Теперь я закрываю этот вопрос. Что же дальше? А дальше будет вот что: я напишу готовый набор функций для управления ACL списками принтеров. Какой это будет набор - я пока в процессе разработки его структуры, но однозначно будут функции вида Get- Set- Add- Remove- PrinterPermission. Возможно будут добавлены функции для владельцев. Но об этом уже в следующий раз, так что продолжение следует однозначно 