Contents of this directory is archived and no longer updated.

Posts on this page:

Как вы знаете, я в своё время писал скрипты для управления сетевыми папками (Shares) и наконец-то решил оформить это всё в человеческий модуль PowerShell.

Данный модуль позволяет вытворять следующее:

  • Получать список сетевых папок на локальном и/или удалённых компьютерах;
  • Расшаривать новые папки;
  • Удалять сетевые папки (останавливать шаринг конкретной папки без удаления фактического содержимого);
  • Добавлять/устанавливать/удалять права доступа к сетевой папке.

Вот инструкции по установке:

ZIP архив содержит папку с файлами, которую нужно распаковать в одну из папок:

  1. %USERPROFILE%\Documents\WindowsPowerShell\Modules (модуль будет доступен только для текущего пользователя)
  2. %WINDIR%\System32\WindowsPowerShell\v1.0\Modules (модуль будет доступен для всех пользователей в системе)
  3. Если один из указанных путей не существует, его придётся создать вручную.

Убедитесь, что архив распакован правильно и вы должны получить примерно такой вывод в консоли:

PS C:\> Get-Module -ListAvailable

ModuleType Name                      ExportedCommands
---------- ----                      ----------------
Manifest   ShareUtils                {}


PS C:\>

Если вы не получаете таких же результатов, значит вы что-то сделал не так. Убедитесь, что папка ShareUtils расположена в указанном пути: "%USERPROFILE%\Documents\WindowsPowerShell\Modules"

Импорт модуля в сессию:

PS C:\> Import-Module ShareUtils

Примечание: консоль должна быть запущена в elevated mode с правами локального администратора. Если модуль запущен с правами стандартного пользователя (или не в elevated mode), вы получите гневное сообщение и модуль работать не будет. Для просмотра доступных функций можно выполнить следующую команду: 

PS C:\> Get-Command -Module shareutils

CommandType     Name                                                Definition
-----------     ----                                                ----------
Filter          Add-SharePermission                                 ...
Function        Get-Share                                           ...
Function        New-Share                                           ...
Filter          Remove-Share                                        ...
Filter          Remove-SharePermission                              ...
Filter          Set-Share                                           ...
Filter          Set-SharePermission                                 ...


PS C:\>

Каждая функция имеет свой собственный хелп и для получения справки по конкретной функции достаточно набрать:

Get-Help <FunctionName>

И, собственно, ссылка на сам зип:

19

