Pull to refresh

Автоматическая раздача прав на файловом сервере

Reading time22 min
Views17K

В 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 видов.

  1. Создаст OU для вашей ресурса.

  2. Создаст одну служебную супер группу _tech_GRFS_DYDel_имя_ресурса - эта группа необходима, чтобы запретить группам RW изменять структуру.

  3. Создаст одну служебную группу _tech_GRFS_FULL_имя_ресурса - эта группа будет иметь полный доступ ко всему ресурсу, режим Бога в шаре (подарок шифровальщикам).

  4. Скрипт создаст в текущей директории папку 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!

Конечно, поместить пользователей в группы доступа вам будет необходимо самостоятельно и поэтому раздача прав не совсем уж такая автоматическая, но практическая польза скрипта от этого все равно не уменьшается.

Просьба профессиональных программистов не судить строго за то, как написан скрипт. Я не являюсь программистом, а просто иногда пишу код для решения рутинных задач.

Некоторые функции для скрипта я взял у коллег из Сети, например, функцию включения наследования с заменой прав у наследников. И скрипт подготовки ресурса для выполнения основного скрипта.

Также извините за справку и комментарии в скрипте на ломаном английском, но скриптом пользуются зарубежные коллеги и лучше такие комментарии, чем их полное отсутствие.

Очень приветствуются конструктивные дополнения к скрипту и логике его работы.
Всем желаю экономить время и делать ручной работы как можно меньше!

Спасибо!

Tags:
Hubs:
Total votes 6: ↑6 and ↓0+6
Comments18

Articles