Ну что ж, настало время подвести итоги по материалу управления сетевыми шарами и управлению доступа к ним. Как и обещал, я написал скрипт, который выполнен в виде функций при помощи которого можно действительно легко и просто управлять самими сетевыми папками и их безопасностью из PowerShell. Интеграция данного скрипта в профиль PowerShell позволяет использовать уже готовые функции как простые командлеты. Я считаю, это действительно большим плюсом, т.к. до этого администратору для решения этих задач приходилось писать длиннющий код в каждом скрипте, который касался безопасности сетевых шар. Итак, какой же функционал заложен в данный скрипт:
Касательно последнего пункта, то хочу отметить, что импорт при отсутствии наличия папки для расшаривания создаст папку и расшрарит с данными из CSV файла. Сначала я приведу список команд, которые доступны при использовании скрипта и их синтаксис:
Ну и собственно сам скрипт с некоторыми комментариями по работе самих функций. Для желающих подробно изучить скрипт я не делал особо комментариев, т.к. построение кода описано в предыдущих частях по управлению безопасностью сетевых папок. Ссылки на предыдущие части:
# внутренняя функция, которая преобразовывает числовой код возврата операции записи ACL
# в текстовое значение.
function _ShareUtils_Get-Code ($share) {
switch ($Share.ReturnValue) {
"0" {"Успешно"}
"2" {"Отказано в доступе"}
"8" {"Неизвестная ошибка"}
"9" {"Указано недопустимое имя шары"}
"21" {"Указан неправильный параметр"}
"22" {"Сетевая шара уже существует"}
"23" {"Путь перенаправлен"}
"24" {"Указан неверный путь"}
"25" {"Сетевое имя не найдено"}
}
}
# основная функция экспорта сведений о сетевых папках в CSV файл.
function Export-ShareInfo ($path, $name) {
# если переменная $name пустая, то функция вовзращает все сетевые папки с типом DiskDrive
if ($name -ne $null) {
$shares = Get-WmiObject Win32_Share -filter "name = '$name'"
} Else {$shares = Get-WmiObject Win32_Share -filter 'type = 0'}
$Shareinfo = @()
# цикл извлечения сведений о каждой сетевой папке в переменную $ShareInfo
foreach ($share in $shares) {
$ShareSec = Get-WmiObject Win32_LogicalShareSecuritySetting -filter "name='$($share.name)'"
if($shareSec) {
$sd = $sharesec.GetSecurityDescriptor()
$ShareInfo += $SD.Descriptor.DACL | % {
$_ | 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'}
}
}
}
# если переменная $path не передана, то сведения о сетевых папках передаётся в вызывющую
# функцию для последующей обработки, в частности добавления и удаления ACE из ACL
# списка сетевой папки
if ($path -eq $null) {
$shareinfo} else {
# собственно сам экспорт содержимого $ShareInfo в CSV файл
$ShareInfo | select Name, Path, Description, User, Domain, SID, AccessMask, AceFlags, AceType | export-csv -noType $path
# если указан путь к CSV файлу, то после экспорта данных в файл проверяется, что файл действительно был создан
if (Test-Path $path) {Write-Host "Выполнено!"} else {
Write-Warning "Не удалось создать файл $path. Возможно у вас не хватает прав или путь недоступен."}
}}
# внутренняя функция для записи уже сформированной переменной $ShareInfo в ACL сетевой папки
function _ShareUtils_WriteShare ($ShareInfo, $shares, $param) {
$ShareInfo | select -unique name, Path, Description | ForEach-Object {
$name = $_.name
$path = $_.Path
$description = $_.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
}
# здесь проверяется наличие промежуточного параметра $param, который после сборки определяет
# тип записи. Если $param пустой, то производится запись только в конкретную сетевую папку. Если
# же $param не пустой, то при записи и отсутствии сетевой папки она будет создана и в неё будут записаны
# данные из CSV файла. Конструкция после Else используется только при импорте данных о сетевых папках
# включая Access из заранее подготовленного CSV файла.
if ($param -eq $null) {
$inParams = $shares.psbase.GetMethodParameters("SetShareInfo")
$inParams.Access = $SD
$write = $shares.psbase.invokemethod("setshareinfo", $inParams, $null)
Write-Host "Запись DACL сетевой папки:"
_ShareUtils_Get-Code $write}
else {
$shares = ([WMIClass] "Win32_Share")
$inParams = $shares.psbase.GetMethodParameters("Create")
$inParams["Name"] = $_.name
$inParams["Type"] = 0
$inParams["Path"] = $_.Path
$inParams["Description"] = $_.Description
$inParams["Access"] = $SD.PsObject.BaseObject
$write = $shares.psbase.invokemethod("Create", $inParams, $null)
Write-Host "Обработка сетевой папки $name по пути $path:"
_ShareUtils_Get-Code $write
}}}
# основная функция для импорта данных о сетевых папках из CSV файла. Переменная $path
# должна содержать путь к CSV файлу. Внутри функции проверяется, чтобы был указан
# верный путь к CSV файлу.
function Import-ShareInfo ($path) {
if (Test-Path $path) {
$param = "param"
$ShareInfo = Import-Csv $path
_ShareUtils_WriteShare $ShareInfo -param $param}
Else {Write-Warning "путь к CSV файлу указан неверный!"
}}
# основная функция для компоновки объекта $AddInfo параметрами безопасности, которые
# включают в себя как имя сетевой папки, имени пользователя, который должен иметь к ней
# доступ, и типах доступа, как чтение/запись и действие разрешено/запрещено. Переменная
# $param определяет действие с готовым объектом - отправить на запись сразу (при этом все
# существующие разрешения будут удалены и заменены только данными из текущего объекта)
# или вернуть обратно в вызывющую функцию, для присоединения этого объекта к уже имеющимся,
# для окончательной компоновки объекта с полным списком ACL.
function Set-SharePermission ($name, $user, $AceType, $AccessMask, $param) {
$shares = gwmi Win32_share -Filter "name = '$name'"
if ($shares -eq $null) {Write-Warning "Указанная сетевая шара не найдена"}
else {
# здесь я использовал хэш-таблицы для преобразования текстовых значений маски и типа
# доступа, которые вводит пользователь в числовые значения, которые затем транслируются и
# и помещаются в текущий объект с параметрами безопасности.
$masks = @{FullControl = 2032127; Change = 1245631; Read = 1179817}
$types = @{Allow = 0; Deny = 1}
$AddInfo = New-Object System.Management.Automation.PSObject
# здесь происходит инициализация свойств объекта. Значение каждого параметра приравнял к $null
# для того, чтобы при отсутствии каких-либо данных они либо оставались пустыми, либо заполнялись
# системой автоматически.
$AddInfo | Add-Member NoteProperty Name ([PSObject]$null)
$AddInfo | Add-Member NoteProperty Path ([PSObject]$null)
$AddInfo | Add-Member NoteProperty Description ([PSObject]$null)
$AddInfo | Add-Member NoteProperty AccessMask ([uint32]$null)
$AddInfo | Add-Member NoteProperty AceFlags ([uint32]$null)
$AddInfo | Add-Member NoteProperty AceType ([uint32]$null)
$AddInfo | Add-Member NoteProperty User ([PSObject]$null)
$AddInfo | Add-Member NoteProperty Domain ([PSObject]$null)
$AddInfo | Add-Member NoteProperty SID ([PSObject]$null)
# собственно заполнение свойств созданного объекта данными, которые были переданы из вызывющей
# функции.
$AddInfo.Name = $name
$AddInfo.Path = $shares.Path
$AddInfo.Description = $Shares.Description
$AddInfo.User = $user
$AddInfo.SID = (new-object security.principal.ntaccount $user).translate([security.principal.securityidentifier])
$AddInfo.AccessMask = $masks.$AccessMask
$AddInfo.AceType = $types.$AceType
# тут так же использовалась временная переменная $param, которая определяет дальнейшее действие
# с данным объектом - отправка объекта на запись (только при использовании функции Set-SharePermission),
# либо возврат в вызываемую функцию (только при использовании функции Add-SharePermission).
if ($param -ne $null) {
$AddInfo} else {
_ShareUtils_WriteShare $AddInfo $shares
}}}
# основная функция для добавления участников безопасности к имеющимуся списку ACL. Данная функция
# сперва использует функцию экспорта для извлечения сведений об указанной сетевой папке, после чего
# вызывается функция Set-SharePermission в качестве промежуточной функции, т.к. в неё передаётся перменная
# $param, то вызываемая функция не будет записывать новый ACL, а вернёт скомпонованный объект $AddInfo.
function Add-SharePermission ($name, $user, $AceType, $AccessMask) {
$shares = gwmi Win32_share -Filter "name = '$name'"
if ($shares -eq $null) {Write-Warning "Указанная сетевая шара не найдена"}
else {
# здесь нужно быть внимательным, т.к. нужно обязательно использовать обозначение массива @() для того,
# чтобы переменная $ShareInfo смогла бы содержать массив объектов с параметрами безопасности. Один объект
# содержит один ACE для каждого пользователя/группы. Если не использовать обозначение массива, то данная
# переменная сможет содержать только один объект (т.е. только одного участника безопасности).
$ShareInfo = @(Export-ShareInfo -name $name)
$param = "param"
$ShareInfoNew = Set-SharePermission $name $user $AceType $AccessMask $param
# вот здесь происходит присоединение с нуля созданного объекта (ACE) к имеющемуся массиву текущих
# ACE. Таким образом мы можем добавлять участников безопасности к ACL сетевой папки без удаления
# текущих ACE.
$ShareInfo += $ShareInfoNew
_ShareUtils_WriteShare $ShareInfo $shares
}}
# основная функция для удаления единичного ACE из ACL сетевой папки. Процесс сводится к извлечению
# текущего списка ACL и фильтрации ACE в этом списке по методу Not Equal. Всё, что не подпадает под
# это действие записываются обратно в переменную, а всё, что подпало (указанный пользователь) обратно
# в переменную $ShareInfo не записывается.
function Remove-SharePermission ($name, $user) {
$shares = gwmi Win32_share -Filter "name = '$name'"
if ($shares -eq $null) {Write-Warning "Указанная сетевая шара не найдена"}
else {
$ShareInfo = Export-ShareInfo -name $name
$ShareInfo = $shareInfo | where {$_.name -eq "$name" -and $_.user -ne "$user"}
_ShareUtils_WriteShare $ShareInfo $shares}}
# основная функция для создания новых сетевых папок на локальном компьютере. Здесь я использую упрощённый
# вариант создания сетевой папки, но учитывая один большой нюанс я добавил одно действие. Суть проблемы
# изложена тут: http://vpodans.spaces.live.com/blog/cns!BB1419A2CFC1E008!170.entry
# поэтому при создании новой сетевой папки я вручную создаю с нуля список ACL, который содержит
# только группу Everyone и с правом Allow Read.
function New-Share ($name, $path, $Description) {
$Share = ([wmiClass] 'Win32_share').Create($path, $name, 0, $null, $Description)
Write-Host "Создание сетевой шары $name :"
$Return = _ShareUtils_Get-Code $share
$Return
if ($Return -eq "Успешно") {
# для использования скрипта в мультиязычных системах без лишних правок в скрипте я вместо именования
# группы Everyone я использовал трансляцию её уникального для всех систем SID в строковое значение,
# которое может отличаться в зависимости от языка системы.
$user = (new-object security.principal.securityidentifier "S-1-1-0").translate([security.principal.ntaccount])
Set-SharePermission $name $user.value "Allow" "Read"}
}
# основная функция для удаления сетевой шары (равносильно Stop Sharing в консоли Shares). Сама папка
# и её содержимое не удаляется.
function Remove-Share ($name) {
$share = gwmi Win32_share -Filter "name = '$name'"
if ($share -eq $null) {Write-Warning "Указанная сетевая шара не найдена"} else {
$share.delete($null)
Write-Host "Удаление сетевой шары $name :"
_ShareUtils_Get-Code $share
}}
# функция, которая возвращает на экран пользователю список всех сетевых папок на локальном компьютере.
# можно так же получить сведения только об одной сетевой папке, которую нужно указать при вызове.
function Get-Share ($name) {
if ($name -eq $null) {
gwmi Win32_Share -Filter 'type = 0' | fl}
else {
gwmi Win32_Share -Filter "name='$name'" | fl}
}
# основная функция для вывода на экран сведений о безопасности (содержимого списка ACL) как для всех
# сетевых папок (если вызывается функция без параметров), так и для конкретной сетевой папки. Т.к. маски и типы
# доступа приводятся в числовых значениях после вывода сведений выводится краткая справка по трансляции
# данных значений. Считаю, что нету смысла писать транслятор, который перед выводом информации на экран
# данных сам автоматически переводил бы в понятные текстовые значения.
function Get-SharePermission ($name) {
Export-ShareInfo -name $name | select name, user, AccessMask, AceType | ft -a -group name
Write-Host "Данные колонки AccessMask имеют следующие значения:
2032127 - FullControl
1245631 - Change
1179817 - Read `n
Данные колонки AceType имеют следующие значения:
0 - Allow
1 - Deny
2- SystemAudit (группы Administrators и System имеют право Allow FullControl" -foregroundcolor "Yellow"}
Вот так это всё выглядит. На первый взгляд много и страшно, но если прочитать все предыдушие статьи по данной теме, то данный код уже будет обретать некий смысл. На этом я предлагаю поставить жирную точку в вопросе управления сетевыми папками и безопасностью (Share Permissions) сетевых папок в PowerShell.
И на последок добавлю, что принимаются любые замечания, поправки предложения по данному скрипту, т.к. это лишь мой первый пробный многофункциональный скрипт по ACL и что-то может быть упущено или неоптимизировано. Вобщем, если есть что сказать, то отписывайтесь в комментариях.