По просьбе читателей, а так же с учётом востребованности (судя по сообщениям форумов и ньюсгрупп) я нашёл время переписать скрипт ShareUtils.ps1 с поддержкой работы с удалёнными машинами и попутно пофиксив недочёты, которые были найдены за время эксплуатации предыдущей версии скрипта. Предыдущая версия опубликована здесь: Управление безопасностью общих папок (сетевых шар) в PowerShell (часть 4)

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

  1. New-Share –Computer <Computer> –Name <Name> –Path <Path> –Description <Description>
    где Computer – имя или IP адрес компьютера, на котором необходимо расшарить папку. (не обязательный параметр). Если не указан, используется текущий компьютер.
    Name - сетевое имя для папки;
    Path - путь к физической папке;
    Description описание к сетевой папке. При наличии пробелов -  заключить в кавычки (не обязательный параметр);
  2. Remove-Share –Computer <Computer> –Name <Name> – отменяет расшаривание на папке. Сама папка не удаляется.
    где Computer – имя или IP адрес компьютера, на котором нужно отменить расшаривание папки. (не обязательный параметр). Если не указан, используется текущий компьютер.
    Name - сетевое имя папки;
  3. Get-Share –Computer <Computer> –Name <Name> – получает основные сведения и списки DACL Share Permissions с указанных или всех сетевых папок.
    где Computer – имя или IP адрес компьютера, с которого нужно получить сведения о сетевых папках. (не обязательный параметр). Если не указан, используется текущий компьютер.
    Name - имя сетевой папки (не обязательный параметр). Если не указан, то выбираются все сетевые папки с типом Disk Drive (в которые системные шары не входят).
  4. Set-SharePermission –User <User> –AceType <AceType> –AccessMask <AccessMask> – устанавливает единственный Share Permission ACE для указанного в аргументах пользователя.
    User - имя пользователя/группы, которой предоставляется доступ;
    AceType - тип доступа. Этот параметр должен иметь одно из значений Allow/Deny;
    AccessMask - маска доступа. Этот параметр должен иметь одно из значений FullControl/Change/Read;

    Функция не может быть вначале строки, а только после конвейера Get-Share или другого источника с подходящими данными (например, если данные были сохранены в CSV/XML файле, то их можно использовать в качестве источника: Import-Csv path.csv | Set-SharePermission Everyone Allow Change). При этом все текущие права на сетевую папку будут удалены и записан только указанный в аргументах пользователь/группа.
  5. Add-SharePermission –User <User> –AceType <AceType> –AccessMask <AccessMask> – добавляет указанного в аргументах пользователя к Share Permissions выбранной сетевой папки (или папок)
    User - имя пользователя/группы, которой предоставляется доступ;
    AceType - тип доступа. Этот параметр должен иметь одно из значений Allow/Deny;
    AccessMask - маска доступа. Этот параметр должен иметь одно из значений FullControl/Change/Read;

    Функция не может быть вначале строки, а только после конвейера Get-Share или другого источника с подходящими данными (например, если данные были сохранены в CSV/XML файле, то их можно использовать в качестве источника: Import-Csv path.csv | Add-SharePermission Everyone Allow Change).
  6. Remove-SharePermission –User <User> – удаляет указанного пользователя из DACL выбранной сетевой папки (папок). Не может быть вначале строки, а только на выходе конвейера, откуда поступают объекты сетевых папок. Например, Get-Share | Remove-SharePermission Everyone – удалит группу Everyone из всех SharePermissions всех расшаренных папок на локальном компьютере. Разрешения NTFS при этом не изменяются.
    где User - имя пользователя/группы, которого следует удалить из ACL сетевой папки.

Примеры использования практически идентичные, как и в PrinterUtils: http://www.sysadmins.lv/PermaLink,guid,22c0550d-0c46-44ca-97ce-2b0bccbb51de.aspx

И, собственно, сам код:

########################################################
# ShareUtils.ps1
# Version 0.9
#
# Functions for advanced share management
#
# Note:
# Previous version is published at my former blog:
# http://vpodans.spaces.live.com/blog/cns!BB1419A2CFC1E008!188.entry
#
# Vadims Podans (c) 2009
# http://www.sysadmins.lv/
########################################################

# внутренняя функция, которая преобразовывает числовой код возврата операции записи DACL
# в текстовое значение.
function _ShareUtils_Get-Code ($write) {
    switch ($write.ReturnValue) {
        "0" {"Success"}
        "2" {"Access Denied"}
        "8" {"Unknown Failure"}
        "9" {"Invalid Name"}
        "21" {"Invalid Parameter"}
        "22" {"Duplicate Share"}
        "23" {"Redirected Path"}
        "24" {"Unknown Device or Directory"}
        "25" {"Net Name Not Found"}
        default {"Unknown error $write.ReturnValue"}
    }
}

