Contents of this directory is archived and no longer updated.

Update 28.10.2010: исправлена ошибка с назначением права ManageDocuments.


Вот и пришло время закрыть тему управления принтерами и их списками ACL в PowerShell с использованием WMI. Я в блоге уже расписывал решение частных задач по основным задачам управления принтеров и по управлению их ACL списками. В этом посте я сложу все наработки по этому вопросу в единый концептуальный скрипт, который будет называться PrinterUtils.ps1 с достаточно объёмным набором функций, которые нацелены на упрощение для администраторов автоматизации принтеров с использованием PowerShell. Если кто-то захочет разобраться в работе скрипта и понять используемые приёмы, то предлагаю ознакомиться с следующими ссылками:

В качестве основы я использовал свои предыдущие наработки с SecurityDescriptor в предыдущем блоге, когда разбирал вопрос управления SharePermissions из PowerShell:

Материала у меня на эту тему набралось достаточно много, чтобы проникнуться в идею работы классов WMI и SecurityDescriptor, который не раз пытался посадить меня в лужу :) Однако версия скрипта ShareUtils чётко говорит о том, что скрипт далеко не идеален и не оптимален, имеет свои недостатки, т.к. это был мой первый опыт работы с функциями. Сейчас я значительно переработал структуру работы скрипта (оставив только Core работы с SecurityDescriptor), добавив удалённое управление (в разумных пределах) и, главное (как мне кажется), реализовал работу функций в конвейере. Примеры использования скрипта распишу чуть ниже. Итак, представляю набор функций, которые реализованы в скрипте:

  1. Подключение (маппинг) сетевого принтера к пользователю;
  2. Отключение маппинга сетевого принтера от пользователя;
  3. Получение сведений о принтерах;
  4. Установка принтера по умолчанию;
  5. Установка принтера для общего пользования (расшаривание принтера);
  6. Отмена принтера для общего пользования;

Данная секция представляет собой базовые возможности по управлению принтерами и полностью работоспособна в среде Windows XP/Windows Server 2003. А вот секция управления ACL списками принтеров доступна только в среде Windows Vista/Windows Server 2008. Сюда входят следующие функции:

  1. Получение сведений о правах доступа на конкретный принтер, конкретный принтсервер или по списку компьютеров;
  2. Импорт сведений о правах доступа из внешнего источника. Это может быть и CSV и XML или другой формат;
  3. Добавление пользователя или группы в ACL список принтера или всех принтеров, которые подключены к принтсерверу;
  4. Удаление пользователя или группы из ACL списка принтера или всех принтеров, которые подключены к принтсерверу;
  5. Установка пользователя или группы в 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 вам в помощь ;-)


Share this article:

Comments:

Rooz

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 }

Vadims Podāns

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.

Marius

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 **************************

me.yahoo.com/a/PeJ2aV8Vpcn0KXZjzSv3peka

Я хотел бы прочитать в этом по-английски, это возможно? Я думаю, что вы единственный человек в мире Microsoft кто решил эту проблему для нескольких принтеров! спасибо Alex

Comments are closed.