Примечание: данный пост перепечатан в связи с закрытием бложиков на spaces.live.com, как имеющий какую-то ценность для автора и/или читателей.
Примечание: существует обновлённый вариант этого поста с модернизированным скриптом: Управление безопасностью общих папок (сетевых шар) в PowerShell (часть 5)
Ну что ж, настало время подвести итоги по материалу управления сетевыми шарами и управлению доступа к ним. Как и обещал, я написал скрипт, который выполнен в виде функций при помощи которого можно действительно легко и просто управлять самими сетевыми папками и их безопасностью из PowerShell. Интеграция данного скрипта в профиль PowerShell позволяет использовать уже готовые функции как простые командлеты. Я считаю, это действительно большим плюсом, т.к. до этого администратору для решения этих задач приходилось писать длиннющий код в каждом скрипте, который касался безопасности сетевых шар. Итак, какая же функциональность заложена в данный скрипт:
Касательно последнего пункта, то хочу отметить, что импорт при отсутствии наличия папки для расшаривания создаст папку и расшрарит с данными из CSV файла. Сначала я приведу список команд, которые доступны при использовании скрипта и их синтаксис:
Ну и собственно сам скрипт с некоторыми комментариями по работе самих функций. Для желающих подробно изучить скрипт я не делал особо комментариев, т.к. построение кода описано в предыдущих частях по управлению безопасностью сетевых папок. Ссылки на предыдущие части:
######################################################## # ShareUtils.ps1 # Version 0.0.0.5 # # Functions for advanced share management # # Vadims Podans (c) 2008 # http://vpodans.spaces.live.com/ ######################################################## Write-Host "Vadims Podans's ShareUtils are installed" # внутренняя функция, которая преобразовывает числовой код возврата операции записи 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'} else {gwmi Win32_Share -Filter "name='$name'"} } # основная функция для вывода на экран сведений о безопасности (содержимого списка 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 и что-то может быть упущено или неоптимизировано. Вобщем, если есть что сказать, то отписывайтесь в комментариях.
Comments: