Contents of this directory is archived and no longer updated.

Как-то давно Александр Станкевич просил у меня вариант скрипта, который бы менял владельца файла или папки из 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. Но это уже оффтопик и личные размышления. Вот :)


Share this article:

Comments:

Comments are closed.