Contents of this directory is archived and no longer updated.

Примечание: данный пост перепечатан в связи с закрытием бложиков на 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. Ну что ж, приступим к разбору. Итак, у нас есть уже шара с дефолтными правами. Чтобы создать новый набор прав нам нужно знать следующее:

  • Имя сетевой папки для которой будем изменять права сетевого доступа;
  • имя пользователя, которому будут назначены права;
  • тип доступа, который нужно назначить (FullControl, Change, Read).

Теперь нужно объявляем переменные для них:

$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 уже объявлен, поэтому мы просто добавляем значения свойств для этого класса.

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

  • имя пользователя/группы в SID;
  • SID в байтовый массив SIDArray;
  • передача SIDArray и имени в Win32_Trustee (первый этап т.н. "инкапсуляции");
  • Передача преобразованного типа прав доступа в класс Win32_Ace (второй этап инкапсуляции);
  • Передача готового Trustee в класс Win32_Ace (третий этап "инкапсуляции");
  • Передача сформированного ACE в класс Win32_SecurityIdentifier (четвёртый этап "инкапсуляции");
  • Передача уже сформированного SecurityDescriptor в метод SetShareInfo;
  • И применение самого метода SetShareInfo для записи параметров безопасности (прав доступа) в ACL самой шары.

Вот так выглядит общая схема последовательности действий, которая является шаблоном для данной операции.

А теперь продолжим. Теперь нам нужно преобразовать права доступа в класс 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

  • AccessAllowed;
  • AccessDenied.

Их можно указывать по порядковым номерам. Например, 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 и значительная часть материала была написана с использованием его наработок.

Давайте, теперь поговорим о том, как добавить несколько участников безопасности. Суть процесса не изменится. Для решения данной задачи я воспользуюсь следующими приёмами:

  • циклом foreach;
  • hashtables;
  • конструкцией switch.

Если посмотреть код выше, то для добавления других пользователей/групп в 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. Об этом я уже расскажу следующий раз. Ждите продолжения :-)


Share this article:

Comments:

Comments are closed.