В предыдущей части мы рассмотрели чтение Share Permissions, их редактирование и удаление ACE из полного списка ACL. Здесь осталось рассмотреть вопрос добавления участников безопасности в DACL сетевой шары. Этот процесс, к сожалению, не такой и простой, как может показаться, но тем не менее его тоже нужно решать. Для решения этой задачи нам нужно создать такой же объект с такими же свойствами как и содержимое $ShareInfo. Давайте посмотрим, какими свойствами обладают элементы массива $ShareInfo:
[C:\] $shareinfo | Get-Member
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method System.Boolean Equals(Object obj)
GetHashCode Method System.Int32 GetHashCode()
GetType Method System.Type GetType()
ToString Method System.String ToString()
AccessMask NoteProperty System.UInt32 AccessMask=1245631
AceFlags NoteProperty System.UInt32 AceFlags=0
AceType NoteProperty System.UInt32 AceType=0
Description NoteProperty System.Management.Automation.PSObject
Domain NoteProperty System.Management.Automation.PSObject
Name NoteProperty System.Management.Automation.PSObject
Path NoteProperty System.Management.Automation.PSObject
SID NoteProperty System.Management.Automation.PSObject
User NoteProperty System.Management.Automation.PSObject
Нас тут будут интересовать только NoteProperty. Давайте теперь исходя из этих данных создадим свой объект, который будет обладать вот этими свойствами. Тип объекта будет такой же - System.Management.Automation.PSObject (без Custom). Новый объект создаётся командной New-Object, а члены объекта создаются командой Add-Member:
# создаём новый объект с типом System.Management.Automation.PSObject
$AddInfo = new-object System.Management.Automation.PSObject
# добавляем по очереди членов NoteProperty объекта как и в исходном варианте
$AddInfo | add-member NoteProperty Name ([PSObject])
$AddInfo | add-member NoteProperty Path ([PSObject])
$AddInfo | add-member NoteProperty Description ([PSObject])
$AddInfo | add-member NoteProperty AccessMask ([uint32])
$AddInfo | add-member NoteProperty AceFlags ([uint32])
$AddInfo | add-member NoteProperty AceType ([uint32])
$AddInfo | add-member NoteProperty User ([PSObject])
$AddInfo | add-member NoteProperty Domain ([PSObject])
$AddInfo | add-member NoteProperty SID ([PSObject])
Теперь можно посмотреть на результаты нашей работы:
[C:\] $AddInfo
Name :
Path :
Description :
AccessMask : 0
AceFlags : 0
AceType : 0
User :
Domain :
SID :
Ну что ж, уже лучше, теперь можно заполнять эти поля в соответствии с нашими требованиями. Но чтобы не заполнять все поля вручную, предполагается, что для добавления нового участника безопасности в Share Permissions пользователь укажет только имя сетевой шары, имя пользователя, маску доступа и тип доступа (Allow/Deny). Поэтому нам потребуется вытащить информацию о текущей шаре (которая общая для всей шары, как имя, путь, описание и т.д.) и передать эти значения в новую переменную $AddInfo. Остальную часть информации мы обработаем на основании уже переданных параметров и запишем оставшиеся поля переменной $AddInfo, которая в конечном итоге будет содержать всю необходимую информацию. Т.к. уже неоднократно говорилось в предыдущих частях, что метод SetShareInfo перезаписывает полностью информацию о сетевой шаре, поэтому для сохранения существующих ACE мы произведём уже известным способом чтение существующих DACL и сделаем инкремент (добавим к существующим DACL нами созданный DACL). Когда все данные будут скомпонованы мы произведём запись обновлённого списка DACL в ACL шары. Итак, поехали:
# принимаем вводные параметры от пользователя из командной строки
param ($share, $user, $AccessMask, $AceType)
# предполагается, что данный скрипт выполняет только добавление участников безопасности
# поэтому сразу создаём новый объект с необходимыми членами и указанием типа принимаемых
# данных
$AddInfo = New-Object System.Management.Automation.PSObject
$AddInfo | Add-Member NoteProperty Name ([PSObject])
$AddInfo | Add-Member NoteProperty Path ([PSObject])
$AddInfo | Add-Member NoteProperty Description ([PSObject])
$AddInfo | Add-Member NoteProperty AccessMask ([uint32])
$AddInfo | Add-Member NoteProperty AceFlags ([uint32])
$AddInfo | Add-Member NoteProperty AceType ([uint32])
$AddInfo | Add-Member NoteProperty User ([PSObject])
$AddInfo | Add-Member NoteProperty Domain ([PSObject])
$AddInfo | Add-Member NoteProperty SID ([PSObject])
# зная имя сетевой шары делаем её поиск и копируем информацию о имени, пути
# и описании (поле Description) и выставим прочие параметры
$CustomShare = Get-WmiObject Win32_Share -filter "name = '$share'"
$AddInfo.Name = $share
$AddInfo.Path = $CustomShare.Path
$AddInfo.Description = $CustomShare.Description
$AddInfo.Domain = $null
$AddInfo.AceFlags = 3
# далее заполняется информация о пользователе и его доступе, поэтому
# дальше мы ничего не копируем, а обрабатываем уже переданные параметры:
$AddInfo.User = $user
# преобразовываем маску доступа из текстовой в численный формат с использованием
# конструкции Switch.
switch ($AccessMask) {
"Full" {$AddInfo.AccessMask = 2032127}
"Change" {$AddInfo.AccessMask = 1245631}
"Read" {$AddInfo.AccessMask = 1179817}
}
# таким же образом обрабатываем и переменную $AceType:
switch ($AceType) {
"Allow" {$AddInfo.AceType = 0}
"Deny" {$AddInfo.AceType = 1}
}
# заполняем последнее поле SID путём трансляции имени пользователя/группы в его SID
$AddInfo.SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier])
# теперь считываем текущий список DACL с указанной сетевой шары в переменную $ShareInfo
$shares = Get-WmiObject Win32_Share -filter "name='$share'"
$Shareinfo = @()
foreach ($share in $shares) {
$shareSec = Get-WmiObject Win32_LogicalShareSecuritySetting -filter "name='$($share.name)'"
if($shareSec) {
$sd = $sharesec.GetSecurityDescriptor()
$ShareInfo += $sd.Descriptor.DACL | ForEach-Object {
$_ | select @{e={$share.name};n='Name'},
@{e={$share.Path};n='Path'},
@{e={$share.Description};n='Description'},
AccessMask,
AceFlags,
AceType,
@{e={$_.trustee.Name};n='User'},
@{e={$_.trustee.Domain};n='Domain'},
@{e={$_.trustee.SIDString};n='SID'}
}
}
}
# теперь делаем добавление (инкремент) созданного нами массива объектов с зполненными полями к
# существующему массиву объектов $ShareInfo
$ShareInfo += $AddInfo
# Можно для верности убедиться, что $ShareInfo обладает всей необходимой информацией, которую
# теперь можно записать в шару. В принципе, эта строчка несёт в себе лишь отладочную информацию
# и когда отладка будет завершена эту строчку можно будет удалить или закомментировать для
# отладки скрипта в будущем.
$ShareInfo
# Если всё в порядке, то можно перезаписывать эти данные в DACL указанной шары:
$ShareInfo | select -unique name, Path, Description | ForEach-Object {
$name = $_.name
$path = $_.Path
$description = $_.Description
"Processing : $name $path $description"
$SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance()
$ace = ([WMIClass] "Win32_Ace").CreateInstance()
$Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()
$sd.DACL = @()
$ShareInfo | where {$_.name -eq $name} | ForEach-Object {
$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
$sd.DACL += $ACE.psObject.baseobject
}
$inParams = $CustomShare.psbase.GetMethodParameters("SetShareInfo")
$inParams.Access = $SD
$CustomShare.psbase.invokemethod("setshareinfo", $inParams, $null)
}
Формат запуска данного скрипта из командной строки CMD или меню Run будет следующим:
powershell %path%\AddUser.ps1 -share "Имя сетевой шары" -user "имя добавляемой группы" -mask "маска доступа, Full/Change/Read" -type "тип доступа, Allow/Deny"
эту команду следует выполнять в одну строчку. В качестве переменной %path% нужно указать путь к папке со скриптом, если он заранее не добавлен в системную переменную %path%. В качестве маски доступа нужно указать одно из 3-х значений, которое может быть Full, Change или Read. Более одного параметра указывать нельзя. Ну и в качестве типа доступа указать либо Allow, что даст доступ, либо Deny, что явно запретит доступ.
Ну вот как бы и всё на данном этапе. Здесь я много чего не пояснял, т.к. достаточно (в моём понимании) разобрал в предыдущих 3-х частях о работе с сетевыми шарами в PowerShell. Но это ещё не всё. Главный девиз PowerShell - быть удобным для использования и кратким для написания (это я сам придумал :) ), однако при работе с сетевыми шарами это совершенно не прослеживается и даже может создаться впечатление громоздкости (хотя тот, кто считает этот код громоздким может написать скрипт короче на VBS с использованием только WMI/.NET :) ), поэтому в следующей части я постараюсь исправить сей момент путём написания единого (относительно компактного по возможности) и представить его как готовое решение, которое в работе будет действительно удобным. Не отключайтесь, продолжение обязательно будет :)
p.s. Данный скрипт не обязательно является самым простым решением, т.к. возможно, что данную операцию можно провести более удобным и изящным способом (хотя, после чтения документации на MSDN мне так не кажется, что это возможно), но в любом случае адекватные замечания/поправки/дополнения к этому скрипту всячески приветствуются.