ScriptBlocks/DryAD_SB_ADAccessRule_Set.ps1

# DryActiveDirectory is an AD config module for use with DryDeploy, or by itself.
#
# Copyright (C) 2021 Bjørn Henrik Formo (bjornhenrikformo@gmail.com)
# LICENSE: https://raw.githubusercontent.com/bjoernf73/DryActiveDirectory/main/LICENSE
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

[ScriptBlock]$DryAD_SB_ADAccessRule_Set = {
    Param (
            [String]
            $Path,

            [String]
            $TargetName,

            [String]
            $TargetType,

            [System.DirectoryServices.ActiveDirectoryRights[]]
            $ActiveDirectoryRights,

            [System.Security.AccessControl.AccessControlType]
            $AccessControlType,

            [String]
            $ObjectType,

            [String]
            $InheritedObjectType,

            [String]
            $ActiveDirectorySecurityInheritance,

            [String]
            $ExecutionType,

            [String]
            $Server
    )
    Try {
        $ReturnError           = $Null
        $ReturnValue           = $False
        $DebugReturnStrings    = @("Entered Scriptblock")
        
        $DebugReturnStrings   += @("'Path' = '$Path'")
        $DebugReturnStrings   += @("'TargetName' = '$TargetName'")
        $DebugReturnStrings   += @("'TargetType' = '$TargetType'")
        $DebugReturnStrings   += @("'ActiveDirectoryRights' = '$ActiveDirectoryRights'")
        $DebugReturnStrings   += @("'AccessControlType' = '$AccessControlType'")
        $DebugReturnStrings   += @("'ObjectType' = '$ObjectType'")
        $DebugReturnStrings   += @("'InheritedObjectType' = '$InheritedObjectType'")
        $DebugReturnStrings   += @("'ActiveDirectorySecurityInheritance' = '$ActiveDirectorySecurityInheritance'")
        $DebugReturnStrings   += @("'ExecutionType' = '$ExecutionType'")
        $DebugReturnStrings   += @("'Server' = '$Server'")
        
        # Remove any blank optional parameter value to ensure correct constructor of System.DirectoryServices.ActiveDirectoryAccessRule
        If ($ObjectType -eq '') { 
            $DebugReturnStrings += "Removing 'ObjectType' (it is blank)"
            Remove-Variable -name ObjectType 
        }
        If ($InheritedObjectType -eq '') { 
            $DebugReturnStrings += "Removing 'InheritedObjectType' (it is blank)"
            Remove-Variable -name InheritedObjectType 
        }
        If ($ActiveDirectorySecurityInheritance -eq '') { 
            $DebugReturnStrings += "Removing 'ActiveDirectorySecurityInheritance' (it is blank)"
            Remove-Variable -Name ActiveDirectorySecurityInheritance 
        }

        # Make sure ActiveDirectory module is loaded, so the AD drive is mounted
        If ((Get-Module | Select-Object -Property Name).Name -notcontains 'ActiveDirectory') {
            Try {
                Import-Module -Name 'ActiveDirectory' -ErrorAction Stop
                $DebugReturnStrings += @("The AD PSModule was not loaded, but I loaded it successfully")
                Start-Sleep -Seconds 4
            }
            Catch {
                $PSCmdlet.ThrowTerminatingError($_)
            }
        }
        Else {
            $DebugReturnStrings += @("The AD PSModule was already loaded in session")
        }

        # However, that is not necessarily the case. That ActiveDirectory module is a bit sloppy
        Try {
            Get-PSDrive -Name 'AD' -ErrorAction Stop | 
            Out-Null
            $DebugReturnStrings += @("The AD Drive exists already")
        }
        Catch [System.Management.Automation.DriveNotFoundException] {
            $DebugReturnStrings += @("The AD Drive does not exist - trying to create it")
            
            Try {
                $NewPSDriveParams = @{
                    Name        = 'AD' 
                    PSProvider  = 'ActiveDirectory' 
                    Root        = '//RootDSE/' 
                    ErrorAction = 'Stop'
                }
                New-PSDrive @NewPSDriveParams | Out-Null
            }
            Catch {
                $DebugReturnStrings += @("Failed to create the AD Drive: $($_.ToString())")
                $PSCmdlet.ThrowTerminatingError($_)
            }
        }
        Catch {
            $DebugReturnStrings += @("The AD Drive did not exist, and an error occurred trying to get it?")
            $PSCmdlet.ThrowTerminatingError($_)
        }

        # Make sure the AD-drive is connected to $Server. This ensures that Set-ACL operates on a Domain Controller
        # that have the OUs, groups, users or any other AD object that the configuration set creates and configures,
        # without the need for a full AD replication to have happened
        $ADDrive             = Get-PSDrive -Name 'AD' -ErrorAction Stop
        $ADDrive.Server      = "$Server"
    
        $RootDSE             = Get-ADRootDSE -ErrorAction Stop
        $DomainDN            = $RootDSE.defaultNamingContext

        # Create a hashtable to store the GUID value of each schema class and attribute
        $ObjectTypeGUIDs = @{}
        Get-ADObject -SearchBase ($RootDSE.SchemaNamingContext) -LDAPFilter "(schemaidguid=*)" -Properties lDAPDisplayName,schemaIDGUID -ErrorAction Stop | 
            ForEach-Object {
                $ObjectTypeGUIDs[$_.lDAPDisplayName]=[System.GUID]$_.schemaIDGUID
            }
        $ObjectTypeGUIDs['All'] = [GUID]::Empty
        $DebugReturnStrings += "Success getting ObjectTypeGUIDs"

        # Create a hashtable to store the GUID value of each extended right in the forest
        $ExtendedRightsMap = @{}
        Get-ADObject -SearchBase ($RootDSE.ConfigurationNamingContext) -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties displayName,rightsGuid -ErrorAction Stop | 
            ForEach-Object {
                $ExtendedRightsMap[$_.displayName]=[System.GUID]$_.rightsGuid
            }
        $DebugReturnStrings += "Success getting ExtendedRightsMap"

        # Add Domain dN to $Path if it is missing
        If ($Path -notmatch "$DomainDN$") {
            $Path = $Path + ",$DomainDN"
        }
        $PathObject = Get-ADObject -Identity $Path -ErrorAction Stop
        $DebugReturnStrings += "PathObject: $($PathObject.distinguishedName)"

        # Get the object to deletegate rights to
        Switch ($TargetType) {
            'group' {
                $ADGroup = Get-ADGroup -Identity $TargetName -ErrorAction Stop -Properties SID
                [System.Security.Principal.IdentityReference]$Target = $ADGroup.SID
                

            }
            'user' {
                $ADUser = Get-ADUser -Identity $User -ErrorAction Stop -Properties SID
                [System.Security.Principal.IdentityReference]$Target = $ADUser.SID
            }
        }
        $DebugReturnStrings         += "SID of the Target: $Target"

        $ObjectTypeGUID              = $Null
        If ($ObjectType) {
            $DebugReturnStrings     += "Finding GUID of ObjectType: '$ObjectType'"
            $ObjectTypeGUID          = [GUID]($ObjectTypeGUIDs.$($ObjectType))
            $DebugReturnStrings     += "ObjectTypeGUID: $($ObjectTypeGUID.ToString())"
        }
        
        $InheritedObjectTypeGUID     = $Null
        If ($InheritedObjectType) {
            $DebugReturnStrings     += "Finding GUID of InheritedObjectType: '$InheritedObjectType'"
            $InheritedObjectTypeGUID = [GUID]($ObjectTypeGUIDs.$($InheritedObjectType)) 
            $DebugReturnStrings     += "InheritedObjectTypeGUID: $($InheritedObjectTypeGUID.ToString())"
        }

        ForEach ($ActiveDirectoryRight in $ActiveDirectoryRights) {
            $DebugReturnStrings   += "Setting ACL for right: '$ActiveDirectoryRight'"
            # Current ACL
            $ACL = Get-ACL -Path "AD:\$($PathObject.DistinguishedName)" -ErrorAction Stop
            $DebugReturnStrings   += "Success getting current ACL"
            $AccessRule = $null
                
            # If $ActiveDirectoryRight is an Extended Right, then $ActiveDirectoryRight becomes "ExtendedRight",
            # the GUID of the original right becomes the $ObjectTypeGuid, and GUID of $ObjectType becomes $InheritedObjectTypeGuid
            If ($ExtendedRightsMap.ContainsKey($ActiveDirectoryRight)) {
                $DebugReturnStrings   += "'$ActiveDirectoryRight' is an Extended Right"
                $InheritedObjectTypeGuid = $ObjectTypeGUID
                $ObjectTypeGuid          = $ExtendedRightsMap.$($ActiveDirectoryRight)
                $ActiveDirectoryRight    = "ExtendedRight"
            } 
            Else {
                $DebugReturnStrings   += "'$ActiveDirectoryRight' is a Standard Right"
            }

            # Convert to proper types
            If ($Null -ne $ActiveDirectorySecurityInheritance) {
                [System.DirectoryServices.ActiveDirectorySecurityInheritance]$ActiveDirectorySecurityInheritance = $ActiveDirectorySecurityInheritance
            }

            # Now we're able to find the constructor
            If (
                ($Null -eq $ActiveDirectorySecurityInheritance) -and 
                ($Null -eq $ObjectTypeGUID) -and 
                ($Null -eq $InheritedObjectTypeGUID)
            ) { 
                
                $DebugReturnStrings += "Constructor: 1 (Target, ActiveDirectoryRight, AccessControlType)"
                $AccessRule            = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($Target, $ActiveDirectoryRight, $AccessControlType) -ErrorAction Stop
            }
            ElseIf (
                ($Null -ne $ActiveDirectorySecurityInheritance) -and 
                ($Null -eq $ObjectTypeGUID) -and 
                ($Null -eq $InheritedObjectTypeGUID)
            ) { 
                $DebugReturnStrings += "Constructor: 2 (Target, ActiveDirectoryRight, AccessControlType, ActiveDirectorySecurityInheritance)"
                $AccessRule            = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($Target, $ActiveDirectoryRight, $AccessControlType, $ActiveDirectorySecurityInheritance) -ErrorAction Stop
            }
            ElseIf (
                ($Null -ne $ActiveDirectorySecurityInheritance) -and 
                ($Null -eq $ObjectTypeGUID) -and 
                ($Null -ne $InheritedObjectTypeGUID)
            ) { 
                $DebugReturnStrings += "Constructor: 3 (Target, ActiveDirectoryRight, AccessControlType, ActiveDirectorySecurityInheritance, InheritedObjectTypeGUID)"
                $AccessRule            = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($Target, $ActiveDirectoryRight, $AccessControlType, $ActiveDirectorySecurityInheritance, $InheritedObjectTypeGUID) -ErrorAction Stop
            }
            ElseIf (
                ($Null -eq $ActiveDirectorySecurityInheritance) -and 
                ($Null -ne $ObjectTypeGUID) -and 
                ($Null -eq $InheritedObjectTypeGUID)
            ) { 
                $DebugReturnStrings += "Constructor: 4 (Target, ActiveDirectoryRight, AccessControlType, ObjectTypeGuid)"
                $AccessRule            = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($Target, $ActiveDirectoryRight, $AccessControlType, $ObjectTypeGuid) -ErrorAction Stop
            }
            ElseIf (
                ($Null -ne $ActiveDirectorySecurityInheritance) -and 
                ($Null -ne $ObjectTypeGUID) -and 
                ($Null -eq $InheritedObjectTypeGUID)
            ) { 
                $DebugReturnStrings += "Constructor: 5 (Target, ActiveDirectoryRight, AccessControlType, ObjectTypeGuid, ActiveDirectorySecurityInheritance)"
                $AccessRule            = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($Target, $ActiveDirectoryRight, $AccessControlType, $ObjectTypeGuid, $ActiveDirectorySecurityInheritance) -ErrorAction Stop
            }
            ElseIf (
                ($Null -ne $ActiveDirectorySecurityInheritance) -and 
                ($Null -ne $ObjectTypeGUID) -and 
                ($Null -ne $InheritedObjectTypeGUID)) { 
                #Type = 6
                $DebugReturnStrings += "Constructor: 6 (Target, ActiveDirectoryRight, AccessControlType, ObjectTypeGuid, ActiveDirectorySecurityInheritance, InheritedObjectTypeGUID)"
                $AccessRule            = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($Target, $ActiveDirectoryRight, $AccessControlType, $ObjectTypeGuid, $ActiveDirectorySecurityInheritance, $InheritedObjectTypeGUID) -ErrorAction Stop
            }
            Else {
                Throw "Unable to determine constructor"
            }

            Try { 
                # Add the AccessRule to the existing ACL
                $DebugReturnStrings += "Trying to add ACE to current ACL"
                $ACL.AddAccessRule($AccessRule)
                $DebugReturnStrings += "Successfully added ACE to current ACL"

                # Submitting the changes
                $DebugReturnStrings += "Trying to submit (Set-ACL) the modified ACL"
                Set-ACL -ACLObject $ACL -Path ("AD:\$($PathObject.DistinguishedName)") -ErrorAction Stop
                $DebugReturnStrings += "Successfully submitted the ACL!"
            }
            Catch { 
                $PSCmdlet.ThrowTerminatingError($_)
            }
        }

        # If we reached this, assume success
        $ReturnValue = $True
        Return @($DebugReturnStrings,$ReturnValue,$ReturnError)
    }
    Catch {
        $DebugReturnStrings += "Set-DryADAccessRule failed"
        $ReturnError = $_
        Return @($DebugReturnStrings,$ReturnValue,$ReturnError)
    }
    Finally {
        # should probably remove a bunch of stuff here
    }
}