Previous Page Page 3 of 4 in the PowerShellWMI category Next Page

Итак, как я и обещал, я вернулся к вопросу изменения 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. Rock

Примечание: данный скрипт удаляет все имеющиеся ACE из ACL принтера и записывает новый единственный ACE. Не забывайте об этом.

Теперь я закрываю этот вопрос. Что же дальше? А дальше будет вот что: я напишу готовый набор функций для управления ACL списками принтеров. Какой это будет набор - я пока в процессе разработки его структуры, но однозначно будут функции вида Get- Set- Add- Remove- PrinterPermission. Возможно будут добавлены функции для владельцев. Но об этом уже в следующий раз, так что продолжение следует однозначно happy

PowerShell |  ACL |  WMI
Wednesday, November 19, 2008 6:36:47 PM (FLE Standard Time, UTC+02:00)   Comments [2]    

 

Как-то давно Александр Станкевич просил у меня вариант скрипта, который бы менял владельца файла или папки из PowerShell. В своё время я занимался этим вопросом и результат моих исследований:

Что-то меня натолкнуло снова вернуться к этому вопросу. Учитывая проблематику, изложенных в предыдущих статьях, я перестал искать нативный способ изменения владельца в PowerShell через .NET и решил поискать его в WMI (что означает очередные мучения многострадального SecurityDescriptor :'( ). Итак, у WMI есть несколько классов для работы с ACL (AccessControlList) файлов и папок. Например:

Я для решения данной задачи решил использовать класс Win32_LogicalFileSecuritySetting (хотя, можно и Win32_Directory использовать но после мелкой доработки. Но об этом я выскажусь в конце статьи).

Итак, класс Win32_LogicalFileSecuritySetting имеет те же методы, что и остальные классы, работающие со списками ACL - GetSecurityDescriptor и SetSecurityDescriptor. Я уже неоднократно поднимал вопрос работы с SecurityDescriptor в PowerShell, поэтому приступим сразу к решению задачи.

SecurityDescriptor Structure

Как видно из картинки, нас будет интересовать объект Owner и ControlFlags. Объект DACL нас не будет интересовать совсем, поэтому работать с Win32_Ace нам не придётся, а только с Trustee:

$SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance()
$Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()

Далее следует стандартная процедура преобразования имени пользователя в SID и получение байтового массива из SID'а:

$SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier])
[byte[]] $SIDArray = ,0 * $SID.BinaryLength
$SID.GetBinaryForm($SIDArray,0)

Теперь $SIDArray и имя пользователя запишем в Trustee и поместим этот объект в свойство Owner дескриптора безопасности:

$Trustee.Name = $user
$Trustee.SID = $SIDArray
$SD.Owner = $Trustee

Для того, чтобы заменить владельца папки нужно заполнить объект Control Flags, которые описаны здесь: http://msdn.microsoft.com/en-us/library/aa394402(VS.85).aspx. Не уверен, что стоит углубляться в этот момент (на практике очень редко приходится им пользоваться), поэтому скажу, что нас заинтересует флаг SE_SELF_RELATIVE. Заполняется он одной строчкой:

$SD.ControlFlags="0x8000"

Его можно записывать как десятичное число (32768), так и в HEX нотации. Я использую HEX. Вот и всё, дескриптор безопасности у нас готов. Теперь самое время получить ACL в формате SecurityDescriptor из имеющейся папки:

$wPrivilege = gwmi Win32_LogicalFileSecuritySetting -filter "path='$path'"

Вот теперь мы вплотную подошли к нашей проблеме. К слову говоря, если этот скрипт использовать в Windows Vista/Windows Server 2008 с повышенными привилегиями (запустив консоль в привилегированном режиме), то можно добавлять последнюю строчку с записью нового владельца в папку. В системах, где есть UAC нету такой проблемы, которая описана в ссылках, которые приведены в начале поста, поскольку при запуске консоли с повышенными привилегиями UAC включает для нас все необходимые привилегии (в частности SeRestorePrivilege и SeTakeOwnershipPrivilege, которые необходимы для этой операции). Но в более ранних ОС при запуске консоли PowerShell эти права не включаются и их нужно включать отдельно. Если в .NET нету нативного метода включения этих привилегий, то в WMI они есть и вот они:

$wPrivilege.psbase.Scope.Options.EnablePrivileges = $true

В Options помимо включения привилегий можно указывать имперсонализацию пользователя (Impersonate) и другие параметры. Чтобы посмотреть доступные свойства достаточно набрать в консоли:

$wPrivilege.psbase.scope.options | Get-Member

Когда привилегии включены, можно уже записывать дескриптор в папку при помощи метода SetSecurityDescriptor:

$wPrivilege.setsecuritydescriptor($SD)

Если имя папки указано верно, то в выводе ReturnValue должен вернуть значение 0, что означает, что владелец сменён! Rock и мы небольшим (но для PowerShell'а это уже много, учитывая что многие вещи в нём делаются в одну строчку ;) ) увеличением объёма кода можем полноценно изменять владельца файла или папки без установки дополнительных расширений, как PSCX или отдельных консольных утилит, как SubInAcl или SetAcl.

Теперь это всё окультурим в готовый скрипт:

function Set-Owner ($user, $Path) {
    if (!(Test-Path -LiteralPath $Path)) {Write-Warning "Указан неверный путь к папке"}
    else {
        # преобразовываем путь вида C:\Folder в C:\\Folder (к слешу пути добавляем ещё один
        # для корректной работы класса Win32_LogicalFileSecuritySetting и эскейпим другие символы
        $path = $path -replace "\\|'",'\$0'
        $Path = $Path -replace '\[', "$([char]91)"
        $Path = $Path -replace '\]', "$([char]93)"
        # т.к. DACL мы не записываем, то объявляем только классы SecurityDescriptor и Trustee
        $SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance()
        $Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()
        # преобразовываем имя пользователя в SID и заполняем необходимые поля в Trustee
        $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
        $SD.Owner = $Trustee
        # здесь мы добавляем флаг управления
        $SD.ControlFlags="0x8000"
        # выбираем сведения о безопасности необходимой папки
        $wPrivilege = gwmi Win32_LogicalFileSecuritySetting -filter "path='$path'"
        # включаем привилегия для WMI. Для Windows Vista/Windows Server 2008,
        # при запуске скрипта с повышенными привилегиями данная строка не обязательна
        $wPrivilege.psbase.Scope.Options.EnablePrivileges = $true
        # записываем SecurityDescriptor с новым владельцем в папку
        $Return = $wPrivilege.setsecuritydescriptor($SD)
        # преобразовываем возвращаемый код в текстовое значение
        switch ($Return.ReturnValue) {
            "0" {"Успешно"}
            "2" {Write-Warning "Отказано в доступе"}
            "8" {Write-Warning "Неизвестная ошибка"}
            "9" {Write-Warning "Отсутствуют привилегии"}
            "21" {Write-Warning "Указан неправильный параметр"}
            "1307" {Write-Warning "Указанный пользователь не может быть владельцем данного объекта"}
            default {Write-Warning "Произошла неизвестная ошибка с кодом:" $Return.Value}
        }
    }
}

# эта часть совсем необязательна, я её включил лишь для наглядности 
# и полноты скрипта 
function Get-Owner ($path) {(Get-Acl $path).owner}

Примечание: При указании пути, который содержит пробелы, путь нужно заключать в кавычки!

и немного о стандартности использования методов SetSecurityDescriptor для различных объектов. Мне не понятно, почему в различных классах WMI используются различные именования свойств дескриптора безопасности, когда в этом явных причин нету? Например:

  • Win32_Share для дескриптора использует свойство Access метода SetShareInfo
  • Win32_Printer использует свойство Descriptor метода SetSecurityDescriptor
  • Win32_Directory использует SecurityDescriptor метода SetSecurityDescriptor (и для SetSecurityDescrptorEx)
  • Win32_LogicalFileSecuritySetting использует Descriptor метода SetSecurityDescriptor

Вот 4 WMI класса управления ACL списками различных объектов, с которыми я недавно работал и имеем 3 различных именования свойства дескриптора безопасности и Win32_Share использует даже другое название метода (SetShareInfo), хотя этот метод использует тот же Win32_SecurityDescriptor. Но это уже оффтопик и личные размышления. Вот :)

PowerShell |  ACL |  WMI
Monday, November 17, 2008 2:19:53 AM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

В первой и второй части я рассказал про основные моменты управления принтерами в 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, но пока это почти единственный способ добиться результата - приходится с этим работать. Хотя, на первый взгляд может показаться, что там всё страшно и ужасно, но на самом деле, если разобрать предметно вопрос и поупражняться, всё оказывается вполне понятным, хоть и не совсем логичным и не всегда это хочет работать как положено :)

PowerShell |  ACL |  WMI
Friday, November 14, 2008 9:21:45 PM (FLE Standard Time, UTC+02:00)   Comments [1]    

 

По некоторым причинам я пока не готов разговаривать об управлении безопасностью принтеров в PowerShell, поэтому продолжу начатую тему базового управления. Я обещал, что создам набор необходимых функций, для работы с принтерами и на данный момент приготовил следующие функции:

  • New-NetworkPrinter - добавляет (мапит) сетевой принтер к пользователю
  • Remove-NetworkPrinter - удаляет сетевой принтер у пользователя (удаляет только маппинг принтера)
  • Set-DefaultPrinter - устанавливает выбранный принтер принтером по умолчанию
  • Get-Printer - получение сведений о выбранном принтере. Как полный набор сведений, так и краткий
  • New-PrinterShare - расшаривает локальный принтер для общего сетевого доступа
  • Remove-PrinterShare - отменяет сетевой доступ к принтеру

Примечание: здесь я не буду подробно описывать всё подряд, а расскажу только о тех вещах, которые считаю важными для читателя. Остальное он и сам додумает ;)

Сразу хочу оговориться, что для первых 3-х функций реализована только локальная поддержка (т.е. использовать функции по отношению к другим компьютерам нельзя). Сделано это почему: дело в том, что все WMI операции выполняются (даже на удалённых машинах) в контексте того пользователя, который запустил скрипт. Очень сомнительный смысл мапить для себя принтер на другой машине. Ко всему прочему в классе Win32_Printer не реализована даже поддержка удалённого маппинга принтера. Сейчас я это продемонстрирую:

В классе Win32_Printer есть замечательный метод AddPrinterConnection, который подключает сетевой принтер к пользователю. Если в MSDN написано, что он есть, значит таконо и есть. Посмотрим:

[vPodans] gwmi Win32_Printer | gm -membertype method


   TypeName: System.Management.ManagementObject#root\cimv2\Win32_Printer

Name                  MemberType Definition
----                  ---------- ----------
CancelAllJobs         Method     System.Management.ManagementBaseObject CancelAllJobs()
GetSecurityDescriptor Method     System.Management.ManagementBaseObject GetSecurityDescriptor()
Pause                 Method     System.Management.ManagementBaseObject Pause()
PrintTestPage         Method     System.Management.ManagementBaseObject PrintTestPage()
RenamePrinter         Method     System.Management.ManagementBaseObject RenamePrinter(System.String NewPrinterName)
Reset                 Method     System.Management.ManagementBaseObject Reset()
Resume                Method     System.Management.ManagementBaseObject Resume()
SetDefaultPrinter     Method     System.Management.ManagementBaseObject SetDefaultPrinter()
SetPowerState         Method     System.Management.ManagementBaseObject SetPowerState(System.UInt16 PowerState, Syst...
SetSecurityDescriptor Method     System.Management.ManagementBaseObject SetSecurityDescriptor(System.Management.Mana...


[vPodans]

Здесь я выбрал список методов объекта Win32_Printer. Но метода AddPrinterConnection мы там не наблюдаем! Почему? А всё потому, что ни один объект Win32_Printer не содержит данный метод! Метод AddPrinterConnection есть только внутри самого класса Win32_Printer и ни одним объектом не наследуется. Чтобы убедиться в этом, я вызову новый инстанс класса (не существующего объекта) и просмотрю его методы:

[vPodans] [wmiClass]'Win32_Printer' | gm -membertype method


   TypeName: System.Management.ManagementClass#ROOT\cimv2\Win32_Printer

Name                 MemberType Definition
----                 ---------- ----------
AddPrinterConnection Method     System.Management.ManagementBaseObject AddPrinterConnection(System.String Name)


[vPodans]

Вуаля, вот и наш метод! Ещё раз обратите внимание, что в первом случае я просматривал методы существующего объекта Win32_Printer, во втором же случае я просмотрел методы самого класса и увидел искомый метод. В принципе, можно создать новый инстанс класса на удалённой машине, но я, всё же, не вижу в этом реальной необходимости, поэтому я решил оставить это всё как есть. У кого энтузиазма больше - может поупражняться в этом.

Что касается удаления примапленного принтера, то я рассказывал об этом в первой части. Чтобы не было вопросов, откуда взялся метод Delete, которого мы здесь не увидели (а так же не увидим на сайте MSDN), то сразу отвечу, что это не чистый метод, а скриптметод. Его можно найти, выполнив лишь команду:

gwmi Win32_Printer | gm -membertype scriptmethod

И там он будет. Поехали дальше. Что касается расшаривания принтера для предоставления общего доступа и его отмены. Мы не имеем ни единого метода или скриптметода, который бы это делал. Но если ещё раз и внимательно изучить класс Win32_Printer на MSDN (ещё раз даю ссылку), то можно заметить, что некоторые свойства объектов имеют Access Type: Read/Write. Именно этим мы и воспользуется:

[System32] (gwmi Win32_Printer -Filter "name='CutePDF Writer'").shared
False
[System32] $a = gwmi Win32_Printer -Filter "name='CutePDF Writer'"
[System32] $a.shared = $True
[System32] $a.put()


Path          : \\localhost\root\cimv2:Win32_Printer.DeviceID="CutePDF Writer"
RelativePath  : Win32_Printer.DeviceID="CutePDF Writer"
Server        : localhost
NamespacePath : root\cimv2
ClassName     : Win32_Printer
IsClass       : False
IsInstance    : True
IsSingleton   : False



[System32] (gwmi Win32_Printer -Filter "name='CutePDF Writer'").shared
True
[System32]

Итак, что же я сделал. Первой строкой я просмотрел свойство Shared для принтера CutePDF Writer и получил его значение - False. Это значит, что принтер просто локальный и не предоставлен в общий доступ. Далее, я изменил свойство Shared на True, предоставив его в общий доступ. Но это я только изменил свойство в переменной $a, но не в самом объекте. Чтобы записать изменения в объект я выполняю скриптметод put() и изменения уже записываются в сам объект принтера. Для верности я снова просмотрел свойство Shared и увидел мои изменения (вместо False получил True), т.е. принтер расшарился. Отмена предоставления принтера в общий доступ делается с точностью наоборот, а именно - свойство Shared выставляется обратно в False и изменения записываются командой put().

Примечание: при первом расшаривании принтера, кроме свойства Shared следует заполнить свойство ShareName, под которым он будет виден в сети. Так же выставив свойство Published в True мы можем опубликовать принтер в службе Active Directory.

На основе вышеизложенного материала напишем 6 несложных функций, которые обеспечивают базовый (это далеко не всё, что мы можем сделать с классом Win32_Printer) функционал по управлению принтерами:

########################################################
# BasicPrinterUtils.ps1
# Version 1.0
#
# Functions for basic printer management
#
# Vadims Podans (c) 2008
#
http://www.sysadmins.lv/
########################################################

function New-NetworkPrinter ($computer, $name) {
([wmiclass]'Win32_Printer').AddPrinterConnection("\\$computer\$name")
}

function Remove-NetworkPrinter ($name) {
if ($name) {(gwmi Win32_Printer -Filter "sharename='$name'").delete()}
else {(gwmi Win32_Printer -Filter "local='$false'").delete()}
}

function Set-DefaultPrinter ($name) {
if (!$name) {Write-Host "Не указано имя принтера. Операция прервана"}
else {$internal = gwmi win32_Printer -Filter "name='$name'"
$internal.setdefaultprinter()}
}

function Get-Printer ($computer, $name, $full) {
$internal = gwmi Win32_Printer -ComputerName $computer -Filter "name='$name'"
# здесь я предлагаю получить как полный набор свойств, так и упрощённый вывод сведений.
if ($full) {$internal | select *} else {Write-Host $internal}
}

function New-PrinterShare ($computer, $name, $ShareName) {
$internal = gwmi win32_Printer -ComputerName $computer -Filter "name='$name'"
if ($internal) {$internal.shared = $true; $internal.ShareName = $ShareName; $internal.put()}
else {Write-Host "Указано неверное имя принтера"}
}

function Remove-PrinterShare ($computer, $name) {
if ($name) {$internal = gwmi Win32_Printer -Filter "name='$name'"; $internal.shared = $false; $internal.put()}
else {gwmi Win32_Printer -Filter "shared='$true'" | %{$_.shared = $false; $_.put()}}
}

Полагаю, что здесь комментировать особо нечего, т.к. тут всё очень просто и понятно. На этом пока всё. Продолжение, полагаю, следует. :)

BasicPrinterUtils.ps1 (1,6 KB)
Friday, November 14, 2008 3:31:03 AM (FLE Standard Time, UTC+02:00)   Comments [3]    

 

Ни для кого не секрет, что PowerShell умеет управлять принтерами. Для этого используются как WMI классы, так и COM объекты. При этом управление ими из PowerShell совсем не сложное. WMI представляет следующие классы:

Итак, самое простое - перечисление принтеров:

Write-Host "`tLocal Printers"
gwmi win32_printer -Filter "Local='$true'" | ft Name, Drivername -a
Write-Host "`tNetwork Printers"
gwmi win32_printer -Filter "Local='$false'" | ft Name, Drivername, ServerName -a

Здесь я вывожу 2 списка принтеров - которые подключены локально и сетевые принтеры. Фильтрация осуществляется по свойству Local, которое может быть или $true или $false. Так же можно посмотреть, какие принтеры расшарены для других пользователей:

Write-Host "`tShared Printers"
gwmi win32_printer -Filter "Shared='$true'" | ft Name, ShareName -a

Безусловно, как и остальные классы WMI, класс Win32_Printer позволяет удалённо подключаться к принтсерверу с использованием параметра -ComputerName.

Так же можно посмотреть текущие настройки принтера:

gwmi Win32_PrinterConfiguration | ft Caption, XResolution, YResolution, PaperSize -a

Этой командой я увижу основные параметры качества печати и текущий размер используемой бумаги. Дополнительные свойства описаны в классе Win32_PrinterConfiguration и описывать их тут смысла нету.

Классы Win32_PrinterDriver и Win32_PrinterDriverDll несут лишь справочную информацию о драйверах принтеров и реальное применение им я представляю слабо и рассматривать не буду.

Класс Win32_PrintJob показывает текущее состояние очереди печати принтера. Например, сейчас мой принтер печатает тестовую страницу:

[user name] gwmi Win32_Printjob | select *


Document         : Test Page
JobId            : 3
JobStatus        : Printing
Name             : Новая оргтехника, 3
PagesPrinted     : 0
Status           : OK
__GENUS          : 2
__CLASS          : Win32_PrintJob
__SUPERCLASS     : CIM_Job
__DYNASTY        : CIM_ManagedSystemElement
__RELPATH        : Win32_PrintJob.Name="Новая оргтехника, 3"
__PROPERTY_COUNT : 24
__DERIVATION     : {CIM_Job, CIM_LogicalElement, CIM_ManagedSystemElement}
__SERVER         : CAMELOT
__NAMESPACE      : root\cimv2
__PATH           : \\CAMELOT\root\cimv2:Win32_PrintJob.Name="Новая оргтехника, 3"
Caption          : Новая оргтехника, 3
DataType         : NT EMF 1.008
Description      : Новая оргтехника, 3
DriverName       : HP Photosmart D6100 series
ElapsedTime      :
HostPrintQueue   : \\CAMELOT
InstallDate      :
Notify           : user name
Owner            : user name
Parameters       :
PrintProcessor   : hpzpp4pi
Priority         : 1
Size             : 114944
StartTime        :
StatusMask       : 16
TimeSubmitted    : 20081110195514.849000+120
TotalPages       : 1
UntilTime        :

Здесь видно, на какой принтер идёт печать, имя документа, статус печати, размер документа, имя пользователя, от которого производится печать и др. Кстати говоря, параметр JobId показывает количество отправленных заданий с момента последнего перезапуска спулера печати. Так же хочу заметить, что Win32_PrintJob возвращает информацию только при наличии заданий в спулере. Когда принтер простаивает, то данный класс ничего не возвращает.

Теперь настало время поговорить, как подключать новые принтеры и удалять текущие принтеры. Подключать принтеры можно как с помощью WMI, так и с помощью COM:

  • WMI
([wmiclass]'Win32_Printer').AddPrinterConnection("\\server\Printername")
  • COM
(New-Object -ComObject WScript.Network).AddWindowsPrinterConnection("\\Server\PrinterName")

При этом важно отметить, что при получении объекта Win32_Printer в нём метод AddPrinterConnection не содержится, т.к. данный метод содержится в самом классе Win32_Printer.

Удаление принтера производится при помощи метода Delete:

(gwmi Win32_Printer -Filter "name='PrinterName'").delete()

Таким образом удаляется только один принтер. Все принтеры удаляются без указания фильтра:

(gwmi Win32_Printer).delete()

Например, недавно на ньюсгруппах был вопрос, как для развёртывания нового принтсервера удалить со всех компьютеров домена. Задача решалась в несколько строк:

$filter = "(objectcategory=computer)"
$ds = New-object System.DirectoryServices.DirectorySearcher([ADSI]"",$filter)
$computers = $ds.Findall() | %{$_.properties.name}
foreach ($computer in $computers) {gwmi win32_printer -computername "$computer" | %{$_.delete()}
На сегодня, я думаю, это всё. Во второй части я планирую поговорить о чтении и установке пермишенов на принтеры с использованием PowerShell.
Monday, November 10, 2008 10:06:45 PM (FLE Standard Time, UTC+02:00)   Comments [1]    

 

Previous Page Page 3 of 4 in the PowerShellWMI category Next Page
 · 

All content © 2008 - 2012, Vadims Podāns
"Spaces" Theme provided by: Vadims Podāns
About


E-mail - Send mail to the author(s)
Live Messenger -
For english language visitors
Библиотека
Календарик
<May 2012>
SunMonTueWedThuFriSat
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

Карта расположения посетителей
Favorites





Fan list



Disclaimer
Вся информация на сайте предоставляется на условиях «как есть», без предоставления каких-либо гарантий и прав.

При использовании материалов c данного сайта ссылка на оригинальный источник обязательна.
Protected by Copyscape Online Plagiarism Scanner