# функция для извлечения сведений и DACL с существующих сетевых папок.
# обязательна для использования функций Add-SharePermission и Set-SharePermission
# если компьютер не указан, то используется текущий. Если имя сетевой паки не указано,
# то возвращается список сведений и DACL всех сетевых папок на локальном или удалённом компьютере
function Get-Share ($computer = ".", $name) {
    if ($name) {
        $shares = gwmi Win32_Share -ComputerName $computer -Filter "name = '$name'"
    } else {
        $shares = gwmi Win32_Share -ComputerName $computer -Filter "type = 0"
    }
    $ShareInfo = @()
    foreach ($share in $shares) {
        $ShareSec = gwmi Win32_LogicalShareSecuritySetting -ComputerName $computer -filter "name='$($share.name)'"
        if ($shareSec) {
            $SD = $sharesec.GetSecurityDescriptor()
            $ShareInfo += $SD.Descriptor.DACL | % {
                $_ | select @{e={$share.ClassPath.Server};n='Computer'},
                @{e={$share.name};n='Name'},
                @{e={$share.Path};n='Path'},
                @{e={$share.Description};n='Description'},
                AccessMask,
                AceFlags,
                AceType,
                @{e={$_.trustee.Name};n='User'},
                @{e={$_.trustee.Domain};n='Domain'},
                @{e={$_.trustee.SIDString};n='SID'}
            }
        } else {
            Write-Warning "Specified share not exist or you may not have sufficient rights to access them!"
        }
    }
    $ShareInfo
}

# функция записи обновлённых сведений в сетевые папки. Не может быть первой в строке, а только после
# конвейера, откуда поступают данные для записи. Если папка не расшарена, то скрипт её расшарит
# автоматически и запишет необходимые сведения о сетевой папке.
function Set-Share {
    $ShareInfo = @($input)
    $ShareInfo | select -unique Computer, Name, Path, Description | % {
        $Computer = $_.Computer
        $name = $_.name
        $SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance()
        $ace = ([WMIClass] "Win32_Ace").CreateInstance()
        $Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()
        $sd.DACL = @()
        $ShareInfo | ? {$_.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
            $SD.DACL += $ace.psObject.baseobject
        }
# проверяется наличие расшаренной папки. Если папка есть, то в неё записывается только SecurityDescriptor
# в противном случае она расшаривается и в неё производится полная запись всех данных
        $share = gwmi Win32_Share -ComputerName $computer -Filter "name = '$name'"
        if ($share) {
            $inParams = $share.psbase.GetMethodParameters("SetShareInfo")
            $inParams.Access = $SD
            $write = $share.psbase.invokemethod("SetShareInfo", $inParams, $null)
            Write-Host "Setting DACL on current share: $name on server $computer" -ForegroundColor green
            _ShareUtils_Get-Code $Write
        } else {
            $shareobject = [wmiClass]"\\$computer\root\cimv2:win32_Share"
            $inParams = $shareobject.psbase.GetMethodParameters("Create")
            $inParams.name = $_.name
            $inParams.path = $_.path
            $inParams.Description = $_.Description
            $inParams.Type = 0
            $inParams.Access = $SD
            $write = $shareobject.psbase.invokemethod("Create", $inParams, $null)
            Write-Host "Processing current share: $name on server $computer" -ForegroundColor green
            _ShareUtils_Get-Code $Write
        }
    }
}

function _Create-SDObject ($user, $AceType, $AccessMask) {
    # преобразование текстового вида прав в числовые значения
    $masks = @{FullControl = 2032127; Change = 1245631; Read = 1179817}
    $types = @{Allow = 0; Deny = 1}
    # создание необходимых свойств для объекта. Для поддержки удалённого управления
    # было добавлено свойство Computer, которое будет принимать от Get-Share аналогичное
    # значение. Тем самым обеспечивается сквозная трансляция имени компьютера, где
    # находится сетевая папка, по конвейеру для последующей записи
    $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 Path  ([PSObject]$null)
    $AddInfo | Add-Member NoteProperty Description  ([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
    $AddInfo
}

function Set-SharePermission ($user, $AceType, $AccessMask) {
    # принимаются данные с конвейера
    $ShareInfo = @($input)
    $AddInfo = _Create-SDObject $user $AceType $AccessMask
    # в этом цикле перебираются по именам все имена расшаренных папок и для каждой из них
    # записывается указанный в аргументах пользователь с удалением текущих ACE из ACL шары
    # это видно по тому, что никакая часть $ShareInfo не передаётся по конвейеру на запись
    foreach ($share in ($ShareInfo | select -Unique Computer, Name)) {
        $AddInfo.Computer = $share.Computer
        $AddInfo.Name = $share.name
        $AddInfo.Description = $Share.Description
        $AddInfo | Set-Share
    }
}

# просто добавляет нового участника безопасности к текущему DACL расшаренной папки.
# NTFS Acl не изменяется.
function Add-SharePermission ($user, $AceType, $AccessMask) {
    $ShareInfo = @($input); $ShareInfoNew = @()
    $AddInfo = _Create-SDObject $user $AceType $AccessMask
    foreach ($Share in ($ShareInfo | select -Unique Computer, Name)) {
        $AddInfo.Name = $Share.name
        $AddInfo.Computer = $Share.Computer
        $AddInfo.Description = $Share.Description
        # вот этой строкой мы из списка всех сетевых папок итеративно перебираем каждую шару
        $ShareInfoNew = @($ShareInfo | ?{$_.name -eq $Share.name})
        # в хвост списка ACL каждой сетевой шары добавляем новый ACE
        $ShareInfoNew += $AddInfo
        # и подаём на запись
        $ShareInfoNew | Set-Share
    }
}

# основная функция для удаления единичного ACE из ACL сетевой папки. Процесс сводится к извлечению
# текущего списка (или списков) ACL и фильтрации ACE в этом списке по методу Not Equal. Всё, что не подпадает под
# это действие записываются обратно в переменную, а всё, что подпало (указанный пользователь) обратно
# в переменную $ShareInfo не записывается.
function Remove-SharePermission ($user) {
    $shares = @($input)
    # просто берём списки ACL, которые пришли по конвейеру и выкидываем оттуда все ACE,
    # в которых фигурирует указанный в аргументах пользователь/группа и записывем ACE обратно в ACL
    $shares | ? {$_.user -ne $user} | Set-Share
}

# основная функция для создания новых сетевых папок на локальном компьютере. Здесь я использую упрощённый
# вариант создания сетевой папки, но учитывая один большой нюанс я добавил одно действие. Суть проблемы
# изложена тут: http://vpodans.spaces.live.com/blog/cns!BB1419A2CFC1E008!170.entry# поэтому при создании новой сетевой папки я вручную создаю с нуля список ACL, который содержит
# только группу Everyone и с правом Allow Read.
function New-Share ($computer = $env:COMPUTERNAME, $name, $path, $Description) {
    $user = (new-object security.principal.securityidentifier "S-1-1-0").translate([security.principal.ntaccount])
    $AddInfo = _Create-SDObject $user.Value Allow Read
    $AddInfo.Computer = $computer
    $AddInfo.Path = $path
    $AddInfo.Description = $Description
    $AddInfo | Set-Share
}

# отменяет расшаривание сетевой папки. Сама же физическая папка не изменяется.
function Remove-Share ($computer = ".", $name) {
    $share = gwmi Win32_Share -ComputerName $computer -Filter "name = '$name'"
    if (!$share) {
        Write-Warning "Specified network share doesn't exist!"
    } else {
        $write = $share.delete()
        Write-Host "Deleting network share $name on computer $computer:"
        _ShareUtils_Get-Code $write
    }
}

Наткнулся на него когда готовил ответ для ньюсгрупп. Как известно, владелец объекта может спокойно изменять списки ACL объектов минуя все их ограничения пользуясь неоткланяемым правом владения объектом. Поэтому, если из ACL объекта удалить все ACE и сохранить, то мы из проводника можем в любой момент вызвать вкладку Security объекта и задать требуемые ACE. Однако, это возможно только из графического интерфейса проводника сделать. При использовании скрипта и командлета Set-Acl мы этого сделать не сможем. Продемонстрирую проблему:

[C:\] whoami contoso\administrator [C:\] Get-Acl C:\Test | fl Path : Microsoft.PowerShell.Core\FileSystem::C:\Test Owner : CONTOSO\administrator Group : CONTOSO\Domain Users Access : Audit : Sddl : O:LAG:DUD:PAI [C:\] $acl = Get-Acl C:\Test [C:\] $accessrule = New-Object System.Security.AccessControl.FileSystemAccessRule("Administrator","FullControl", "Allow" ) [C:\] $acl.AddAccessRule($accessRule) [C:\] $acl | Set-Acl C:\Test Set-Acl : Attempted to perform an unauthorized operation. At line:1 char:15 + $acl | Set-Acl <<<< C:\Test + CategoryInfo : PermissionDenied: (C:\Test:String) [Set-Acl], UnauthorizedAccessException + FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.PowerShell.Commands.SetAclCommand [C:\]

Как видите, текущий пользователь (администратор) является владельцем папки. Но все ACE в ACL пустые (секция Access). Пользуясь правами владельца, всё же, я не могу ничего сделать с этим списком. Как выяснилось в процессе исследования, пользователю-владельцу, который собирается менять ACL необходимо иметь явно назначенное или унаследованное разрешение TakeOwnership. Никаких Read/ChangePermissions и прочих не надо. Теперь я из проводника добавлю себе право TakeOwnership и попробую снова исполнить код:

[C:\] Get-Acl C:\Test | fl Path : Microsoft.PowerShell.Core\FileSystem::C:\Test Owner : CONTOSO\administrator Group : CONTOSO\Domain Users Access : CONTOSO\administrator Allow TakeOwnership, Synchronize Audit : Sddl : O:LAG:DUD:PAI(A;OICI;0x180000;;;LA) [C:\] $acl = Get-Acl C:\Test [C:\] $accessrule = New-Object System.Security.AccessControl.FileSystemAccessRule("Administrator","FullControl", "Allow" ) [C:\] $acl.AddAccessRule($accessRule) [C:\] $acl | Set-Acl C:\Test [C:\] Get-Acl C:\Test | fl Path : Microsoft.PowerShell.Core\FileSystem::C:\Test Owner : CONTOSO\administrator Group : CONTOSO\Domain Users Access : CONTOSO\Administrator Allow TakeOwnership, Synchronize CONTOSO\Administrator Allow FullControl Audit : Sddl : O:LAG:DUD:PAI(A;OICI;0x180000;;;LA)(A;;FA;;;LA) [C:\]

Как видите, теперь всё получилось. Я не понимаю, зачем мне нужно иметь право TakeOwnership, чтобы изменить ACL, если я являюсь владельцем. В реальной среде это может вызвать определённые трудности, как неадекватное поведение скрипта, который будет сыпать ошибками (в связи с чем появился вопрос на ньюсгруппах).

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

$path = "C:\Test"
$user = "Administrator"
$path = $path.replace("\", "\\")
$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 = [System.Security.AccessControl.FileSystemRights]"FullControl"
$ace.AceFlags = "0x3"
$ace.AceType = 0
$ace.Trustee = $trustee
# читаем текущий ACL с объекта
$oldDACL = (gwmi Win32_LogicalFileSecuritySetting -filter "path='$path'").GetSecurityDescriptor().Descriptor.DACL
# добавляем его к пустому объекту DACL
$SD.DACL = $oldDACL
# и добавляем новый ACE
$SD.DACL += @($ace.psobject.baseobject)
# устанавливаем флаг SE_DACL_PRESENT
$SD.ControlFlags = "0x4"
$folder = gwmi Win32_LogicalFileSecuritySetting -filter "path='$path'"
$folder.setsecuritydescriptor($SD)

Я больше склонен доверять WMI и констатировать факт, что он работает честно – даёт мне делать с объектом что угодно и как угодно признавая мои права владения.

Я считаю, что Set-Acl был сильно не прав, отказав мне в изменении пустых ACE, поэтому отрепортил это дело на connect:

https://connect.microsoft.com/feedback/ViewFeedback.aspx?FeedbackID=418906&SiteID=99

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 вам в помощь ;-)

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