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. Сюда входят следующие функции:
- Получение сведений о правах доступа на конкретный принтер, конкретный принтсервер или по списку компьютеров;
- Импорт сведений о правах доступа из внешнего источника. Это может быть и CSV и XML или другой формат;
- Добавление пользователя или группы в ACL список принтера или всех принтеров, которые подключены к принтсерверу;
- Удаление пользователя или группы из ACL списка принтера или всех принтеров, которые подключены к принтсерверу;
- Установка пользователя или группы в ACL список принтера или всех принтеров, которые подключены к принтеру. При этом все имеющиеся права доступа будут удалены и заменены только одним ACE с правом ManagePrinters.
Для этих функций полностью реализована поддержка удалённой работы и работа в конвейере. Синтаксис команд используется примерно следующий:
- New-NetworkPrinter -Computer <name> -name <name>
где Computer - имя компьютера, к которому подключён сетевой принтер
Name - сетевое имя принтера, который следует подключить к пользователю - Remove-NetworkPrinter -Name <name>
где Name - имя примапленного сетевого принтера (не обязательный параметр). Если параметр Name не указан, то будут отключены все примапленные сетевые принтеры. - Get-PrinterInfo -Computer <name> -Name <name>
где Computer - имя компьютера, к которому подключён принтер (не обязательный параметр. Если не указан, то будет использоваться локальная машина),
Name - имя принтера на удалённой или локальной машине, зависит от предыдущего параметра (не обязательный параметр. Если не указан, то будет выведена краткая справка о всех принтерах указанного компьютера. Если указан, то будет выведена подробная информация о принтере). Вывод данной команды не будет содержать сведений о правах доступа на принтер. - Set-DefaultPrinter -Name <name>
где Name - имя (или путь) принтера, который должен стать для пользователя принтером по умолчанию. - New-PrinterShare -Computer <name> -Name <name> -ShareName <name>
где Computer - имя компьютера, к которому подключён принтер (не обязательный параметр. Если не указан, будет использоваться локальная машина)
Name - имя принтера, который требуется предоставить для общего доступа
ShareName - сетевое имя принтера, т.е. имя, под которым принтер будет виден из сети - Remove-PrinterShare -Computer <name> -Name <name>
где Computer - имя компьютера, к которому подключён принтер (не обязательный параметр. Если не указан, будет использоваться локальная машина)
Name - имя принтера, для которого необходимо отключить общий доступ (не обязательный параметр. Если не указан, то общий доступ будет отменён для всех расшаренных принтеров на выбранном предыдущим параметром компьютере) - Get-Printer -Computer <name> -Name <name>
где Computer - имя компьютера, к которому подключён принтер (не обязательный параметр. Если не указан, будет использоваться локальная машина)
Name - имя принтера, для которого следует получить сведения о правах доступа (не обязательный параметр. Если не указан, то будут получены сведения об ACL всех принтеров на выбранном предыдущим параметром компьютере)
Генерирует на выходе массив объектов с необходимыми сведениями о каждом ACE. Данный массив можно использовать как для изменения прав доступа, так и просто для экспорта во внешний файл. - Set-Printer
не принимает никаких аргументов, а только получает данные по конвейеру. В качестве входных данных должны использоваться объекты, которые по свойствам соответствуют объектам, которые генерирует команда Get-Printer. Команда не может быть в начале строки, а только на выходе конвейера, с которого поступают объекты. Так же по конвейеру можно передавать объекты из внешних файлов (например, CSV, XML) - Add-PrinterPermission -User <name> -AceType <name> -AccessMask <name>
где User - имя пользователя/группы, которого следует добавить в ACL список принтера
AceType - тип доступа. Может быть Allow или Deny
AccessMask - маска доступа. Может иметь следующие значения: ManagePrinters, ManageDocuments, Print, TakeOwnership, ReadPermissions, ChangePermissions
Данная команда так же не может быть в начале строки, а должна находиться на выходе конвейера, с которого поступают объекты. В качестве входных данных должны использоваться объекты, которые по свойствам соответствуют объектам, которые генерирует команда Get-Printer. Команда не может быть в начале строки, а только на выходе конвейера, с которого поступают объекты. Так же по конвейеру можно передавать объекты из внешних файлов (например, CSV, XML) - Remove-PrinterPermission -User <name>
где User - имя пользователя/группы, которого следует удалить из списка ACL принтера
Данная команда так же не может быть в начале строки, а должна находиться на выходе конвейера, с которого поступают объекты. В качестве входных данных должны использоваться объекты, которые по свойствам соответствуют объектам, которые генерирует команда Get-Printer. Команда не может быть в начале строки, а только на выходе конвейера, с которого поступают объекты. Так же по конвейеру можно передавать объекты из внешних файлов (например, CSV, XML) - Set-PrinterPermission -User <name>
где User - имя пользователя/группы, для которого следует предоставить привилегированный доступ.
Важно: при использовании команды Set-PrinterPermission следует помнить, что указанный пользователь/группа будут иметь единственный доступ к принтеру с правом ManagePrinters. При этом вероятно, что вы после исполнения команды потеряете доступ к принтеру
Данная команда так же не может быть в начале строки, а должна находиться на выходе конвейера, с которого поступают объекты. В качестве входных данных должны использоваться объекты, которые по свойствам соответствуют объектам, которые генерирует команда Get-Printer.
Команда не может быть в начале строки, а только на выходе конвейера, с которого поступают объекты. Так же по конвейеру можно передавать объекты из внешних файлов (например, CSV, XML)
и несколько примеров использования:
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 вам в помощь 