Update 28.10.2010: исправлена ошибка с назначением права ManageDocuments.
Вот и пришло время закрыть тему управления принтерами и их списками ACL в PowerShell с использованием WMI. Я в блоге уже расписывал решение частных задач по основным задачам управления принтеров и по управлению их ACL списками. В этом посте я сложу все наработки по этому вопросу в единый концептуальный скрипт, который будет называться PrinterUtils.ps1 с достаточно объёмным набором функций, которые нацелены на упрощение для администраторов автоматизации принтеров с использованием PowerShell. Если кто-то захочет разобраться в работе скрипта и понять используемые приёмы, то предлагаю ознакомиться с следующими ссылками:
В качестве основы я использовал свои предыдущие наработки с SecurityDescriptor в предыдущем блоге, когда разбирал вопрос управления SharePermissions из PowerShell:
Материала у меня на эту тему набралось достаточно много, чтобы проникнуться в идею работы классов WMI и SecurityDescriptor, который не раз пытался посадить меня в лужу :) Однако версия скрипта ShareUtils чётко говорит о том, что скрипт далеко не идеален и не оптимален, имеет свои недостатки, т.к. это был мой первый опыт работы с функциями. Сейчас я значительно переработал структуру работы скрипта (оставив только Core работы с SecurityDescriptor), добавив удалённое управление (в разумных пределах) и, главное (как мне кажется), реализовал работу функций в конвейере. Примеры использования скрипта распишу чуть ниже. Итак, представляю набор функций, которые реализованы в скрипте:
Данная секция представляет собой базовые возможности по управлению принтерами и полностью работоспособна в среде Windows XP/Windows Server 2003. А вот секция управления ACL списками принтеров доступна только в среде Windows Vista/Windows Server 2008. Сюда входят следующие функции:
Для этих функций полностью реализована поддержка удалённой работы и работа в конвейере. Синтаксис команд используется примерно следующий:
и несколько примеров использования:
Get-Printer PrintSrv "MyPrinter" | export-csv C:\LaserJet.csv - экспортирует ACL списки принтера MyPrinter в CSV файл
Get-Printer PrintSrv | Remove-PrinterPermission Everyone - удаляет группу Everyone из списков ACL всех принтеров сервера PrintSrv
Import-Clixml C:\Printers.xml | Set-Printer - восстанавливает права для принтеров на те, которые содержатся в XML файле. При этом текущие списки ACL указанных в файле принтеров будут полностью перезаписаны списком ACL из файла.
Get-Content C:\Computers.txt | %{Get-printer $_ | Add-Printerpermission NewPrinterWorkers Allow Print} - даёт право печати на всех принтерах, которые подключены к компьютерам из списка computers.txt
New-NetworkPrinter PrintSrv "HP LaserJet 2100" - подключает пользователю в контексте котрого исполняется скрипт сетевой принтер HP LaserJet 2100, который физически подключен к компьютеру PrintSrv
Это далеко не все варианты использования :) Вобщем, я старался создать достаточно широким функционалом, который обычно требуется в скриптах для принтменеджмента. Безусловно, он не охватывает все задачи и в нём реализованы только те функции, которые я посчитал актуальными. А вот, собственно и скрипт с небольшими комментариями по ходу дела:
######################################################## # PrinterUtils.ps1 # Version 0.1.0.0 # # Functions for advanced printer management # # Vadims Podans (c) 2008 # http://www.sysadmins.lv/ ######################################################## # внутренняя функция, которая преобразовывает числовой код возврата операции записи ACL # в текстовое значение. function _PrinterUtils_Get-Code ($Write) { switch ($Write.ReturnValue) { "0" {"Success"} "2" {"Access Denied"} "8" {"Unknown Error"} "9" {"The user does not have adequate privileges to execute the method"} "21" {"A parameter specified in the method call is invalid"} default {"Unknown error $Write.ReturnValue"} } } # функция получения списка (списков) ACL принтера или всех принтеров function Get-Printer ($Computer = ".", $name) { # Если переменная $name пустая, то возвращается список всех локальных принтеров if ($name) { $Printers = gwmi Win32_Printer -ComputerName $Computer -Filter "name = '$name'" } else { $Printers = gwmi Win32_Printer -ComputerName $Computer -Filter "local = '$True'" } # объявление массива списков ACL $PrinterInfo = @() # извлечение списка ACL из каждого элемента массива списков ACL foreach ($Printer in $Printers) { if ($printer) { # в переменную $SD получаем дескриптор безопасности для каждого принтера и каждый элемент ACE (DACL) # и добавляем в $PrinterInfo $SD = $Printer.GetSecurityDescriptor() $PrinterInfo += $SD.Descriptor.DACL | %{ $_ | Select @{e = {$Printer.SystemName}; n = 'Computer'}, @{e = {$Printer.name}; n = 'Name'}, AccessMask, AceFlags, AceType, @{e = {$_.trustee.Name}; n = 'User'}, @{e = {$_.trustee.Domain}; n = 'Domain'}, @{e = {$_.trustee.SIDString}; n = 'SID'} } } else { Write-Warning "Specified printer not found!" } } # выдача сведений об ACL на выход функции для последующей подачи на конвейер $PrinterInfo } # функция записи в ACL принтера. Она не принимает никаких аргументов, # а только принимает данные с конвейера function Set-Printer { # по конвейеру получаем массив ACE из внешнего источника $PrinterInfo = @($Input) # расшиваем полученный массив по имени принтера и дальше по циклу подаём на # обработку только ACL одного принтера $PrinterInfo | Select -Unique Computer, Name | % { $Computer = $_.Computer $name = $_.name # создаём новые объекты необходимых классов $SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance() $ace = ([WMIClass] "Win32_Ace").CreateInstance() $Trustee = ([WMIClass] "Win32_Trustee").CreateInstance() # теперь расшиваем каждый ACE уже отфильтрованного списка ACL из PrinterInfo и # заполняем форму SecurityDescriptor $PrinterInfo | ? {$_.Computer -eq $Computer -and $_.name -eq $name} | % { $SID = new-object security.principal.securityidentifier($_.SID) [byte[]] $SIDArray = ,0 * $SID.BinaryLength $SID.GetBinaryForm($SIDArray,0) $Trustee.Name = $_.user $Trustee.SID = $SIDArray $ace.AccessMask = $_.AccessMask $ace.AceType = $_.AceType $ace.AceFlags = $_.AceFlags $ace.trustee = $Trustee # набор ACE поэтапно добавляем в DACL дескриптора безопасности $SD.DACL += @($ace.psobject.baseobject) # устанавливаем флаг SE_DACL_PRESENT, что будет говорить о том, что мы изменяем # только DACL и ничего более $SD.ControlFlags = 0x0004 } # когда полный список ACL для текущего принтера собран, выбираем имя текущего принтера $Printer = gwmi Win32_Printer -ComputerName $Computer -Filter "name = '$name'" # проверяется, что принтер для записи ACL найден и производится запись. # В противном случае запись ACL пропускается if ($Printer) { $Write = $Printer.SetSecurityDescriptor($SD) Write-Host "Processing current printer: $name" _PrinterUtils_Get-Code $Write } else { Write-Warning "Skipping non-present printer: $name" } } } # внутренняя функция, которая только формирует объект пользователя с набором прав # и возвращает объект в вызывающую функцию для последующих преобразований function _Create-SDObject ( $user, $AceType, $AccessMask) { # преобразование текстового вида прав в числовые значения $masks = @{ManagePrinters = 983052; ManageDocuments = 983088; Print = 131080; TakeOwnership = 524288; ReadPermissions = 131072; ChangePermissions = 262144} $types = @{Allow = 0; Deny = 1} # создание необходимых свойств для объекта. Для поддержки удалённого управления # было добавлено свойство Computer, которое будет принимать от Get-Printer аналогичное # значение. Тем самым обеспечивается сквозная трансляция имени компьютера, где # подключен принтер, по конвейеру для последующей записи $AddInfo = New-Object System.Management.Automation.PSObject $AddInfo | Add-Member NoteProperty Computer ([PSObject]$null) $AddInfo | Add-Member NoteProperty Name ([PSObject]$null) $AddInfo | Add-Member NoteProperty AccessMask ([uint32]$null) $AddInfo | Add-Member NoteProperty AceFlags ([uint32]$null) $AddInfo | Add-Member NoteProperty AceType ([uint32]$null) $AddInfo | Add-Member NoteProperty User ([PSObject]$null) $AddInfo | Add-Member NoteProperty Domain ([PSObject]$null) $AddInfo | Add-Member NoteProperty SID ([PSObject]$null) # заполнение объекта данными, которые были указаны в качестве аргументов вызова функции и возврат # объекта в вызывающую функцию $AddInfo.Name = $name $AddInfo.User = $user $AddInfo.SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier]) $AddInfo.AccessMask = $masks.$AccessMask $AddInfo.AceType = $types.$AceType if ($masks.$AccessMask -eq 983088) {$AddInfo.AceFlags = 9} $AddInfo } # функция для установки разрешений на принтер. При её использовании, текущий ACL очищается # от всех записей и устанавливается только один ползователь/группа с правом ManagePrinters function Set-PrinterPermission ($user) { # принимаются данные с конвейера $PrinterInfo = @($Input) $AddInfo = _Create-SDObject $user Allow ManagePrinters # в этом цикле перебираются по именам все имена принтеров и для каждого из них # записывается указанный в аргументах пользователь с удалением текущих ACE из ACL принтера # это видно по тому, что никакая часть $PrinterInfo не передаётся по конвейеру на запись foreach ($Printer in ($PrinterInfo | select -Unique Computer, Name)) { $AddInfo.Computer = $Printer.Computer $AddInfo.Name = $Printer.name $AddInfo | Set-Printer } } # функция добавления пользователя/группу в имеющийся список ACL принтера. Основное отличие от # предыдущего варианта, что для каждого принтера ACE не устанавливается, а добавляется function Add-PrinterPermission ($user, $AceType, $AccessMask) { $PrinterInfo = @($Input) $AddInfo = _Create-SDObject $user $AceType $AccessMask foreach ($Printer in ($PrinterInfo | select -Unique Computer, Name)) { $AddInfo.Name = $Printer.name $AddInfo.Computer = $Printer.Computer # вот этой строкой мы из списка всех принтеров итеративно перебираем каждый принтер $PrinterInfoNew = $PrinterInfo | ?{$_.name -eq $Printer.name} # и в хвост списка ACL добавляем новый ACE $PrinterInfoNew += $AddInfo # и подаём на запись $PrinterInfoNew | Set-Printer } } # функция для удаления ACE пользователя/группы из ACL function Remove-PrinterPermission ($user) { $Printers = @($Input) # просто берём списки ACL, которые пришли по конвейеру и выкидываем оттуда все ACE, # в которых фигурирует указанный в аргументах пользователь/группа и записывем ACE обратно в ACL $printers | ? {$_.user -ne $user} | Set-Printer } 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-Warning "You must to specify printer name. Operation aborted!" } else { if (gwmi win32_Printer -Filter "name='$name'") { $SetDefault = (gwmi win32_Printer -Filter "name='$name'").SetDefaultPrinter() switch ($SetDefault.ReturnValue) { "0" {Write-Host "Now your default printer is $name"} default {Write-Warning "Some error occur"} } } else { Write-Warning "Specified printer not exist!" } } } function Get-PrinterInfo ($Computer = ".", $name) { # здесь я предлагаю получить как полный набор свойств, так и упрощённый вывод сведений. if ($name) { gwmi Win32_Printer -ComputerName $Computer -Filter "name='$name'" | select * } else { gwmi Win32_Printer -ComputerName $Computer } } function New-PrinterShare ($Computer = ".", $name, $ShareName) { $Printer = gwmi win32_Printer -ComputerName $Computer -Filter "name='$name'" if ($Printer) { $Printer.shared = $True $Printer.ShareName = $ShareName $Printer.put() } else { Write-Warning "Specified printer not exist!" } } function Remove-PrinterShare ($Computer = ".", $name) { if ($name) { $filter = "name = '$name'" } else { $filter = "local = '$false'" } gwmi Win32_Printer -ComputerName $Computer -Filter $filter | % { $_.shared = $false $_.put() } }
многовато, конечно же, но ничего космически сложного для разбора я тут не вижу. Главное - чёткое понимание структуры SecurityDescriptor и хотя бы базовые навыки работы с ним.
Важно: если не указываете необязательные параметры, то указание последующих параметров в виде именованных (не позиционных) обязательна!
Даже не знаю, что ещё добавить сюда. Вроде все вопросы разобрал ранее, сейчас просто это всё собрал воедино. Вобщем, как обычно, если есть вопросы, замечания - кнопка Comments вам в помощь ;-)
The managedocuments acccess mask wasnt working for me. After some research i noticed that when the manage documents mask is used the ACEFLAG needs to be set to 9. So i made the following alterations to the _create-sdobject function function _Create-SDObject ( $user, $AceType, $AccessMask) { $masks = @{ManagePrinters = 983052; ManageDocuments = 983088; Print = 131080; TakeOwnership = 524288; ReadPermissions = 131072; ChangePermissions = 262144} $types = @{Allow = 0; Deny = 1} $AddInfo = New-Object System.Management.Automation.PSObject $AddInfo | Add-Member NoteProperty Computer ([PSObject]$null) $AddInfo | Add-Member NoteProperty Name ([PSObject]$null) $AddInfo | Add-Member NoteProperty AccessMask ([uint32]$null) $AddInfo | Add-Member NoteProperty AceFlags ([uint32]$null) $AddInfo | Add-Member NoteProperty AceType ([uint32]$null) $AddInfo | Add-Member NoteProperty User ([PSObject]$null) $AddInfo | Add-Member NoteProperty Domain ([PSObject]$null) $AddInfo | Add-Member NoteProperty SID ([PSObject]$null) $AddInfo.Name = $name $AddInfo.User = $user $AddInfo.SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier]) $AddInfo.AccessMask = $masks.$AccessMask ******************************** ADDED ************************** if($masks.$AccessMask=983088) { $AddInfo.AceFlags = 9 } ******************************** ADDED ************************** $AddInfo.AceType = $types.$AceType $AddInfo }
Hi, Rooz! Thanks for reply! I've tested this script as possible, however some moments may be missed. I will try your suggestion and fix this.
Thanks for the great script. The new lines don't work for me. ******************************** ADDED ************************** if($masks.$AccessMask=983088) { $AddInfo.AceFlags = 9 } ******************************** ADDED ************************** I think this one has to work (instead of '=', I use '-eq'): ******************************** ADDED ************************** if($masks.$AccessMask -eq 983088) { $AddInfo.AceFlags = 9 } ******************************** ADDED **************************
Я хотел бы прочитать в этом по-английски, это возможно? Я думаю, что вы единственный человек в мире Microsoft кто решил эту проблему для нескольких принтеров! спасибо Alex
Comments: