Как-то давно Александр Станкевич просил у меня вариант скрипта, который бы менял владельца файла или папки из PowerShell. В своё время я занимался этим вопросом и результат моих исследований:
Что-то меня натолкнуло снова вернуться к этому вопросу. Учитывая проблематику, изложенных в предыдущих статьях, я перестал искать нативный способ изменения владельца в PowerShell через .NET и решил поискать его в WMI (что означает очередные мучения многострадального SecurityDescriptor :( ). Итак, у WMI есть несколько классов для работы с ACL (AccessControlList) файлов и папок. Например:
Я для решения данной задачи решил использовать класс Win32_LogicalFileSecuritySetting (хотя, можно и Win32_Directory использовать но после мелкой доработки. Но об этом я выскажусь в конце статьи).
Итак, класс Win32_LogicalFileSecuritySetting имеет те же методы, что и остальные классы, работающие со списками ACL - GetSecurityDescriptor и SetSecurityDescriptor. Я уже неоднократно поднимал вопрос работы с SecurityDescriptor в PowerShell, поэтому приступим сразу к решению задачи.
Как видно из картинки, нас будет интересовать объект 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 используются различные именования свойств дескриптора безопасности, когда в этом явных причин нету? Например:
Вот 4 WMI класса управления ACL списками различных объектов, с которыми я недавно работал и имеем 3 различных именования свойства дескриптора безопасности и Win32_Share использует даже другое название метода (SetShareInfo), хотя этот метод использует тот же Win32_SecurityDescriptor. Но это уже оффтопик и личные размышления. Вот :)
Comments: