Вот и пришло время закрыть тему управления принтерами и их списками 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
$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 вам в помощь ;)

PowerShell |  ACL |  WMI
Saturday, November 22, 2008 8:11:28 PM (FLE Standard Time, UTC+02:00)   Comments [3]    

 

Итак, как я и обещал, я вернулся к вопросу изменения ACL принтеров в PowerShell. В первой части (Странности метода SetSecurityDescriptor класса Win32_Printer) я изложил проблематику вопроса. В конечном итоге я сегодня смог найти решение, которое оказалось не совсем понятным, но относительно предсказуемым.

Вернёмся снова к документации MSDN: SetSecurityDescriptor Method of the Win32_Printer Class

Это, конечно же, было моим попустительством, что не указал флаг управления SE_DACL_PRESENT и не включил привилегии SeSecurityPrivilege ("умение читать - первое умение системного администратора" (c) Peter.G). Понимание этого факта пришло после очередного прочтения поста о смене владельца папки (Смена владельца папки или файла в PowerShell (часть 2)). Что касается флагов управления, то выложу здесь значения флагов:

  • SE_OWNER_DEFAULTED = 0x0001
  • SE_GROUP_DEFAULTED = 0x0002
  • SE_DACL_PRESENT = 0x0004
  • SE_DACL_DEFAULTED =  0x0008
  • SE_SACL_PRESENT = 0x0010
  • SE_SACL_DEFAULTED = 0x0020
  • SE_DACL_AUTO_INHERIT_REQ = 0x0100
  • SE_SACL_AUTO_INHERIT_REQ =0x0200
  • SE_DACL_AUTO_INHERITED = 0x0400
  • SE_SACL_AUTO_INHERITED = 0x0800
  • SE_DACL_PROTECTED = 0x1000
  • SE_SACL_PROTECTED = 0x2000
  • SE_RM_CONTROL_VALID = 0x4000
  • SE_SELF_RELATIVE = 0x8000

Здесь я выделил жирным тот флаг, который нам нужен.

Добавим эту строчку к скрипту и добавим включение привилегий. И мы должны будем получить примерно такой скрипт:

$user = "everyone"
$SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance()
$ace = ([WMIClass] "Win32_Ace").CreateInstance()
$Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()
$SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier])
[byte[]] $SIDArray = ,0 * $SID.BinaryLength
$SID.GetBinaryForm($SIDArray,0)
$Trustee.Name = $user
$Trustee.SID = $SIDArray
$ace.AccessMask = 393224
$ace.AceType = 0
$ace.AceFlags = 0
$ace.Trustee = $Trustee
$SD.DACL = $ace
$SD.ControlFlags = 0x0004
$Printer = gwmi win32_printer -filter "name='CutePDF Writer'"
$Printer.psbase.Scope.Options.EnablePrivileges = $true
$inParams = $Printer.psbase.GetMethodParameters("SetSecurityDescriptor")
$inParams.Descriptor = $SD
$Printer.SetSecurityDescriptor($inParams)

Пробуем запустить его:

[System32] $user = "everyone" [System32] $SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance() [System32] $ace = ([WMIClass] "Win32_Ace").CreateInstance() [System32] $Trustee = ([WMIClass] "Win32_Trustee").CreateInstance() [System32] $SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier]) [System32] [byte[]] $SIDArray = ,0 * $SID.BinaryLength [System32] $SID.GetBinaryForm($SIDArray,0) [System32] $Trustee.Name = $user [System32] $Trustee.SID = $SIDArray [System32] $ace.AccessMask = 393224 [System32] $ace.AceType = 0 [System32] $ace.AceFlags = 0 [System32] $ace.Trustee = $Trustee [System32] $SD.DACL = $ace [System32] $SD.ControlFlags = 0x0004 [System32] $Printer = gwmi win32_printer -filter "name='CutePDF Writer'" [System32] $Printer.psbase.Scope.Options.EnablePrivileges = $true [System32] $inParams = $Printer.psbase.GetMethodParameters("SetSecurityDescriptor") [System32] $inParams.Descriptor = $SD [System32] $Printer.SetSecurityDescriptor($inParams) __GENUS : 2 __CLASS : __PARAMETERS __SUPERCLASS : __DYNASTY : __PARAMETERS __RELPATH : __PROPERTY_COUNT : 1 __DERIVATION : {} __SERVER : __NAMESPACE : __PATH : ReturnValue : 2147749896 [System32]

И снова мы получаем ошибку, которая указывает, что какой-то параметр вызова неверный. Снова посмотрев предыдущую статью, я стал понимать в чём тут дело. А дело в том, что метод SetSecurityDescriptor в этом классе не принимает параметр Descriptor (хотя он есть в выдаче команды GetSecurityDescriptor) и в MSDN чётко указано:

uint32 SetSecurityDescriptor(

[in] Win32_SecurityDescriptor Descriptor

);

При этом, в методе SetShareInfo класса Win32_Share параметр Access я указывал и всё проходило на "ура". Здесь мы сталкиваемся с отсутствием единого формата использования метода SetSecurityDescriptor (отсутствие единого формата параметров я уже указывал в первой части) для различных WMI классов. И если один приём работает для одного класса, то, увы, далеко не гарантия, что этот же приём сработает для другого. Поэтому выкинем 2 предпоследние строчки из скрипта и в последней строке вместо аргумента $inParams заменим на $SD:

[System32] $user = "everyone" [System32] $SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance() [System32] $ace = ([WMIClass] "Win32_Ace").CreateInstance() [System32] $Trustee = ([WMIClass] "Win32_Trustee").CreateInstance() [System32] $SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier]) [System32] [byte[]] $SIDArray = ,0 * $SID.BinaryLength [System32] $SID.GetBinaryForm($SIDArray,0) [System32] $Trustee.Name = $user [System32] $Trustee.SID = $SIDArray [System32] $ace.AccessMask = 393224 [System32] $ace.AceType = 0 [System32] $ace.AceFlags = 0 [System32] $ace.Trustee = $Trustee [System32] $SD.DACL = $ace [System32] $SD.ControlFlags = 0x0004 [System32] $Printer = gwmi win32_printer -filter "name='CutePDF Writer'" [System32] $Printer.psbase.Scope.Options.EnablePrivileges = $true [System32] $Printer.SetSecurityDescriptor($SD) __GENUS : 2 __CLASS : __PARAMETERS __SUPERCLASS : __DYNASTY : __PARAMETERS __RELPATH : __PROPERTY_COUNT : 1 __DERIVATION : {} __SERVER : __NAMESPACE : __PATH : ReturnValue : 0 [System32]

Вуаля! Мы получили ReturnValue=0, что означает успешное выполнение команды. Давайте убедимся, что группа Everyone имеет маску доступа 393224 (это ReadPermissions, ChangePermissions и Print). Для этого снова вызовем метод GetSecurityDescriptor для нового образца класса Win32_Printer:

[System32] $a = (gwmi win32_Printer -filter "name='cutepdf writer'").getsecuritydescriptor() [System32] $a.descriptor.dacl[0] __GENUS : 2 __CLASS : Win32_ACE __SUPERCLASS : __ACE __DYNASTY : __SecurityRelatedClass __RELPATH : __PROPERTY_COUNT : 7 __DERIVATION : {__ACE, __SecurityRelatedClass} __SERVER : __NAMESPACE : __PATH : AccessMask : 393224 AceFlags : 0 AceType : 0 GuidInheritedObjectType : GuidObjectType : TIME_CREATED : Trustee : System.Management.ManagementBaseObject [System32] $a.descriptor.dacl[0].trustee __GENUS : 2 __CLASS : Win32_Trustee __SUPERCLASS : __Trustee __DYNASTY : __SecurityRelatedClass __RELPATH : __PROPERTY_COUNT : 6 __DERIVATION : {__Trustee, __SecurityRelatedClass} __SERVER : __NAMESPACE : __PATH : Domain : Name : Everyone SID : {1, 1, 0, 0...} SidLength : 12 SIDString : S-1-1-0 TIME_CREATED : [System32]

Вот теперь мы видим нашу маску доступа (AccessMask=393224) в корне DACL и нашего уважаемого everyone в Trustee. Rock

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

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

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

 

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

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

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

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

SecurityDescriptor Structure

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

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

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

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

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

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

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

$SD.ControlFlags="0x8000"

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

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

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

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

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

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

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

$wPrivilege.setsecuritydescriptor($SD)

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

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

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

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

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

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

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

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

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

 

В воскресение, 16.11.2008 планирую провести небольшие изменения в блоге, поэтому возможны кратковременные проблемы с отображением контента. Изменения коснутся интерфейса, в частности приведёно в порядок (во всяком случае я так надеюсь) отображения кода на веб-странице и, главное, в выдаче на RSS-ленту. Сейчас посмотрел ленту - поплохело от увиденного. Но завтра (уже сегодня) постараюсь всё поправить. Ну и CSS стили немного пофиксю.

 

Заранее извиняюсь за неудобства.

Sunday, November 16, 2008 12:41:57 AM (FLE Standard Time, UTC+02:00)   Comments [1]    

 

Навеяно темой на форуме: http://forum.sysfaq.ru/index.php?showtopic=13274

Итак, есть задача отслеживать события удаления объектов (файлов и/или папок) с файлового сервера. Итак, для начала необходимо настроить аудит на файловом сервере. Приведу рекомендации коллеги WindowsNT:

  1. Включить аудит успешных событий типа Object Access.
  2. Отрегулировать размер журнала и параметры перезаписи.
  3. В свойствах папки Shared Documents (и других папок общего доступа) включить Аудит на Успешные Everyone: Delete, Delete Subfolders and Files.

Учитывая сопутствующую проблематику удалений, что для части программ при работе нормально удалять файлы при сохранении было предложено фиксировать 5 событий удаления в секунду. Такая схема вполне сгодится для среднестатистического решения. В любом случае предложенный скрипт можно будет изменить под свои нужды вполне свободно. За основу будет взят пост из моего предыдущего блога (Аудит входов на сервере терминалов с использованием PowerShell), но с некоторыми доработками.

Итак, задача разбивается на 4 составные части:

  1. получение всех эвентов с кодом EventID = 560;
  2. из полученных эвентов получить массив с уникальным временем;
  3. подсчитать количество эвентов для каждого уникального времени из массива, который был получен на втором этапе;
  4. если кол-во эвентов больше или равно 5 (цифра может быть и немного другой), то записываем их в лог и отмечаем, как "возможно нелегальное".

Итак, давайте пробовать решить все этапы:

1) получение массива эвентов по коду 560:

$Events = Get-EventLog security | ?{$_.eventid -eq 560 -and
    $_.EntryType -eq "SuccessAudit" -and
    $_.message -like "*Object Name:`tD:\Shared Documents\*" -and
    $_.message -like "*Accesses:`t%delete*"
}

Итак, здесь я сделал 3 фильтра: код события - 560, Тип аудита Success Audit (т.к. под кодом 560 регистрируются и неуспешные аудиты), имя корневой папки, которая нас будет интересовать (только для случаев, если настроено несколько аудитов на различных папках) и явно будем интересоваться действием $Delete. Теперь из этого массива нужно получить массив уникального времени:

$UniqueTime = $Events | select -Unique TimeGenerated | %{($_.timegenerated).datetime}

Здесь я получил массив с набором уникального времени. Отмечу, что я взял свойство DateTime, которое даст нам точность до 1 секунды. Массив мы делаем затем, чтобы потом считать сколько событий удаления зафиксировано с этим временем (отличить легальное удаление от нелегального). Теперь при помощи полученной переменной соберём кол-во эвентов удаления на момент уникального времени. Иными словами мы берём отметку времени, когда зафиксирован лог удаления файла или папки и посмотрим, сколько ещё логов было зафиксировано за эту секунду:

[object[]]$SelectedEvents = $Events | ?{($_.timegenerated).datetime -eq UniqueTime}
$SelectedEvents.count

Переменная $SelectedEvents будет содержать все эвенты удаления в течении секунды. И их количество можно посчитать свойством Count. И дальше можно проводить сравнительный анализ по кол-ву эвентов. Если их за секунду зафиксировано больше 5 (вероятность нелегального удаления файлов), то продолжаем разбирать переменную $SelectedEvents. Разбирается скрипт по той же схеме, что и в упомянутой выше статье по аудиту доступа на терминальный сервер. В итоге мы получим такой скрипт:

########################################################
# FileServerDeleteAudit.ps1
# Version 1.0
#
# Windows Server 2003 eventlog parser
#
# Vadims Podans (c) 2008
#
http://www.sysadmins.lv/
########################################################

$Data = New-Object System.Management.Automation.PSObject
$Data | Add-Member NoteProperty Time ($null)
$Data | Add-Member NoteProperty UserName ($null)
$Data | Add-Member NoteProperty FileName ($null)
$Data | Add-Member NoteProperty EventCount ($null)
$Events = Get-EventLog security | ?{$_.eventid -eq 560 -and
    $_.EntryType -eq "SuccessAudit" -and
    $_.message -like "*Object Name:`tD:\Shared Documents\*" -and
    $_.message -like "*Accesses:`t%delete*"
}
$UniqueTime = $Events | select -Unique TimeGenerated | %{($_.timegenerated).datetime}
foreach ($time in $UniqueTime) {[object[]]$SelectedEvents = $Events | ?{($_.timegenerated).datetime -eq $time}
    if ($SelectedEvents.count -ge 5) {
    $Data.EventCount = $SelectedEvents.Count
    $SelectedEvents | %{
    $message = $_.message.split("`n") |    %{$_.trim()}
    $Data.time = $_.TimeGenerated
    if ($message -like "Primary User Name:`t*$") {
        $Data.UserName = ($message | ?{$_ -like "Client User Name:*"} | %{$_ -replace "^.+`t *"})}
    else {
        $Data.UserName = ($message | ?{$_ -like "Primary User Name:*"} | %{$_ -replace "^.+`t *"})}
    $Data.FileName = ($message | ?{$_ -like "Object Name:*"} | %{$_ -replace "^.+`t *"})
    $Data
        }
    }
}

Что касается поля $Data.UserName, то здесь я сделал проверку поля Primary User Name. Если файл или папка были удалены с компьютера локально, то данное поле заполняется именем пользователя. Если же файл или папку удалили по сети, то в данном поле будет указано имя компьютера в формате ComputerName$, а имя пользователя будет отражено в поле Client User Name. Поэтому если в поле Primary User Name будет встречаться знак доллара ($), то мы в поле $Data.UserName записываем имя пользователя из поля эвентлога Client User Name. Жирным в скрипте я отметил те места, которые в зависимости от местных условий могут изменяться. В первом случае отмечена корневая папка, которая будет изучаться и кол-во инцидентов удаления в секунду описывает порог срабатывания скрипта. И результат работы скрипта:

Time                          UserName              FileName                              EventCount
----                          --------              --------                              ----------
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog                        140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin                    140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\AR                 140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\...                140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\...                140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\BG                 140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\...                140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\...                140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\DA                 140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\...                140
2008.11.15. 23:33:44          Administrator         C:\New Folder\dasblog\bin\...                140

Enjoy!

Sunday, November 16, 2008 12:17:15 AM (FLE Standard Time, UTC+02:00)   Comments [0]    

 

В первой и второй части я рассказал про основные моменты управления принтерами в PowerShell и теперь хочу поговорить о правах на принтеры. Т.к. принтеры управляются с помощью классов WMI, то управление правами доступа к ним будет превращаться в очередную эпохальную эпопею, которую я исследовал при изучении безопасности Share Permissions (вот ссылка на эти статьи в моём прежнем блоге: http://vpodans.spaces.live.com/lists/cns!BB1419A2CFC1E008!178). Однако, с принтерами оказалось всё печальней :'( Мне так и не удалось заставить работать метод SetSecurityDescriptor. Итак, я расскажу о своих кратких исследованиях и в чём же мы имеем проблему.

Для чтения прав доступа принтера потребуется метод GetSecurityDescriptor:

[System32] $a=(gwmi win32_printer -filter "name='cutepdf writer'").getsecuritydescriptor() [System32] $a __GENUS : 2 __CLASS : __PARAMETERS __SUPERCLASS : __DYNASTY : __PARAMETERS __RELPATH : __PROPERTY_COUNT : 2 __DERIVATION : {} __SERVER : __NAMESPACE : __PATH : Descriptor : System.Management.ManagementBaseObject ReturnValue : 0 [System32] $a.Descriptor | fl [a-z]* ControlFlags : 32780 DACL : {System.Management.ManagementBaseObject, System.Management.ManagementBaseObject,System.Mana gement.ManagementBaseObject, System.Management.ManagementBaseObject...} Group : System.Management.ManagementBaseObject Owner : System.Management.ManagementBaseObject SACL : TIME_CREATED : [System32] $a.descriptor.dacl[0] | fl [a-z]* AccessMask : 983052 AceFlags : 0 AceType : 0 GuidInheritedObjectType : GuidObjectType : TIME_CREATED : Trustee : System.Management.ManagementBaseObject [System32] $a.descriptor.dacl[0].trustee | fl [a-z]* Domain : Thor Name : Admin SID : {1, 5, 0, 0...} SidLength : 28 SIDString : S-1-5-21-3020384060-3247076327-363933757-1000 TIME_CREATED : [System32]

Как видите, метод GetSecurityDescriptor вернул нам единственный параметр - Descriptor. Заглянув в Descriptor, нам нужно было найти параметр DACL, который уже содержит все пермишены. Подробнее материал изложен тут: WMI Security Descriptor Objects.

Примечание: методы GetSecurityDescriptor и SetSecurityDescriptor доступны только в ОС начиная от Windows Vista/Windows Server 2008 и выше.

Структура DACL содержит в себе как права доступа (AccessMask), так и сведения о пользователе, который имеет указаную AccessMask маску доступа (Trustee). Я попробовал создать идентичную структуру SecurityDescriptor (как описано в ссылке: WMI Security Descriptor Objects) и получил вот такой скрипт:

# задаём пользователя, которому хотим предоставить доступ
$user = "everyone"
# объявляем необходимые классы, которые описывают дескриптор безопасности
$SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance()
$ace = ([WMIClass] "Win32_Ace").CreateInstance()
$Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()
# преобразовываем имя пользователя в строковый SID и массив байтов для Win32_Trustee
$SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier])
[byte[]] $SIDArray = ,0 * $SID.BinaryLength
$SID.GetBinaryForm($SIDArray,0)
# заполняем необходимыми данными класс Win32_Trustee
$Trustee.Name = $user
$Trustee.SID = $SIDArray
# заполняем поля класса Win32_Ace, которые описывают права доступа и заворачиваем
# пользователя в лице класса Win32_Trustee
# AccessMask в контексте принтера может принимать значения:
# 524288 - Take ownership
# 131072 - read permissions
# 262144 - change permissions
# 983088 - manage documents
# 983052 - manage printers
# 131080 - print + read permissions

$ace.AccessMask = 983052
$ace.AceType = 0
$ace.AceFlags = 0
$ace.Trustee = $Trustee
# заворачиваем полученный объект Win32_Ace в параметр DACL класса Win32_SecurityDescriptor
$SD.DACL = $ace
# получаем объект принтера, с которым собираемся работать
$Printer = gwmi win32_printer -filter "name='CutePDF Writer'"
# получаем свойства метода SetSecurityDescriptor, чтобы в соответствии с ними
# завернуть туда SecurityDescriptor
$inParams = $Printer.psbase.GetMethodParameters("SetSecurityDescriptor")
# заворачиваем SecurityDescriptor в параметр Descriptor метода SetSecurityDescriptor
$inParams.Descriptor = $SD
# применяем метод SetSecurityDescriptor
$Printer.SetSecurityDescriptor($inParams)

И попробуем его запустить:

[System32] $user = "everyone"
[System32] $SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance()
[System32] $ace = ([WMIClass] "Win32_Ace").CreateInstance()
[System32] $Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()
[System32] $SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier])
[System32] [byte[]] $SIDArray = ,0 * $SID.BinaryLength[System32] $SID.GetBinaryForm($SIDArray,0)
[System32] $Trustee.Name = $user
[System32] $Trustee.SID = $SIDArray
[System32] $ace.AccessMask = 983052
[System32] $ace.AceType = 0
[System32] $ace.AceFlags = 0
[System32] $ace.Trustee = $Trustee
[System32] $SD.DACL = $ace
[System32] $Printer = gwmi win32_printer -filter "name='CutePDF Writer'"
[System32] $inParams = $Printer.psbase.GetMethodParameters("SetSecurityDescriptor")
[System32] $inParams.Descriptor = $SD
[System32] $Printer.SetSecurityDescriptor($inParams)   

__GENUS          : 2
__CLASS          : __PARAMETERS
__SUPERCLASS     :
__DYNASTY        : __PARAMETERS
__RELPATH        :
__PROPERTY_COUNT :
__DERIVATION     : {}
__SERVER         :
__NAMESPACE      :
__PATH           :
ReturnValue      : 2147749896


[System32]

Как видно, ни одна строка не вернула ошибок, а последняя строка вернула значение 2147749896. Прогулявшись по MSDN нашёл описание этой ошибки: Win32SDToSDDL Method of the Win32_SecurityDescriptorHelper Class (практически везде данное значение в контексте SecurityDescriptor интерпретируются так):

  • One of the parameters to the call is not correct.

один из параметров вызова указан неверно. Давайте попробуем проанализировать, что мы сформировали и сравним с данными, которые получили методом GetSecurityDescriptor:

[System32] $inparams 

__GENUS          : 2
__CLASS          : __PARAMETERS
__SUPERCLASS     :
__DYNASTY        : __PARAMETERS
__RELPATH        :
__PROPERTY_COUNT : 1
__DERIVATION     : {}
__SERVER         :
__NAMESPACE      :
__PATH           :
Descriptor       : System.Management.ManagementBaseObject


[System32] $inparams.Descriptor | fl [a-z]* ControlFlags :

DACL         : {System.Management.ManagementBaseObject}
Group        :
Owner        :
SACL         :
TIME_CREATED : 


[System32] $inparams.Descriptor.dacl[0] | fl [a-z]* 

AccessMask              : 983052
AceFlags                : 0
AceType                 : 0
GuidInheritedObjectType :
GuidObjectType          :
TIME_CREATED            :
Trustee                 : System.Management.ManagementBaseObject 


[System32] $inparams.Descriptor.dacl[0].trustee | fl [a-z]* 

Domain       :
Name         : everyone
SID          : {1, 1, 0, 0...}
SidLength    :
SIDString    :
TIME_CREATED : 
[System32]

Если сравнивать с первым листингом, где мы получали текущий SecurityDescriptor, то мы сохранили структуру SecurityDescriptor и в соответствующие поля записали нужные данные. При этом я также пытался для Trustee записать строковый SIDString и SIDLength:

$Trustee.SIDString = $SID.Value
$Trustee.SIDLength = $SID.BinaryLength

и получил уже такой Trustee:

[System32] $inparams.Descriptor.dacl[0].trustee | fl [a-z]* 

Domain       :
Name         : everyone
SID          : {1, 1, 0, 0...}
SidLength    : 12
SIDString    : S-1-1-0
TIME_CREATED : 


[System32]

От этого результат не изменился. Попробовал вызвать несколько иначе метод SetSecurityDescriptor:

$Printer.psbase.invokemethod("SetSecurityDescriptor", $inParams, $null)


и всё по старому. Я попытался нагуглить этот вопрос поисковой фразой "setsecuritydescriptor win32_printer powershell" (да-да, каюсь, я пользуюсь гуглем, как это ни прискорбно). И он мне выдал меньше одной страницы и 2 вменяемые ссылки, одна из которых ведёт на MSDN, а вторая на какой-то французский сайт (в котором я без переводчика плохо понимаю, а жаль) - http://powershell-scripting.com/index.php?option=com_joomlaboard&Itemid=76&func=view&id=2292&catid=6, но человек там творил что-то страшное и я понял, что там помощи не ждать.

Вобщем, этот вопрос пока остаётся открытым. Что ему не нравится, я пока не могу понять. Управление SecurityDescriptor в WMI - это не самая удачная модель управления безопасностью в WMI, но пока это почти единственный способ добиться результата - приходится с этим работать. Хотя, на первый взгляд может показаться, что там всё страшно и ужасно, но на самом деле, если разобрать предметно вопрос и поупражняться, всё оказывается вполне понятным, хоть и не совсем логичным и не всегда это хочет работать как положено :)

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

 

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

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

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

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

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

[vPodans] gwmi Win32_Printer | gm -membertype method


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

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


[vPodans]

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

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


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

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


[vPodans]

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

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

gwmi Win32_Printer | gm -membertype scriptmethod

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

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


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



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

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

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

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

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

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

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

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

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

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

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

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

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

 

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

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

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

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

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

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

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

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

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

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

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

[user name] gwmi Win32_Printjob | select *


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

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

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

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

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

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

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

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

(gwmi Win32_Printer).delete()

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

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

 

И это снова я! Я чуть ранее объявил о закрытии своего первого блога на http://vpodans.spaces.live.com и о переезде сюда. Как я уже отмечал, я рассмотрел несколько вариантов нового блога. Это были и уже готовые сервисы (как wordpress.com, blogspot.com, etc) так и собственные (DasBlog, Blogengine.NET, Community Server, WordPress, etc). Я обкатал в виртуальной среде каждый из них и остановился на движке DasBlog, который написан на ASP.NET и является открытым проектом.

Данный движок, конечно же, не отличается такой функциональностью и удобством, как spaces.live.com, но для личного, некоммерческого продукта - вполне себе неплохо. Конечно же, дефолтной инсталляцией я не отделался и пришлось почти полностью переделать одну из готовых тем оформления и подогнать его под свои нужды. Я считаю, что получилось не так и плохо. Именно на этом сайте я продолжу свою блоггинг-деятельность, рассказывать про PowerShell и другие IT-технологии, которыми я интересуюсь и занимаюсь. Здесь так же буду стараться избегать постов ни о чём (что, к примеру, меня сильно раздражает на itcommunity.ru), а писать по существу.

Учитывая, что сайт находится в финальной стадии рихтовки, но уже пригодным для работы, могут быть обнаружены какие-то недочёты, которые я могу пропустить. Поэтому, если обнаружите, что что-то не работает, то отпишитесь в комментариях или мне на почту (под аватаркой указано, как со мной можно связаться).

Ну и немного рекламки.

  • сайт работает на движке DasBlog 2.2 -
  • Windows Server IIS хостинг предоставлен компанией Nano.lv -

На этом я заканчиваю вводную часть и буду тихонько готовиться к написанию новых сообщений о PowerShell и не только :)

 

happy

Sunday, November 09, 2008 3:00:18 AM (FLE Standard Time, UTC+02:00)   Comments [3]    

 

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


E-mail - Send mail to the author(s)
Live Messenger -
My former blog -
For english language visitors
Библиотека
Календарик
<September 2010>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

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





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

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