В 2021 году достаточно много компаний используют файловые серверы для совместной работы, поэтому остается актуальным вопрос разграничения доступа на общих ресурсах.
Как правильно организовать доступ к файловым ресурсам описано в Best Practices от Microsoft, в том числе и в документе Windows Productivity for IT Professionals из Microsoft Resource Kit. В Сети можно найти множество статей на русском языке по организации файлового сервера, в том числе и на Хабре.
Например, вот эти:
Аспирин от настройки прав на файловом сервере
Правила хорошего тона для дизайна разрешений на файловых серверах
В статьях хорошо описано, что необходимо создавать группы доступа в Active Directory и уже потом раздавать права на папки шары этим группам и в свою очередь в эти группы доступа помещать пользователей или ролевые группы. Подход достаточно здравый и применим в большинстве практических ситуаций.
А что делать, если необходимо, например, раздать доступ на ресурсе, в котором 200 папок? И таких ресурсов у вас несколько штук.
Сделаем подсчет сколько времени уйдет на ручную настройку такой структуры.
… тут был скучный подсчет времени, которое требуется для ручного выполнения задачи.
Избавлю Вас от этого чтения рассуждений и подсчетов и сразу перейду к выводу: такую работу необходимо автоматизировать.
Достаточно просто это сделать при помощи скрипта на PowerShell.
Что требуется от скрипта
После выполнения скрипта мы должны получить в Active Directory структуру OU в виде дерева, которое будет повторять дерево папок нашего ресурса (шары).
В каждой OU-папке будут созданы группы с названием каждой из папок.
Нам необходимо четыре вида групп:
RO – группы только для чтения.
RW – группы на запись, но без возможности переименовывать или создавать папки в закрепленной структуре.
FL – группы с полными правами на папки и файлы, в том числе с правами на редактирование закрепленной структуры.
DY – группы с полным запретом на чтение папки.Систему выдачи прав сделать таким образом, чтобы можно было выдавать права с любого уровня такой структуры. То есть, если мы выдали права на верхнюю папку структуры, необходимо чтобы пользователь имел такие же права и на другие вложенные папки.
Если мы выдали права на папку где-то в глубине структуры, и пользователь не имеет доступа на родительские папки, то он должен иметь права на листинг этих родительских папок выше, чтобы можно было пройти по всему дереву к необходимой папке в глубине структуры.
У каждой группы доступа в описании должен быть закреплен полный путь к ресурсу, которым она управляет.
Структура должна автоматически поддерживаться в одном и том же состоянии за счет периодического выполнения данного скрипта.
Для каждого ресурса, шары должна быть создана одна супер группа, которой должен быть выдан полный доступ ко всем папкам структуры (подарок шифровальщикам).
Весь механизм должен работать с выключенными режимом наследования до желаемого уровня, далее наследование должно быть включено и остальные папки структуры (ниже желаемого уровня) должны наследовать права от родителя.
Сам скрипт
<#
.Synopsis
Creates OU structure in Active Directory like share structure and creates access groups in created OUs.
.Description
Script creates OU structure in Active Directory like share structure and creates access groups in created OUs.
Also script can assign folders permissions for created groups if particular parameters are switched (SetFolderRights, EnableSubInheritance).
Script find folders more than 40 characters long and find folders with spec symbols in names ['',#%^`+]
More than 40 characters long folders not allowed because Active Directory group cant have name more
than 64 characters (24 characters script uses for group prefix and random string).
Spec symbols not allowed because OU names cant contains them.
All OUs and groups with long names and spec symbols will be renamed.
.Parameter ServerName
Specifies server name where shared folder.
.Parameter ShareName
Specifies share name on server.
.Parameter SubDir
Specifies sub directory for share name. Should contains full path to the folder.
For example, creating structure only for specific folder in share not for full share.
Example - ./Create-FoldersOU.ps1 -ServerName FileServer01 -ShareName 'Cloud' -SubDir 'Department3/Unit1'.
It create structure only for specific folder 'Department3/Unit1' in share Cloud on FileServer01.
.Parameter LevelOfDepth
Level of recursion depth. Starts from 0
.Parameter BaseFileShareOU
Specifies the distinguished name path ("OU=Shares,DC=domain,DC=com") to a based OU where script will store OUs structure and groups.
.Parameter SetFolderRights
Assign permissions to folders after creating groups.
.Parameter EnableSubInheritance
Enable inheritance for all folders after last folder and its subtree.
.Parameter GroupPrefix
Specifies group prefix for access groups. Should be equal or less 5 characters. Default prefix is "GRFS"
.Example
.\Create-FolderOU.ps1 -ServerName FileServer01 -ShareName 'Cloud' -SubDir 'Department3/Unit1' -LevelOfDepth 0
Create only OU structure and access groups for subfolder Department3/Unit1 on \\FileServer01\Cloud
.Example
.\Create-FolderOU.ps1 -ServerName FileServer01 -ShareName 'Cloud'-LevelOfDepth 2 -SetFolderRights -EnableSubInheritance
Create OU structure, access groups and assign permissions for groups on \\FileServer01\Cloud with recursion for 3 branch of share.
.Inputs
No inputs
#>
<#/***************************************************************************
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/*************************************************************************** #>
###########################################################
# AUTHOR : Rinat K. Nugaev - http://www.nugaev.net - rn@nugaev.net
# DATE : 15-06-2021
# EDIT : 07-07-2021
# COMMENT : This script creates OU structure in Active Directory like FileShare structure
# and creates groups with RW,FL,DY and RO access to directories and files
# RO - read only permisssions
# RW - read write permissions only for files, cant rename or change folders
# FY - full permissions
# DY - deny read permissions
#
# VERSION : 3.9
###########################################################
#Parameters
[CmdletBinding()]Param (
[Parameter(Mandatory)]
[string] $ServerName,
[Parameter(Mandatory)]
[string]$ShareName,
[Parameter(Mandatory)]
[string]$BaseFileShareOU,
[Parameter(Mandatory)]
[int]$LevelOfDepth,
[string]$SubDir,
[string]$GroupPrefix = "GRFS",
[switch]$SetFolderRights,
[switch]$EnableSubInheritance
)
Set-StrictMode -Version Latest
#Begining functions block
Function TrackTime($Time) {
If (!($Time)) { Return Get-Date } Else {
Return ((get-date) - $Time)
}
}
Function remove-SpecSymbols {
[CmdletBinding()]
Param (
[parameter(Mandatory = $true)][string]$strName
)
Process {
$pattern = '['',#%^`+]'
if ($strName -match $pattern) {
$strName = $strName -replace $pattern, ''
}
return $strName
}
}
function Rename-Longnames {
#Required Get-RandomAlphanumericString function
[CmdletBinding()]
Param (
[parameter(Mandatory = $true)][string]$strName,
[parameter(Mandatory = $true)][ValidateLength(0, 1)][string]$Char,
[parameter(Mandatory = $true)][int]$length
)
Process {
if ($strName.Length -ge $length) {
$strName = $strName.Substring(0, ($length - 1)) + $Char
}
return $strName
}
}
Function Reset-AclInheritance {
[CmdletBinding(SupportsShouldProcess = $true)]
param(
[Parameter(Mandatory = $True)] [String]$Path
)
if (-not (Test-Path -LiteralPath $Path)) {
Write-Error -Message "The object at '$Path' doesn't exist"
return
}
Write-Verbose -Message "Getting security descriptor for item at '$Path'"
$aclinh = Get-Acl -LiteralPath $Path
$Changed = $False
if ($aclinh.AreAccessRulesProtected) {
Write-Verbose -Message "Object at '$Path' has disabled inheritance, re-enabling it"
$aclinh.SetAccessRuleProtection($False, $False)
$Changed = $True
}
$Acls = $aclinh.GetAccessRules($true, $false, [System.Security.Principal.NTAccount])
ForEach ($Ace in $Acls) {
$Ace_String = "$($Ace.IdentityReference.Value): $($Ace.AccessControlType.ToString()) ($($Ace.FileSystemRights.ToString()))"
Write-Verbose -Message "Removing non-inherited ACE at '$Path' - $Ace_String"
$result = $aclinh.RemoveAccessRule($ace)
if (-not $Result) {
Write-Error -Message "Failed to remove non-inherited ACE at '$Path' - $Ace_String"
}
else {
$Changed = $True
}
}
if ($Changed) {
if ($PSCmdlet.ShouldProcess($Path, "Persisting new security descriptor that has been reset")) {
Write-Verbose -Message "Setting new security descriptor for item at '$Path'"
Set-Acl -LiteralPath $Path -AclObject $aclinh
}
else {
Write-Verbose -Message "What if: Setting new security descriptor for item at '$Path'"
}
}
else {
Write-Verbose -Message "No changes required to the security descriptor for item at '$Path'"
}
if (Test-Path -LiteralPath $Path -PathType Container) {
Get-ChildItem -LiteralPath $Path | ForEach-Object { Reset-AclInheritance -Path $_.FullName }
}
}
Function Set-FileObjectPermissions {
<#
.SYNOPSIS
Sets Folders or Files Permissions
.PARAMETER Object
Provide Object path
.PARAMETER Principal
Provide group or user set permissions for
.PARAMETER Permisssion
Provide type of Permissions. Possible parameters are FullControl, "FullControl,TakeOwnership", "CreateFiles, AppendData, Delete" Modify,ReadAndExecute
.PARAMETER Inheritance
Inheritance or None
.PARAMETER TypeOfAccess
Allow or Deny
.EXAMPLE
Set-FileObjectPermissions -Object $PathFolder -Principal Administrators -Permission FullControl -TypeOfAccess Allow
.EXAMPLE
Set-FileObjectPermissions -Object $PathFolder -Principal Administrators -Permission "CreateFiles, AppendData, Delete" -TypeOfAccess Allow -NoneInheritance
#>
param (
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]$Object,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]$Principal,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[ValidateSet("FullControl", "FullControl,TakeOwnership", "CreateFiles, AppendData, Delete", "Delete", "Modify", "ReadAndExecute")]
$Permission,
[switch]$NoneInheritance,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[ValidateSet("Allow", "Deny")]
$TypeOfAccess
)
#Getting Acl from Object
$Acl = Get-Acl "$Object"
#Preparing AccessRule
if ($NoneInheritance) {
$AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule ("$Principal", "$Permission", "None", "None", "$TypeOfAccess");
}
else {
$AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule ("$Principal", "$Permission", "ContainerInherit,ObjectInherit", "None", "$TypeOfAccess");
}
##Setting AccessRule
$Acl.SetAccessRule($AccessRule);
$Acl | Set-Acl "$Object"
}
Function Get-OUExists {
[CmdletBinding()]
Param (
[string] $OUName
)
Process {
### Getting groupname without Tail (random number in the end of name of group)
#$GroupName
if ([adsi]::Exists("LDAP://$OUName")) {
return $true
Write-Verbose "Group $GroupName already exists!"
}
else {
return $false
Write-Verbose "Group $GroupName does not exist!"
}
}
}
Function Get-ADGroupExists {
[CmdletBinding()]
Param (
[string] $GroupName,
[string] $OUName
)
Process {
### Getting groupname without Tail (random number at the end of name of the group)
$GroupName = $GroupName -replace "_\w{4}$"
$Filter = "$($GroupName)_*"
#$GroupName
if (Get-ADGroup -SearchScope OneLevel -SearchBase $OUName -Filter { Name -like $Filter } -ErrorAction SilentlyContinue) {
$ExistedGroup = Get-ADGroup -SearchScope OneLevel -SearchBase $OUName -Filter { Name -like $Filter } | Select-Object -Expand Name
return $ExistedGroup
Write-Verbose "Group $GroupName already exists!"
}
else {
return $false
Write-Verbose "Group $GroupName does not exist!"
}
}
}
Function Get-RandomAlphanumericString {
#Required Get-RandomAlphanumericString function
[CmdletBinding()]
Param (
[int] $length = 4
)
Begin {
}
Process {
Write-Output ( -join ((48..57) + (97..122) | Get-Random -Count $length | ForEach-Object { [char]$_ }) )
}
}
Function Get-subDirectory {
[CmdletBinding()]
Param (
[string] $Directory,
[string] $SubDirectory,
[int] $LevelOfDepth
)
Process {
#Deleting \ at the end of dirs
$Directory = $Directory -replace '\\$'
$SubDirectory = $SubDirectory -replace '\\$'
$TempSubDir = $Directory + "\" + $SubDirectory
$SubDirsArr = $SubDirectory.Split('\')
#Calculating $LevelOfDepth for subdirs
if ($SubDirsArr) {
$SubLevelOfDepth = ($SubDirsArr | Measure-Object).count
$LevelOfDepth = $LevelOfDepth + $SubLevelOfDepth
}
else { $LevelOfDepth = $LevelOfDepth }
$DirsArr = @()
$Directories = Get-ChildItem -Depth $LevelOfDepth -Recurse -Directory -Path $Directory
ForEach ($Dir in $Directories) {
ForEach ($SubDir in $SubDirsArr) {
if ($Dir.FullName.EndsWith($SubDir) -and $Dir -notin $DirsArr ) {
$DirsArr += $Dir
}
}
if ($Dir.FullName.StartsWith($TempSubDir) -and $Dir -notin $DirsArr) {
$DirsArr += $Dir
}
}
return $DirsArr
}
}
Function Write-Log {
[CmdletBinding()]
Param(
[Parameter(Mandatory = $False)]
[ValidateSet("INFO", "WARN", "ERROR", "FATAL", "DEBUG")]
[String]
$Level = "INFO",
[Parameter(Mandatory = $True)]
[string]
$Message,
[Parameter(Mandatory = $False)]
[string]
$Logfile
)
$Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")
$Line = "$Stamp $Level $Message"
If ($logfile) {
Add-Content $logfile -Value $Line -Encoding "utf8"
}
Else {
Write-Output $Line
}
}
#End functions block
$DomainAdminsGroup = "Администраторы домена"
#$DomainAdminsGroup = "Domain Admins"
#Path to shared folder
$BaseSharePath = "\\$ServerName\$ShareName"
$ScriptDir = (Resolve-Path .\).Path
#Logs directory and in script dir and log file in it
$LogsDir = "$ScriptDir\Logs"
$LogFile = $LogsDir + "\" + "Logfile" + "-" + (Get-Date).toString("yyyy-MM-dd_HH-mm-ss") + ".log"
#Begining main part of the script
#Begining Tracking time
Clear-Host
$time = 0
$time = TrackTime $time
#Basic tests
#Test base share exists
if (!(Test-Path $BaseSharePath)) {
Write-Error "Share $BaseSharePath not presence or not created!"
Write-Warning "Please create share $BaseSharePath and set full permissions for the user the script runas."
Exit(0)
}
#Test logs dir exists, if not created, create it.
if (!(Test-Path $LogsDir)) {
Write-Warning "Logs dir $LogsDir not presence or not created!"
Write-Verbose "Creating $LogsDir"
try {
New-Item -ItemType Directory -Force -Path $LogsDir -InformationAction SilentlyContinue
}
catch {
$message = "Failed to create $LogsDir" + $($error[0])
Write-Warning $message
Exit(0)
}
}
#Test Base File Share OU already exists
if (!(Get-OUExists -OUName $BaseFileShareOU)) {
$message = "Base OU $BaseFileShareOU does not exist!`n"
$message += "Please create Base OU $BaseFileShareOU and set full permissions for the user the script runas."
Write-Warning $message
Write-log -Level FATAL $message -Logfile $LogFile
Exit(0)
}
#Test BaseOU already exists and if doesn't, create it
$BaseOU = 'OU=' + $ShareName + ',' + $BaseFileShareOU
try {
if ((Get-OUExists -OUName $BaseOU)) {
Write-Verbose "OU $BaseOU already exists"
}
else {
Write-Verbose "Creating OU $ShareName"
New-ADOrganizationalUnit -Name "$ShareName" -Path "$BaseFileShareOU" -ProtectedFromAccidentalDeletion $false
}
}
catch {
$message = "Failed to create OU $ShareName in $BaseFileShareOU" + $($error[0])
Write-Warning $message
Write-log -Level ERROR $message -Logfile $LogFile
}
$FullGroupNameFullRW = "_tech_" + $GroupPrefix + "FULL_" + $ShareName
start-sleep 1
try {
if (!(Get-ADGroup -SearchScope OneLevel -SearchBase $BaseOU -Filter { Name -eq $FullGroupNameFullRW } -ErrorAction SilentlyContinue) ) {
Write-Verbose "Creating GROUP $FullGroupNameFullRW"
New-ADGroup -Name $FullGroupNameFullRW -GroupCategory Security -GroupScope DomainLocal `
-DisplayName $FullGroupNameFullRW -Path $BaseOU `
-Description "Super FULL RW for $BaseSharePath"
}
else {
Write-Verbose "Group like $FullGroupNameFullRW already exists"
}
}
catch {
$message = "Failed to create group $FullGroupNameFullRW in $BaseOU" + $($error[0])
Write-Warning $message
Write-log -Level ERROR $message -Logfile $LogFile
}
$FullGroupNameFullRW = (Get-adgroup $FullGroupNameFullRW -ErrorAction Stop).Name
###Create OUS, groups of share and set rights to it
#Calculating $SublevelofDepth
$SubDirsArr = $SubDir.Split('\')
if ($LevelOfDepth -lt 0)
{ Write-warning "LevelOfDepth cant be less than 0"; exit }
if ($LevelOfDepth -gt 0 -and !($SubDirsArr)) { $SubLevelOfDepth = $LevelOfDepth - 1 }
elseif ($SubDirsArr) {
$SubLevelOfDepth = ($SubDirsArr | Measure-Object).count
$SubLevelOfDepth = $LevelOfDepth + $SubLevelOfDepth
}
else { $SubLevelOfDepth = $LevelOfDepth }
#Calculating $NameLength
$GroupNameLength = 64 - ($GroupPrefix.Length + 15)
$OUNameLength = 64
#Base work
#GroupCount and FolderCount counters variables that I use for statistics
$GroupCount = 0
$OUsCount = 0
##Getting folders from share and subdir
$Folders = Get-subDirectory -Directory $BaseSharePath -SubDirectory $Subdir -LevelOfDepth $LevelOfDepth | Select-Object -Expand FullName
#Init progress items
$prog_i = 1
$prog_s = 1
#Generating OUs names from folders names, creates groups and set permissions for groups to share folders
$Folders | ForEach-Object {
$NestOU = ''
$GroupDescription = $_
$FolderFullPath = $_
#Deleting \\servername\sharename\
#If you want, please create better regex for it )
$FolderWithOutSRVname = $_ -replace "^\\\\(.*?)\\(.*?)\\"
$OUS = (Split-Path $FolderWithOutSRVname -Parent).Split('\')
#Reversing Array
[array]::Reverse($OUS)
$OUS | ForEach-Object {
if ($_.Length -eq 0) {
return
}
#Removing spec symbols
$strNSS = remove-SpecSymbols -strName $_
#Rename long names for full 64 length
$strOU = Rename-Longnames -strName $strNSS -Char "~" -length $OUNameLength
$NestOU = $NestOU + 'OU=' + $strOU + ','
}
$NestOU += $BaseOU
$LeafOU = Split-Path $_ -Leaf
#removing spec symbols from LeafOU names
$LeafOU = remove-SpecSymbols -strName $LeafOU
#truncate LeafOU to 64 symbols
$LeafOU = Rename-Longnames -strName $LeafOU -Char "~" -length $OUNameLength
#getting GroupName from LeafOU
$GroupName = [string]$LeafOU
#truncate GroupName to $GroupNameLength
$GroupName = Rename-Longnames -strName $GroupName -Char "~" -length $GroupNameLength
#Creating pseudo random 4 symbol string for tail of groups names
$TailGroup = Get-RandomAlphanumericString
#Generating Groups names
$FullGroupNameLIST = "_tech_" + $GroupPrefix + "_" + "LS_" + $GroupName + "_" + $TailGroup
$FullGroupNameDYdel = "_tech_" + $GroupPrefix + "_" + "DD_" + $GroupName + "_" + $TailGroup
$FullGroupNameRO = $GroupPrefix + "_" + "RO_" + $GroupName + "_" + $TailGroup
$FullGroupNameRW = $GroupPrefix + "_" + "RW_" + $GroupName + "_" + $TailGroup
$FullGroupNameFL = $GroupPrefix + "_" + "FL_" + $GroupName + "_" + $TailGroup
$FullGroupNameDY = $GroupPrefix + "_" + "DY_" + $GroupName + "_" + $TailGroup
#Adding groups names to the GroupsHash
$GroupsHash = @{
'FullGroupNameFL' = $FullGroupNameFL
'FullGroupNameDY' = $FullGroupNameDY
'FullGroupNameRO' = $FullGroupNameRO
'FullGroupNameRW' = $FullGroupNameRW
'FullGroupNameLIST' = $FullGroupNameLIST
'FullGroupNameDYdel' = $FullGroupNameDYdel
}
#Creating OUs for from share structure
try {
#Generating GroupOU from LeafOU and NestOU
$GroupOU = 'OU=' + "$LeafOU" + "," + "$NestOU"
if ((Get-OUExists -OUName $GroupOU)) {
Write-Verbose "OU $GroupOU already exists"
}
else {
$PercentComplete = ($prog_s / ($Folders | Measure-Object).count * 100)
Write-Progress -id 5 -Activity "Creating SubOUs in Base OU " -status "SubOU $LeafOU" `
-PercentComplete $PercentComplete
$prog_s++
Write-Verbose "Creating OU $LeafOU"
New-ADOrganizationalUnit -Name $LeafOU -Path $NestOU -ProtectedFromAccidentalDeletion $false
$OUsCount += 1
}
}
catch {
$message = "Failed to create OU $LeafOU in $NestOU" + $($error[0])
Write-Warning $message
Write-log -Level ERROR $message -Logfile $LogFile
}
#Creating Access Groups
#Init progress item
$prog_q = 1
foreach ($hkey in $($GroupsHash.keys)) {
#Here we are adding old ones groups if they are already exist
try {
if ((Get-ADGroupExists -GroupName $GroupsHash[$hkey] -OuName $GroupOU) ) {
Write-Verbose "Group like $($GroupsHash[$hkey]) already exists!!!"
$GroupsHash[$hkey] = (Get-ADGroupExists -GroupName $GroupsHash[$hkey] -OuName $GroupOU)
}
else {
$PercentComplete = ($prog_q / ($($GroupsHash.keys) | Measure-Object).count * 100)
Write-Progress -id 10 -ParentId 5 -Activity "Creating spec Groups for $GroupName" -status "Group $($GroupsHash[$hkey])" `
-PercentComplete $PercentComplete
$prog_q++
Write-Verbose "Creating GROUP $($GroupsHash[$hkey])"
New-ADGroup -Name $GroupsHash[$hkey] -GroupCategory Security -GroupScope DomainLocal `
-DisplayName $GroupsHash[$hkey] -Path $GroupOU `
-Description $GroupDescription
$GroupCount += 1
}
}
catch {
$message = "Failed to create Group $($GroupsHash[$hkey]) in $GroupOU" + $($error[0])
Write-Warning $message
Write-log -Level ERROR $message -Logfile $LogFile
}
}
try {
#Adding group RW to denyDel Group
if (Get-adgroup $($GroupsHash['FullGroupNameRW'])) {
Add-ADGroupMember $($GroupsHash['FullGroupNameDYdel']) -Members $($GroupsHash['FullGroupNameRW'])
}
}
catch {
$message = "Failed to add Group $($GroupsHash['FullGroupNameRW']) to $FullGroupNameDYdel" + $($error[0])
Write-Warning $message
Write-log -Level ERROR $message -Logfile $LogFile
}
#Adding groups to the NestListGroup
$regexLSGroup = "_tech_" + $GroupPrefix + "_" + "LS_*"
$NestListGroup = Get-ADGroup -SearchScope OneLevel -SearchBase $NestOU -filter { Name -like $regexLSGroup }
foreach ($hkey in $($GroupsHash.keys)) {
if (Get-adgroup $GroupsHash[$hkey]) {
#Adding all groups to NestListGroup exept DY groups $FullGroupNameDYdel
if ($NestListGroup -and $GroupsHash[$hkey] -ne $GroupsHash['FullGroupNameDY'] -and $GroupsHash[$hkey] -ne $GroupsHash['FullGroupNameDYdel']) {
Write-Verbose "Adding $($GroupsHash[$hkey]) to $(($NestListGroup).Name)"
Add-ADGroupMember $NestListGroup -Members $GroupsHash[$hkey]
}
}
}
#Getting variables from Hash back
#Sorry, some AD commandlets have strange behavior with hash keys
$FullGroupNameFL = $GroupsHash['FullGroupNameFL']
$FullGroupNameDY = $GroupsHash['FullGroupNameDY']
$FullGroupNameRO = $GroupsHash['FullGroupNameRO']
$FullGroupNameRW = $GroupsHash['FullGroupNameRW']
$FullGroupNameLIST = $GroupsHash['FullGroupNameLIST']
$FullGroupNameDYdel = $GroupsHash['FullGroupNameDYdel']
#Assigning rights to the folders
if ($SetFolderRights) {
$PercentComplete = ($prog_i / ($Folders | Measure-Object).count * 100)
Write-Progress -id 20 -Activity "Setting group $GroupName rights for Folder" -status "Folder $FolderFullPath" `
-PercentComplete $PercentComplete
$prog_i++
try {
#Setting Deny Del Rights to the Folder
if (Get-adgroup -Filter { SamAccountName -eq $FullGroupNameDYdel }) {
Write-Verbose "Setting Deny Del for $FolderFullPath"
#Set-FileObjectPermissions -Object $FolderFullPath -Principal $FullGroupNameDYdel -Permission "CreateFiles, AppendData, Delete" -TypeOfAccess Deny -NoneInheritance
Set-FileObjectPermissions -Object $FolderFullPath -Principal $FullGroupNameDYdel -Permission "Delete" -TypeOfAccess Deny -NoneInheritance
}
#Setting Deny Del Rights to the Folder
if (Get-adgroup -Filter { SamAccountName -eq $FullGroupNameDY }) {
Write-Verbose "Setting Deny Read for $FolderFullPath"
Set-FileObjectPermissions -Object $FolderFullPath -Principal $FullGroupNameDY -Permission "ReadAndExecute" -TypeOfAccess Deny
}
#Setting RW Rights to the Folder
if (Get-adgroup -Filter { SamAccountName -eq $FullGroupNameRW }) {
Write-Verbose "Setting RW for $FolderFullPath"
Set-FileObjectPermissions -Object $FolderFullPath -Principal $FullGroupNameRW -Permission "Modify" -TypeOfAccess Allow
$FoldersRW = Get-subDirectory -Directory $FolderFullPath -SubDirectory $Subdir -LevelOfDepth ($SubLevelOfDepth) | Select-Object -Expand FullName
#Init progress item
$prog_j = 1
$FoldersRW | ForEach-Object {
$FolderPathRW = $_
Write-Verbose "Set ACL for $FullGroupNameRW recursive for folder $_"
$PercentComplete = ($prog_j / ($FoldersRW | Measure-Object).count * 100)
Write-Progress -id 30 -ParentId 20 -Activity "Setting group $FullGroupNameRW rights to subfolders" -status "Folder $_" `
-PercentComplete $PercentComplete
$prog_j++
Set-FileObjectPermissions -Object $FolderPathRW -Principal $FullGroupNameRW -Permission "Modify" -TypeOfAccess Allow }
}
#Setting Full Rights to the Folder
if (Get-adgroup -Filter { SamAccountName -eq $FullGroupNameFL }) {
Write-Verbose "Setting FULL for $FolderFullPath"
Set-FileObjectPermissions -Object $FolderFullPath -Principal $FullGroupNameFL -Permission "Modify" -TypeOfAccess Allow
#Adding $FullGroupNameFL to subfolders recurcively
$FoldersFL = Get-subDirectory -Directory $FolderFullPath -SubDirectory $Subdir -LevelOfDepth ($SubLevelOfDepth) | Select-Object -Expand FullName
#Init progress item
$prog_k = 1
$FoldersFL | ForEach-Object {
$FolderPathFL = $_
Write-Verbose "Set ACL for $FullGroupNameFL recursive for folder $_"
$PercentComplete = ($prog_k / ($FoldersFL | Measure-Object).count * 100)
Write-Progress -id 40 -ParentId 20 -Activity "Setting group $FullGroupNameFL rights to subfolders" -status "Folder $_" `
-PercentComplete $PercentComplete
$prog_k++
Set-FileObjectPermissions -Object $FolderPathFL -Principal $FullGroupNameFL -Permission "Modify" -TypeOfAccess Allow }
}
#Setting RO Rights to the Folder
if (Get-adgroup -Filter { SamAccountName -eq $FullGroupNameRO }) {
Write-Verbose "Setting RO for $FolderFullPath"
Set-FileObjectPermissions -Object $FolderFullPath -Principal $FullGroupNameRO -Permission "ReadAndExecute" -TypeOfAccess Allow
#Adding $FullGroupNameRO to subfolders recurcively
$FoldersRO = Get-subDirectory -Directory $FolderFullPath -SubDirectory $Subdir -LevelOfDepth ($SubLevelOfDepth) | Select-Object -Expand FullName
#Init progress item
$prog_m = 1
$FoldersRO | ForEach-Object {
$FolderPathRO = $_
Write-Verbose "Set ACL for $FullGroupNameRO recursive for folder $_"
$PercentComplete = ($prog_m / ($FoldersRO | Measure-Object).count * 100)
Write-Progress -id 50 -ParentId 20 -Activity "Setting group $FullGroupNameRO rights to subfolders" -status "Folder $_" `
-PercentComplete $PercentComplete
$prog_m++
Set-FileObjectPermissions -Object $FolderPathRO -Principal $FullGroupNameRO -Permission "ReadAndExecute" -TypeOfAccess Allow }
}
#Setting List Rights to the Folder
if (Get-adgroup -Filter { SamAccountName -eq $FullGroupNameLIST }) {
Write-Verbose "Setting List for $FolderFullPath"
Set-FileObjectPermissions -Object $FolderFullPath -Principal $FullGroupNameLIST -Permission "ReadAndExecute" -TypeOfAccess Allow -NoneInheritance
}
}
catch {
$message = "Failed to assign permissions to $FolderFullPath" + $($error[0])
Write-Warning $message
Write-log -Level ERROR $message -Logfile $LogFile
}
}
else {
Write-Verbose "Switch parameter SetFolderRights is not set, nothing to do"
}
try {
#Set rights for super groups
Write-Verbose "Set ACL for $FullGroupNameFullRW recursive for folder $GroupDescription"
Set-FileObjectPermissions -Object $FolderFullPath -Principal $FullGroupNameFullRW -Permission "FullControl" -TypeOfAccess Allow
#Assign permissions for Creator OWNER group
if (Get-adgroup $DomainAdminsGroup -ErrorAction SilentlyContinue) {
Write-Verbose "Set Full perms for Domain admins for folder $GroupDescription"
Set-FileObjectPermissions -Object $FolderFullPath -Principal $DomainAdminsGroup -Permission "FullControl,TakeOwnership" -TypeOfAccess Allow
}
#Assign permissions for Creator OWNER group
Write-Verbose "Setting CREATOR OWNER perms for $FolderFullPath"
###Warnset
Set-FileObjectPermissions -Object $FolderFullPath -Principal "CREATOR OWNER" -Permission "Modify" -TypeOfAccess Allow
#Assign permissions for server admin adminsuser
<#if (Get-aduser adminuser -ErrorAction SilentlyContinue) {
### Write-Verbose "Set ACL for adminuser fullcontrol for folder $GroupDescription"
###Warnset
Set-FileObjectPermissions -Object $FolderFullPath -Principal "nrk" -Permission "FullControl,TakeOwnership" -TypeOfAccess Allow
}#>
}
catch {
$message = "Failed to assign built-in permissions to $FolderFullPath" + $($error[0])
Write-Warning $message
Write-log -Level ERROR $message -Logfile $LogFile
}
#Disable Inheritance if no SubDir
try {
if (!$Subdir) {
Write-Verbose "Reseting inheritance for $FolderFullPath"
$acl = Get-Acl $FolderFullPath
$acl.SetAccessRuleProtection($true, $false)
$acl | Set-Acl $FolderFullPath
}
else {
Write-Verbose "Variable DisableInheritance is not set, nothing to do"
}
}
catch {
$message = "Failed to disable inheritance for $FolderFullPath" + $($error[0])
Write-Warning $message
Write-log -Level ERROR $message -Logfile $LogFile
}
}
#Reenabling inheritance from +1 LevelOfDepth
if ($EnableSubInheritance) {
$FoldersDeep = @()
#$FoldersTemp = Get-ChildItem -Depth ($LevelOfDepth + 1) -Recurse -Directory -Path $BaseSharePath | Select-Object -Expand FullName
$FoldersTemp = Get-subDirectory -Directory $BaseSharePath -SubDirectory $Subdir -LevelOfDepth ($LevelOfDepth + 1) | Select-Object -Expand FullName
ForEach ($Folder in $FoldersTemp) {
if ($Folder -notin $Folders) {
Write-Verbose "Adding $Folder to FoldersDeep array"
$FoldersDeep += $Folder
}
}
#Init progress item
$prog_r = 1
$FoldersDeep | ForEach-Object {
try {
$PercentComplete = ($prog_r / ($FoldersDeep | Measure-Object).count * 100)
Write-Progress -id 60 -Activity "Enable inheritance for Folder" -status "Folder $_" `
-PercentComplete $PercentComplete
$prog_r++
Write-Verbose "Enable inheritance in $_"
Reset-AclInheritance -Path $_
}
catch {
$message = "Failed to enable inheritance to $_" + $($error[0])
Write-Warning $message
Write-log -Level ERROR $message -Logfile $LogFile
}
}
}
$time = TrackTime $time
$message = "All tasks complete!!!`n"
$message += "Created $GroupCount groups for $OusCount OUs`n"
$message += "Script run $($time.Hours) hours $($time.Minutes) minutes"
Write-Output $message
Write-log -Level INFO $message -Logfile $LogFile
Перед запуском скрипта прочитайте все рекомендации ниже.
Требования к окружению для запуска скрипта.
PowerShell 5 и выше
Сервер с установленными PowerShell модулями Active Directory
Также обязательно включите на шаре access based enumeration. Google подскажет как это сделать.
Подготовка шары к выполнению скрипта.
Скрип сработает правильно, если на всех папках ресурса (шары) будет выключено наследование. Сделать это можно при помощи другого скрипта.
Скрипт подготовки ресурса
Function Remove-ACL {
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
[parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)]
[ValidateNotNullOrEmpty()]
[ValidateScript({Test-Path $_ -PathType Container})]
[String[]]$Folder,
[String[]]$AdminUser,
[Switch]$Recurse
)
Process {
foreach ($f in $Folder) {
if ($Recurse) {$Folders = $(Get-ChildItem $f -Recurse -Directory ).FullName} else {$Folders = $f}
if ($Folders -ne $null) {
$Folders | ForEach-Object {
# Remove inheritance
$acl = Get-Acl $_
$acl.SetAccessRuleProtection($true,$true)
Set-Acl $_ $acl
# Remove ACL
$acl = Get-Acl $_
$acl.Access | %{$acl.RemoveAccessRule($_)} | Out-Null
# Add local admin
$permission = "BUILTIN\Administrators","FullControl", "ContainerInherit,ObjectInherit","None","Allow"
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
$acl.SetAccessRule($rule)
Set-Acl $_ $acl
# Add SPEC GROUP PERMS
$acl = Get-Acl $_
$permission = "$AdminUser","FullControl", "ContainerInherit,ObjectInherit","None","Allow"
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
$acl.SetAccessRule($rule)
Set-Acl $_ $acl
#>
Write-Verbose "Remove-HCacl: Inheritance disabled and permissions removed from $_"
}
}
else {
Write-Verbose "Remove-HCacl: No subfolders found for $f"
}
}
}
}
#Path to shared folder
$BaseSharePath = "F:\Fileshare\"
$AdminUser = "scripuser"
Function TrackTime($Time){
If (!($Time)) { Return Get-Date } Else {
Return ((get-date) - $Time)
}
}
$time = 0
$time = TrackTime $time
Remove-ACL $BaseSharePath -Recurse -AdminUser $AdminUser -Verbose
$time = TrackTime $time
Write-host "It run $($time.Hours) hours $($time.Minutes) minutes $($time.seconds) seconds"
Задайте переменные в скрипте
Путь к папке ресурса $BaseSharePath = "\\FileServer\Fileshare\"
Пользователь от которого будете запускать основной скрипт $AdminUser = "scripuser"
После подготовки ресурса (шары) запускайте основной скрипт.
Пример команды запуска основного скрипта с параметрами.
.\create-FoldersOU.ps1 -ServerName nn-FileServer01 -ShareName "FilesShare1" `
-BaseFileShareOU "OU=FileShares,OU=PermissionsGroups,OU=Groups,DC=example,DC=com" `
-SetFolderRights -EnableSubInheritance -LevelOfDepth 1 `
-GroupPrefix "GRFS"
Параметры скрипта:
-ServerName
имя сервера с ресурсом;
-ShareName
название ресурса (шары) на сервере;
-BaseFileShareOU
название OU, где будет создана структура шары;
-LevelOfDepth
желаемый уровень вложения;
-GroupPrefix
префикс создаваемых групп;
-SetFolderRights
применение прав к папкам;
-EnableSubInheritance
включение наследования в папках ниже построенной структуры.
Также скрипт поддерживает параметр -Verbose
, с ним он будет подробно писать, что делает.
Что сделает скрипт после запуска
Далее скрипт создаст структуру OU согласно структуре вашей шары. В каждой OU будет созданы группы 5 видов.
Создаст OU для вашей ресурса.
Создаст одну служебную супер группу
_tech_GRFS_DYDel_имя_ресурса
- эта группа необходима, чтобы запретить группам RW изменять структуру.Создаст одну служебную группу
_tech_GRFS_FULL_имя_ресурса
- эта группа будет иметь полный доступ ко всему ресурсу, режим Бога в шаре (подарок шифровальщикам).Скрипт создаст в текущей директории папку
Logs
, - там будут лежать логи.
Служебная группа
_tech_GRFS_LS_Имя-папки_gwp5
GRFS_DY_Имя-папки_gwp5
- запрет на чтение папкиGRFS_FL_Имя-папки_gwp5
- полный доступ к папке, в том числе с изменением структурыGRFS_RO_Имя-папки_gwp5
- только чтениеGRFS_RW_Имя-папки_gwp5
- полный доступ без возможности изменять структуру папокGRFS
- префикс, обозначающий предназначение группы: GR - group, FS - file server.gwp5
- случайная последовательность, для уникальности каждой группы.
Во время выполнения скрипт автоматически вложит группы FL, RO, RW в группу LS и вложит в нее еще LS группы нижележащих папок для обеспечения возможности давать доступ пользователю в любой части структуры.
Скрипт автоматически выдаст нужные права всем группам на папки ресурса по всей структуре. Выключит наследование прав до заданного уровня вложения (сканирования) и включит наследование прав ниже уровня вложения.
Если запустить скрипт повторно на уже выстроенной структуре, то он просканирует структуру шары и если структура шары соответствует структуре OU созданной раньше, то скрипт ничего не будет создавать, а только заново выставит права на уже созданные группы.
Если же вы или ваши пользователи создали в структуре шары какие-либо папки, то скрипт при следующем запуске достроит структуру учитывая новые папки.
В независимости от того, созданы новые папки или нет, скрипт всегда проходит еще раз по папкам и выставляет заданные права для групп. Таким образом обеспечивается необходимое постоянное состояние структуры OU, которое всегда соответствует ресурсу. Но скрипт ничего не делает, если какая-либо из папок была удалена. При удалении папки из ресурса, необходимо вручную удалять OU и группы.
Как быть, если у вас не везде необходимо выдержать один и тот же уровень вложения скрипта, но в некоторые папки опуститься глубже?
После первого запуска скрипта и создания базовой структуры запустите скрипт с параметром -SubDir и укажите папку ресурса для которой необходимо выстроить структуру с более глубоким уровнем вложения.
P.S!
Конечно, поместить пользователей в группы доступа вам будет необходимо самостоятельно и поэтому раздача прав не совсем уж такая автоматическая, но практическая польза скрипта от этого все равно не уменьшается.
Просьба профессиональных программистов не судить строго за то, как написан скрипт. Я не являюсь программистом, а просто иногда пишу код для решения рутинных задач.
Некоторые функции для скрипта я взял у коллег из Сети, например, функцию включения наследования с заменой прав у наследников. И скрипт подготовки ресурса для выполнения основного скрипта.
Также извините за справку и комментарии в скрипте на ломаном английском, но скриптом пользуются зарубежные коллеги и лучше такие комментарии, чем их полное отсутствие.
Очень приветствуются конструктивные дополнения к скрипту и логике его работы.
Всем желаю экономить время и делать ручной работы как можно меньше!
Спасибо!