Примечание: данный пост перепечатан в связи с закрытием бложиков на spaces.live.com, как имеющий какую-то ценность для автора и/или читателей.
В предыдущей статье я сделал вводну часть по управлению сетевыми папками в PowerShell. В ней я не рассматривал вопрос управления Share Permissions, т.к. это дело весьма непростое. Итак, что нам для этого потребуется? А потребуются нам следующие классы WMI:
Основной класс, который позволит нам извлечь параметры безопасности - Win32_LogicalShareSecuritySettings. Как я говорил в предыдущей статье, упрощённый вариант создания новой сетевой папки - не самый лучшй вариант создания для последующего управления. Сейчас я продемонстрирую вам проблему:
[C:\] ([wmiClass] 'Win32_share').Create("C:\Test", "UserShare", "0", "100", "Network share for users") __GENUS : 2 __CLASS : __PARAMETERS __SUPERCLASS : __DYNASTY : __PARAMETERS __RELPATH : __PROPERTY_COUNT : 1 __DERIVATION : {} __SERVER : __NAMESPACE : __PATH : ReturnValue : 0 [C:\] Get-WmiObject Win32_Share Name Path Description ---- ---- ----------- C$ C:\ Default share UserShare C:\Test Network share for users CertEnroll C:\WINDOWS\system32\certsrv\CertEnroll IPC$ Remote IPC ADMIN$ C:\WINDOWS Remote Admin SYSVOL C:\WINDOWS\SYSVOL\sysvol Logon server share NETLOGON C:\WINDOWS\SYSVOL\sysvol\contoso.com... Logon server share [C:\] Get-WmiObject Win32_LogicalShareSecuritySetting | Format-List [a-z]* Caption : Security settings of CertEnroll ControlFlags : 32772 Description : Security settings of CertEnroll Name : CertEnroll SettingID : Scope : System.Management.ManagementScope Path : \\DC1\root\cimv2:Win32_LogicalShareSecuritySetting.Name="CertEnroll" Options : System.Management.ObjectGetOptions ClassPath : \\DC1\root\cimv2:Win32_LogicalShareSecuritySetting Properties : {Caption, ControlFlags, Description, Name...} SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...} Qualifiers : {dynamic, Locale, provider, UUID} Site : Container : Caption : Security settings of SYSVOL ControlFlags : 32772 Description : Security settings of SYSVOL Name : SYSVOL SettingID : Scope : System.Management.ManagementScope Path : \\DC1\root\cimv2:Win32_LogicalShareSecuritySetting.Name="SYSVOL" Options : System.Management.ObjectGetOptions ClassPath : \\DC1\root\cimv2:Win32_LogicalShareSecuritySetting Properties : {Caption, ControlFlags, Description, Name...} SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...} Qualifiers : {dynamic, Locale, provider, UUID} Site : Container : Caption : Security settings of NETLOGON ControlFlags : 32772 Description : Security settings of NETLOGON Name : NETLOGON SettingID : Scope : System.Management.ManagementScope Path : \\DC1\root\cimv2:Win32_LogicalShareSecuritySetting.Name="NETLOGON" Options : System.Management.ObjectGetOptions ClassPath : \\DC1\root\cimv2:Win32_LogicalShareSecuritySetting Properties : {Caption, ControlFlags, Description, Name...} SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...} Qualifiers : {dynamic, Locale, provider, UUID} Site : Container : [C:\]
Итак, первой строчкой мы создали новую шару. В выводе команды я выделил последнюю строчку ReturnValue и его значение 0. Ноль нам говорит, что шара создалась успешно. Чтобы в этом убедиться в следующей строчке я ввёл команду, которая показывает список всех расшаренных папок на локальной машине. Я так же выделил жирным строчку, которая показывает нашу шару. Третей командой я вывел параметры безопасности всех доступных сетевых ресурсов на локальной машине. И что мы видим? Точнее, чего мы не видим - а не видим мы административных шар (которые заканчиваются на знак $) и, так же, не увидели созданной нами сетевой папки UserShare. И это означает, что мы в последующем через классы WMI не сможем извлекать параметры безопасности для последующей обработки. Поэтому более правильным будет создание новой сетевой папки с явным назначением Share Permissions для неё.
Если посмотреть в описание метода Create класса Win32_Share, то там видно, что в качестве последнего параметра выступает класс Win32_SecurityDescriptor. Посмотрим, что нужно для него. Я для него нужны классы Win32_Ace и Win32_Trustee. Ну что ж, приступим к разбору. Итак, у нас есть уже шара с дефолтными правами. Чтобы создать новый набор прав нам нужно знать следующее:
Теперь нужно объявляем переменные для них:
$share="Test" $user = "Accounting" $mask = "Сhange"
Теперь объявляем вышеуказанные классы WMI:
$SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance() $ace = ([WMIClass] "Win32_Ace").CreateInstance() $Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()
Теперь преобразовываем имя пользователя/группы в его SID. Преобразование я описывал вот в этой статье:
$SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier])
В переменной $SID теперь хранится SID данного пользователя. Правда, здесь он содержится в строковом формате. Но если мы посмотрим в свойства класса Win32_Trustee, то увидим, что тип данных для свойства SID должен быть: Data type: uint8 array. Получить такой массив можно следующей конструкцией:
[byte[]] $SIDArray = ,0 * $SID.BinaryLength $SID.GetBinaryForm($SIDArray,0)
Здесь мы объявили переменную $SIDArray с длиной равной BinaryLength. Длину BinaryLength можно узнать выполнив преобразование имени пользователя в SID, как это рассказано в ссылке приведённой выше. Вот теперь у нас переменная $SID содержит уже байтовый массив из SID пользователя/группы, который получили из $SIDArray. С этим мы закончили. Теперь эти данные можно передать в класс Win32_Trustee, который является абстрактным классом для описания пользователя или группы. Он нам потребуется для дальнейшей передачи этих данных в класс Win32_Ace.
$Trustee.Name = $user $Trustee.SID = $SIDArray
Сам класс Win32_Trustee уже объявлен, поэтому мы просто добавляем значения свойств для этого класса.
Примечание: На первый взгляд это может показаться очень сложным материалом (мне он тоже дался не очень легко). По сути здесь присутствует множественная инкапсуляция данных и преобразование этих данных из одного типа в другой. Т.е. из исходных данных у нас есть разве что имя пользователя/группы и тип устанавливаемых прав. Если внимательно изучить ссылки, которые приведены в самом начале, то можно увидеть следующую последовательность преобразований:
Вот так выглядит общая схема последовательности действий, которая является шаблоном для данной операции.
А теперь продолжим. Теперь нам нужно преобразовать права доступа в класс FileSystemRights. Преобразование типа прав так же было ранее мною описано в первой части Управление ACL в PowerShell. Пара слов о том, чем отличаются права Read, Change и Full Control в контексте разрешений сетевого ресурса от контекста разрешений NTFS. Этим правам в NTFS сопоставляются Read + Execute, Modify и Full Control соответственно и плюс право Synchonize. Поэтому давайте запишем маску доступа в переменную $ace:
$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"Modify, Synchronize"
Ещё в начале мы объявили переменную $mode и вписали текстовое значение типа доступа. Здесь я его использовать не буду, а лишь преобразую его сразу в аналог FileSystemRights. Теперь в этот класс нужно передать тип доступа (Allow/Deny) в класс Win32_Ace. первой части Управление ACL в PowerShell я показывал команду, которая позволяет перечислять доступные значения свойств класса. Применим его снова, но в контексте AceType
[system.enum]::getnames([System.Security.AccessControl.AceType])
И в этом списке нас будут интересовать только первые 2
Их можно указывать по порядковым номерам. Например, 0 будет означать AccessAllowed, а 1 - AccessDenied. Теперь заканчиваем второй этап и делаем третий этап нашей "инкапсуляции":
$ace.AceType = 0 $ace.Trustee = $Trustee
Далее, следуя нашему шаблонному алгоритму нужно сформированную переменную $ace передать в класс Win32_SecurityDescriptor. Здесь нам нужно будет заполнить только свойство DACL, которое будет содержать массив параметров безопасности из переменной $ace:
$SD.DACL = $ace
Ну что ж, вот теперь у нас полностью готов объект SecurityDescriptor для применения его в качестве параметра для метода SetShareInfo. Для начала нам нужно указать шару, для которой будет применяться данный метод:
$share = get-WmiObject win32_share -filter "name='$share'"
Теперь нужно получить шаблон набора свойств для метода SetShareInfo:
$inParams = $share.PsBase.GetMethodParameters("SetShareInfo")
Так мы получили форму с необходимыми свойствами и типами данных, которые мы должны заполнить. Метод SetShareInfo использует следующий синтаксис (приведено из ссылки MSDN):
uint32 SetShareInfo( [in] uint32 MaximumAllowed, [in, optional] string Description, [in] Win32_SecurityDescriptor Access );
Это нам говорит, что требуется именно параметр Access, который нужно заполнить данными из класса Win32_SeceurityDescriptor. У нас уже есть переменная $SD, в которой заполнен параметр DACL. Вот теперь эту всю переменную запишем в качестве параметра Access в форму набора свойств:
$inParams.Access = $SD
Ну вот и всё. Теперь нам только осталось записать всё это в нашу сетевую папку:
$share.PsBase.InvokeMethod("setshareinfo", $inParams, $null)
Здесь я указал шару и использовал PsBase.InvokeMethod, чтобы вызвать метод SetShareInfo и после указания метода перечислил передаваемые прараметры Ну и конечный результат всех наших терзаний в едином целом:
$share="UserShare" ([wmiClass] 'Win32_share').Create("C:\Test", $share, "0", "100", "Network share for users") $user = "Domain Users" $mask = "Change" $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]"Modify, Synchronize" $ace.AceType = 0 $ace.Trustee = $Trustee $SD.DACL = $ace $share = get-wmiObject win32_share -filter "name='$share'" $inParams = $share.psbase.GetMethodParameters("SetShareInfo") $inParams.Access = $SD $share.psbase.invokemethod("setshareinfo", $inParams, $null)
Ну и, собственно, вторая цель, которой мы добивались:
[C:\] gwmi Win32_LogicalShareSecuritySetting | fl [a-z]* Caption : Security settings of UserShare ControlFlags : 32772 Description : Security settings of UserShare Name : UserShare SettingID : Scope : System.Management.ManagementScope Path : \\DC1\root\cimv2:Win32_LogicalShareSecuritySetting.Name="UserShare" Options : System.Management.ObjectGetOptions ClassPath : \\DC1\root\cimv2:Win32_LogicalShareSecuritySetting Properties : {Caption, ControlFlags, Description, Name...} SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...} Qualifiers : {dynamic, Locale, provider, UUID} Site : Container : Caption : Security settings of CertEnroll ControlFlags : 32772 Description : Security settings of CertEnroll Name : CertEnroll SettingID : Scope : System.Management.ManagementScope Path : \\DC1\root\cimv2:Win32_LogicalShareSecuritySetting.Name="CertEnroll" Options : System.Management.ObjectGetOptions ClassPath : \\DC1\root\cimv2:Win32_LogicalShareSecuritySetting Properties : {Caption, ControlFlags, Description, Name...} SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...} Qualifiers : {dynamic, Locale, provider, UUID} Site : Container : Caption : Security settings of SYSVOL ControlFlags : 32772 Description : Security settings of SYSVOL Name : SYSVOL SettingID : Scope : System.Management.ManagementScope Path : \\DC1\root\cimv2:Win32_LogicalShareSecuritySetting.Name="SYSVOL" Options : System.Management.ObjectGetOptions ClassPath : \\DC1\root\cimv2:Win32_LogicalShareSecuritySetting Properties : {Caption, ControlFlags, Description, Name...} SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...} Qualifiers : {dynamic, Locale, provider, UUID} Site : Container : Caption : Security settings of NETLOGON ControlFlags : 32772 Description : Security settings of NETLOGON Name : NETLOGON SettingID : Scope : System.Management.ManagementScope Path : \\DC1\root\cimv2:Win32_LogicalShareSecuritySetting.Name="NETLOGON" Options : System.Management.ObjectGetOptions ClassPath : \\DC1\root\cimv2:Win32_LogicalShareSecuritySetting Properties : {Caption, ControlFlags, Description, Name...} SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...} Qualifiers : {dynamic, Locale, provider, UUID} Site : Container : [C:\]
Вот теперь мы увидели нашу шару через Win32_LogicalShareSecuritySetting и благодаря чему в последующем можно будет работать с DACL списком этого сетевого ресурса. Как говорит /\/\o\/\/ - Enjoy! :)
Кстати, хотелось бы отметить, что по этому вопросу /\/\o\/\/ в своё время проделал очень большую работу, как часть вопроса управления безопасностью объектов в Windows и значительная часть материала была написана с использованием его наработок.
Давайте, теперь поговорим о том, как добавить несколько участников безопасности. Суть процесса не изменится. Для решения данной задачи я воспользуюсь следующими приёмами:
Если посмотреть код выше, то для добавления других пользователей/групп в Share Permissions, то нетрудно предположить, что нам нужно записать в $SD.DACL столько ACE, сколько пользователей/групп нам требуется завести. Следовательно, тут нужен цикл. Имена пользователей/групп и назначемые им права будут выбираться из hashtables. Hashtables - по сути является массивом сопоставлений. Он представляется двумя столбцами - именем параметра в первом столбце и его значением во втором. В нашем случае в качестве параметра будет выступать пользователь/группа, а его значение будет - тип предоставляемого доступа. Hashtables, на мой вгляд, хорошо описаны в книге PowerShell in Action (сегодня постараюсь не забыть прикрепить к блогу список полезной литертуры по PowerShell). Т.к. набор прав у нас будет для каждого пользователя/группы разный, поэтому я применю конструкцию Switch, которая будет сопоставлять текстовому значению типа доступа его значению в классе FileSystemRights.
Итак, давайте проведём анализ, какую часть кода нам нужно поместить в цикл для многократной обработки и получения для пользователя собственного ACE. У нас индивидуальным для каждого пользователя будет SID, Trustee, ACE. Остальное же будет универсальным (общим) для всех пользователей. Вот его и поместим в цикл foreach. Но для foreach нужно передать параметры - имя пользователя/группы и маску доступа (тип предоставляемого доступа). Для этого, как я уже говорил, нам потребуется Hashtables. Общий формат хэш-таблиц выглидт следующим образом:
<hashLiteral> = '@{' <keyExpression> '=' <pipeline> [ <separator> <keyExpression> '=' <pipeline> ] * '}'
<separator> = ';' | <newline>
Поэтому изменив предыдущий код мы заменим переменные $user и $mode на одну переменную $user с указанием имён пользователей и маски предоставляемого доступа согласно формату хэш-таблиц:
$user = @{"Everyone" = "Read"; "Administrators" = "FullControl"; "Domain Users" = "Change"}
Если после этой строки в консоли набрать $user, то мы получим нашу хэш-таблицу:
[C:\] $user Name Value ---- ----- Everyone Read Domain Users Change Administrators FullControl
Теперь готовим цикл foreach:
$user.keys | foreach {
Мы передали содержимое хэш-таблицы в цикл. Теперь нужно изменить несколько строк кода. А именно: мы передадим в цикл сперва имя пользователя/группы. Для этого нужно изменить строчку преобразования аккаунта, чтобы туда при каждой итерации цикла попадало новое имя пользователя:
$SID = (new-object security.principal.ntaccount $_ ).translate([security.principal.securityidentifier])
В комбинацию "$_" будут попадать только значения столбца Name из нашей хэш-таблицы (если указать, как и раньше $user, то туда попадёт вся хэш-таблица и скрипт даст ошибку преобразования аккаунта). Далее, у нас будет передаваться индивидуальная маска доступа. В предыдущем примере мы использовали всего лишь одну строчку, которая начиналась с $ace.AccessMask. В том примере было всё просто - один пользователь = одна маска. Теперь пользователей/групп уже 3 и маски тоже 3. Здесь мы воспользуемся конструкцией Switch, которая будет принимать наше текстовое значение искать ему сопоставление внутри конструкции и выполнять только одно действие:
switch ($($user[$_])) { "FullControl" {$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"FullControl, Synchronize"} "Change" {$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"Modify, Synchronize"} "Read" {$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"ReadAndExecute, Synchronize"} }
Я выделил $($user[$_]), для того, чтобы показать, как выбирать только значения из столбца Value нашей хэш-таблицы. Т.к. эта часть кода находится в теле цикла foreach, то сюда будет подставляться новое значение с каждой итерацией. Ну и последний, завершающий штрих - запись нескольких ACE в массив DACL. Если в предыдущем примере мы делали просто
$SD.DACL = $ace
То теперь такой вариант не подойдёт. А почему? А потому, что операция присвоения каждый раз будет заменять значение DACL последним ACE. Т.к. DACL у нас является массивом данных Win32_Ace, то для добавления новых ACE воспользуемся оператором добавления - "+="
$SD.DACL += $ace
Вот теперь в SD.DACL каждый новый ACE будет добавляться как новый элемент массива. Кстати, говоря, именно этой строчкой мы завершаем цикл foreach, т.к. SecurityDescriptor у нас уже готов и его уже можно как обычно отправлять дальше в код.
Подытожив всю полученную информацию мы в конечном итоге получим вот такой код:
$share="UserShare" ([wmiClass] 'Win32_share').Create("C:\Test", $share, "0", "100", "Network share for users") $user = @{"Everyone" = "Read"; "Administrators" = "FullControl"; "Domain Users" = "Change"} $SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance() $ace = ([WMIClass] "Win32_Ace").CreateInstance() $Trustee = ([WMIClass] "Win32_Trustee").CreateInstance() $user.keys | foreach { $SID = (new-object security.principal.ntaccount $_ ).translate([security.principal.securityidentifier]) [byte[]] $SIDArray = ,0 * $SID.BinaryLength $SID.GetBinaryForm($SIDArray,0) $Trustee.Name = $_ $Trustee.SID = $SIDArray switch ($($user[$_])) { "FullControl" {$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"FullControl, Synchronize"} "Change" {$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"Modify, Synchronize"} "Read" {$ace.AccessMask = [System.Security.AccessControl.FileSystemRights]"ReadAndExecute, Synchronize"} } $ace.AceType = 0 $ace.Trustee = $Trustee $SD.DACL += $ace.psobject.baseobject} $share = Get-WmiObject win32_share -filter "name='$share'" $inparams = $share.psbase.GetMethodParameters("SetShareInfo") $inParams.Access = $SD $share.psbase.invokemethod("setshareinfo", $inParams, $null)
Вот так немного модифицировав код мы добились более широкого функционала, а именно - возможность добавления множества ACE в едином теле скрипта.
Здесь я переступил логику. По логике следовало сначала разобрать чтение Share Permissions, а потом уже запись, но ввиду одного момента, о котором я рассказал в начале пришлось сначала изучить возможность установки DACL, а потом уже чтение их из Share Permissions. Об этом я уже расскажу следующий раз. Ждите продолжения :-)
Comments: