Public/New-Tier2.ps1

function New-Tier2 {

    <#
        .SYNOPSIS
            Creates Tier2 infrastructure including OUs, GPOs and delegations

        .DESCRIPTION
            Creates the Tier2 infrastructure, including all necessary Organizational Units (OUs),
            Group Policy Objects (GPOs), and delegations based on the configuration XML file.
            This function follows the tiered administrative model for Active Directory security.

        .PARAMETER ConfigXMLFile
            [System.IO.FileInfo] Full path to the XML configuration file.
            Contains all naming conventions, OU structure, and security settings.
            Must be a valid XML file with required schema elements.
            Default: C:\PsScripts\Config.xml

        .PARAMETER DMScripts
            Path to all the scripts and files needed by this function.
            Default: C:\PsScripts\

        .PARAMETER EnableTranscript
            Start transcript logging to DMScripts path with function name

        .EXAMPLE
            New-Tier2 -ConfigXMLFile C:\PsScripts\Config.xml
            Creates the Tier2 infrastructure using the specified configuration file

        .EXAMPLE
            New-Tier2 -ConfigXMLFile C:\PsScripts\Config.xml -EnableTranscript
            Creates the Tier2 infrastructure with transcript logging enabled

        .INPUTS
            System.IO.FileInfo, System.String, System.Switch

        .OUTPUTS
            System.String

        .NOTES
            Used Functions:
                Name ║ Module/Namespace
                ═══════════════════════════════════════════╬══════════════════════════════
                Get-ADUser ║ ActiveDirectory
                Get-ADGroup ║ ActiveDirectory
                Get-AdObjectType ║ EguibarIT
                Get-SafeVariable ║ EguibarIT
                Import-MyModule ║ EguibarIT
                Get-FunctionDisplay ║ EguibarIT
                New-DelegateAdOU ║ EguibarIT.DelegationPS
                New-DelegateAdGpo ║ EguibarIT.DelegationPS
                Set-GpoPrivilegeRight ║ EguibarIT.DelegationPS
                Set-AdAclCreateDeleteOU ║ EguibarIT.DelegationPS
                Set-AdAclChangeOU ║ EguibarIT.DelegationPS
                Set-AdAclDelegateUserAdmin ║ EguibarIT.DelegationPS
                Set-AdAclDelegateGalAdmin ║ EguibarIT.DelegationPS
                Add-AdGroupNesting ║ EguibarIT.DelegationPS
                Set-AdAclCreateDeleteGroup ║ EguibarIT.DelegationPS
                Set-AdAclChangeGroup ║ EguibarIT.DelegationPS
                Write-Verbose ║ Microsoft.PowerShell.Utility
                Write-Progress ║ Microsoft.PowerShell.Utility
                Write-Warning ║ Microsoft.PowerShell.Utility

        .NOTES
            Version: 1.0
            DateModified: 9/May/2025
            LastModifiedBy: Vicente Rodriguez Eguibar
                        vicente@eguibar.com
                        Eguibar IT
                        http://www.eguibarit.com

        .LINK
            https://github.com/vreguibar/EguibarIT

        .COMPONENT
            Active Directory

        .ROLE
            Infrastructure, Security

        .FUNCTIONALITY
            Tier Model, AD Delegation, Security
    #>


    [CmdletBinding(
        SupportsShouldProcess = $true,
        ConfirmImpact = 'High'
    )]
    [OutputType([System.String])]

    param (

        [Parameter(Mandatory = $true,
            ValueFromPipeline = $True,
            ValueFromPipelineByPropertyName = $True,
            ValueFromRemainingArguments = $false,
            HelpMessage = 'Full path to the configuration.xml file',
            Position = 0)]
        [ValidateScript({
                if (-Not ($_ | Test-Path -PathType Leaf) ) {
                    throw ('File not found: {0}' -f $_)
                }
                if ($_.Extension -ne '.xml') {
                    throw ('File must be XML: {0}' -f $_)
                }
                try {
                    [xml]$xml = Get-Content -Path $_ -ErrorAction Stop
                    if ($null -eq $xml.n.Admin -or
                        $null -eq $xml.n.Sites -or
                        $null -eq $xml.n.NC) {
                        throw 'XML file is missing required elements (Admin or OUs section)'
                    }
                    return $true
                } catch {
                    throw ('Invalid XML file: {0}' -f $_.Exception.Message)
                }
            })]
        [PSDefaultValue(Help = 'Default Value is "C:\PsScripts\Config.xml"',
            Value = 'C:\PsScripts\Config.xml'
        )]
        [Alias('Config', 'XML', 'ConfigXml')]
        [System.IO.FileInfo]
        $ConfigXMLFile,

        [Parameter(Mandatory = $false,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ValueFromRemainingArguments = $false,
            HelpMessage = 'Path to all the scripts and files needed by this function',
            Position = 1)]
        [PSDefaultValue(
            Help = 'Default Value is "C:\PsScripts\"',
            Value = 'C:\PsScripts\'
        )]
        [Alias('ScriptPath')]
        [string]
        $DMScripts = 'C:\PsScripts\',

        [Parameter(Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false,
            ValueFromRemainingArguments = $false,
            HelpMessage = 'Start transcript logging to DMScripts path with function name',
            Position = 2)]
        [Alias('Transcript', 'Log')]
        [switch]
        $EnableTranscript

    )

    Begin {
        Set-StrictMode -Version Latest

        If (-not $PSBoundParameters.ContainsKey('ConfigXMLFile')) {
            $PSBoundParameters['ConfigXMLFile'] = 'C:\PsScripts\Config.xml'
        } #end If

        If (-not $PSBoundParameters.ContainsKey('DMScripts')) {
            $PSBoundParameters['DMScripts'] = 'C:\PsScripts\'
        } #end If

        # If EnableTranscript is specified, start a transcript
        if ($EnableTranscript) {
            # Ensure DMScripts directory exists
            if (-not (Test-Path -Path $DMScripts -PathType Container)) {
                try {
                    New-Item -Path $DMScripts -ItemType Directory -Force | Out-Null
                    Write-Verbose -Message ('Created transcript directory: {0}' -f $DMScripts)
                } catch {
                    Write-Warning -Message ('Failed to create transcript directory: {0}' -f $_.Exception.Message)
                } #end try-catch
            } #end if

            # Create transcript filename using function name and current date/time
            $TranscriptFile = Join-Path -Path $DMScripts -ChildPath ('{0}_{1}.LOG' -f $MyInvocation.MyCommand.Name, (Get-Date -Format 'yyyyMMdd_HHmmss'))

            try {
                Start-Transcript -Path $TranscriptFile -Force -ErrorAction Stop
                Write-Verbose -Message ('Transcript started: {0}' -f $TranscriptFile)
            } catch {
                Write-Warning -Message ('Failed to start transcript: {0}' -f $_.Exception.Message)
            } #end try-catch
        } #end if

        # Initialize logging
        if ($null -ne $Variables -and
            $null -ne $Variables.Header) {

            $txt = ($Variables.Header -f
                (Get-Date).ToString('dd/MMM/yyyy'),
                $MyInvocation.Mycommand,
                (Get-FunctionDisplay -HashTable $PsBoundParameters -Verbose:$False)
            )
            Write-Verbose -Message $txt
        } #end If

        ##############################
        # Module imports

        Import-MyModule -Name 'ServerManager' -SkipEditionCheck -Verbose:$false
        Import-MyModule -Name 'GroupPolicy' -SkipEditionCheck -Verbose:$false
        Import-MyModule -Name 'ActiveDirectory' -Verbose:$false
        Import-MyModule -Name 'EguibarIT' -Verbose:$false
        Import-MyModule -Name 'EguibarIT.DelegationPS' -Verbose:$false

        ##############################
        # Variables Definition

        # parameters variable for splatting CMDlets
        [hashtable]$Splat = [hashtable]::New([StringComparer]::OrdinalIgnoreCase)
        [System.Collections.Generic.List[object]]$ArrayList = [System.Collections.Generic.List[object]]::New()
        [System.Collections.Generic.List[object]]$DenyLogon = [System.Collections.Generic.List[object]]::New()

        # Progress bar variables
        [hashtable]$ProgressParams = @{
            Activity         = 'Creating Tier2 Infrastructure'
            Status           = 'Initializing'
            PercentComplete  = 0
            CurrentOperation = 'Loading configuration'
        }
        Write-Progress @ProgressParams

        # Phases to track progress
        [string[]]$ProgressPhases = @(
            'Loading Configuration',
            'Creating Tier2 Organizational Units',
            'Creating Tier2 Baseline GPOs',
            'Creating Tier2 GPO Restrictions',
            'Creating Tier2 Delegations'
        )
        [int]$PhaseCount = $ProgressPhases.Count
        [int]$CurrentPhase = 0



        # Update progress to show we're loading configuration
        $CurrentPhase++
        $ProgressParams['Status'] = $ProgressPhases[$CurrentPhase - 1]
        $ProgressParams['PercentComplete'] = [math]::Min([int](($CurrentPhase / $PhaseCount) * 100), 100)
        Write-Progress @ProgressParams

        # Load the XML configuration file
        try {
            $confXML = [xml](Get-Content $PSBoundParameters['ConfigXMLFile'])
        } catch {
            Write-Error -Message "Error reading XML file: $($_.Exception.Message)"
            throw
        } #end Try-Catch

        # Load naming conventions from XML
        [hashtable]$NC = @{
            'sl'    = $confXML.n.NC.LocalDomainGroupPreffix
            'sg'    = $confXML.n.NC.GlobalGroupPreffix
            'su'    = $confXML.n.NC.UniversalGroupPreffix
            'Delim' = $confXML.n.NC.Delimiter
            'T0'    = $confXML.n.NC.AdminAccSufix0
            'T1'    = $confXML.n.NC.AdminAccSufix1
            'T2'    = $confXML.n.NC.AdminAccSufix2
        }

        #region Users Variables
        $AdminName = Get-SafeVariable -Name 'AdminName' -CreateIfNotExist {
            try {
                Get-ADUser -Filter * | Where-Object { $_.SID -like 'S-1-5-21-*-500' }
            } catch {
                Write-Debug -Message ('Failed to retrieve Administrator: {0}' -f $_.Exception.Message)
                $null
            }
        }

        $NewAdminExists = Get-SafeVariable -Name 'NewAdminExists' -CreateIfNotExist {
            $newAdminName = $confXML.n.Admin.users.NEWAdmin.Name
            if (-not [string]::IsNullOrEmpty($newAdminName)) {
                Get-AdObjectType -Identity $newAdminName
            } else {
                $null
            }
        }
        #endregion Users Variables

        #region Well-Known groups Variables
        $Administrators = Get-SafeVariable -Name 'Administrators' -CreateIfNotExist {
            try {
                Get-ADGroup -Identity 'S-1-5-32-544'
            } catch {
                Write-Debug -Message ('Failed to retrieve Administrators group: {0}' -f $_.Exception.Message)
                $null
            }
        }

        $DomainAdmins = Get-SafeVariable -Name 'DomainAdmins' -CreateIfNotExist {
            try {
                Get-ADGroup -Filter * | Where-Object { $_.SID -like 'S-1-5-21-*-512' }
            } catch {
                Write-Debug -Message ('Failed to retrieve Domain Admins group: {0}' -f $_.Exception.Message)
                $null
            }
        }

        $EnterpriseAdmins = Get-SafeVariable -Name 'EnterpriseAdmins' -CreateIfNotExist {
            try {
                Get-ADGroup -Filter * | Where-Object { $_.SID -like 'S-1-5-21-*-519' }
            } catch {
                Write-Debug -Message ('Failed to retrieve Enterprise Admins group: {0}' -f $_.Exception.Message)
                $null
            }
        }

        $SchemaAdmins = Get-SafeVariable -Name 'SchemaAdmins' -CreateIfNotExist {
            try {
                Get-ADGroup -Filter * | Where-Object { $_.SID -like 'S-1-5-21-*-518' }
            } catch {
                Write-Debug -Message ('Failed to retrieve Schema Admins group: {0}' -f $_.Exception.Message)
                $null
            }
        }

        $AccountOperators = Get-SafeVariable -Name 'AccountOperators' -CreateIfNotExist {
            try {
                Get-ADGroup -Identity 'S-1-5-32-548'
            } catch {
                Write-Debug -Message ('Failed to retrieve Account Operators group: {0}' -f $_.Exception.Message)
                $null
            }
        }

        $BackupOperators = Get-SafeVariable -Name 'BackupOperators' -CreateIfNotExist {
            try {
                Get-ADGroup -Identity 'S-1-5-32-551'
            } catch {
                Write-Debug -Message ('Failed to retrieve Backup Operators group: {0}' -f $_.Exception.Message)
                $null
            }
        }

        $PrintOperators = Get-SafeVariable -Name 'PrintOperators' -CreateIfNotExist {
            try {
                Get-ADGroup -Identity 'S-1-5-32-550'
            } catch {
                Write-Debug -Message ('Failed to retrieve Print Operators group: {0}' -f $_.Exception.Message)
                $null
            }
        }

        $ServerOperators = Get-SafeVariable -Name 'ServerOperators' -CreateIfNotExist {
            try {
                Get-ADGroup -Identity 'S-1-5-32-549'
            } catch {
                Write-Debug -Message ('Failed to retrieve Server Operators group: {0}' -f $_.Exception.Message)
                $null
            }
        }
        #endregion Well-Known groups Variables

        #region Global groups Variables
        # ToDo: the GetSafeVariable is finding the variable, but variable has old DN. Interim fix filling the variable again
        $groupName = ('{0}{1}{2}' -f $NC['sg'], $NC['Delim'], $confXML.n.Admin.GG.Tier0ServiceAccount.Name)
        $SG_Tier0ServiceAccount = Get-AdObjectType -Identity $groupName


        # ToDo: the GetSafeVariable is finding the variable, but variable has old DN. Interim fix filling the variable again
        $groupName = ('{0}{1}{2}' -f $NC['sg'], $NC['Delim'], $confXML.n.Admin.GG.Tier1ServiceAccount.Name)
        $SG_Tier1ServiceAccount = Get-AdObjectType -Identity $groupName

        # ToDo: the GetSafeVariable is finding the variable, but variable has old DN. Interim fix filling the variable again
        $groupName = ('{0}{1}{2}' -f $NC['sg'], $NC['Delim'], $confXML.n.Admin.GG.Tier2ServiceAccount.Name)
        $SG_Tier2ServiceAccount = Get-AdObjectType -Identity $groupName

        $SG_Tier0Admins = Get-SafeVariable -Name 'SG_Tier0Admins' -CreateIfNotExist {
            $groupName = ('{0}{1}{2}' -f $NC['sg'], $NC['Delim'], $confXML.n.Admin.GG.Tier0Admins.Name)
            Get-AdObjectType -Identity $groupName
        }

        $SG_Tier1Admins = Get-SafeVariable -Name 'SG_Tier1Admins' -CreateIfNotExist {
            $groupName = ('{0}{1}{2}' -f $NC['sg'], $NC['Delim'], $confXML.n.Admin.GG.Tier1Admins.Name)
            Get-AdObjectType -Identity $groupName
        }

        $SG_Tier2Admins = Get-SafeVariable -Name 'SG_Tier2Admins' -CreateIfNotExist {
            $groupName = ('{0}{1}{2}' -f $NC['sg'], $NC['Delim'], $confXML.n.Admin.GG.Tier2Admins.Name)
            Get-AdObjectType -Identity $groupName
        }

        $SG_GlobalGroupAdmins = Get-SafeVariable -Name 'SG_GlobalGroupAdmins' -CreateIfNotExist {
            $groupName = ('{0}{1}{2}' -f $NC['sg'], $NC['Delim'], $confXML.n.Admin.GG.GlobalGroupAdmins.Name)
            Get-AdObjectType -Identity $groupName
        }
        #endregion Global groups Variables

        #region Local groups Variables
        $SL_GpoAdminRight = Get-SafeVariable -Name 'SL_GpoAdminRight' -CreateIfNotExist {
            $groupName = ('{0}{1}{2}' -f $NC['sl'], $NC['Delim'], $confXML.n.Admin.LG.GpoAdminRight.Name)
            Get-AdObjectType -Identity $groupName
        }

        $SL_AdRight = Get-SafeVariable -Name 'SL_AdRight' -CreateIfNotExist {
            $groupName = ('{0}{1}{2}' -f $NC['sl'], $NC['Delim'], $confXML.n.Admin.LG.AdRight.Name)
            Get-AdObjectType -Identity $groupName
        }

        $SL_InfraRight = Get-SafeVariable -Name 'SL_InfraRight' -CreateIfNotExist {
            $groupName = ('{0}{1}{2}' -f $NC['sl'], $NC['Delim'], $confXML.n.Admin.LG.InfraRight.Name)
            Get-AdObjectType -Identity $groupName
        }

        $SL_GlobalAppAccUserRight = Get-SafeVariable -Name 'SL_GlobalAppAccUserRight' -CreateIfNotExist {
            $groupName = ('{0}{1}{2}' -f $NC['sl'], $NC['Delim'], $confXML.n.Admin.LG.GlobalAppAccUserRight.Name)
            Get-AdObjectType -Identity $groupName
        }

        $SL_GlobalGroupRight = Get-SafeVariable -Name 'SL_GlobalGroupRight' -CreateIfNotExist {
            $groupName = ('{0}{1}{2}' -f $NC['sl'], $NC['Delim'], $confXML.n.Admin.LG.GlobalGroupRight.Name)
            Get-AdObjectType -Identity $groupName
        }
        #endregion Local groups Variables

        [String]$SitesOu = $confXML.n.Sites.OUs.SitesOU.Name
        [string]$SitesGlobalOu = $confXML.n.Sites.OUs.OuSiteGlobal.Name
        [string]$SitesGlobalGroupOu = $confXML.n.Sites.OUs.OuSiteGlobalGroups.Name
        [string]$SitesGlobalAppAccUserOu = $confXML.n.Sites.OUs.OuSiteGlobalAppAccessUsers.Name

        [String]$SitesOuDn = ('OU={0},{1}' -f $SitesOu, $Variables.AdDn)
        [string]$SitesGlobalOuDn = ('OU={0},{1}' -f $SitesGlobalOu, $SitesOuDn)
        [string]$SitesGlobalAppAccUserOuDn = ('OU={0},{1}' -f $SitesGlobalAppAccUserOu, $SitesGlobalOuDn)
        [string]$SitesGlobalGroupOuDn = ('OU={0},{1}' -f $SitesGlobalGroupOu, $SitesGlobalOuDn)

    } #end Begin

    Process {

        if ($PSCmdlet.ShouldProcess('Create Tier2 Organizational Units')) {

            # Update progress to show we're creating OUs
            $CurrentPhase++
            $ProgressParams['Status'] = $ProgressPhases[$CurrentPhase - 1]
            $ProgressParams['PercentComplete'] = [math]::Min([int](($CurrentPhase / $PhaseCount) * 100), 100)
            $ProgressParams['CurrentOperation'] = 'Creating base OUs'
            Write-Progress @ProgressParams

            New-DelegateAdOU -ouName $SitesOu -ouPath $Variables.AdDn -ouDescription $confXML.n.Sites.OUs.SitesOU.Description

            # Create Global OU within SITES area
            $Splat = @{
                ouName        = $SitesGlobalOu
                ouPath        = $SitesOuDn
                ouDescription = $confXML.n.Sites.OUs.OuSiteGlobal.Description
            }
            New-DelegateAdOU @Splat

            $ProgressParams['CurrentOperation'] = 'Creating sub-OUs'
            Write-Progress @ProgressParams

            $Splat = @{
                ouName        = $SitesGlobalGroupOu
                ouPath        = $SitesGlobalOuDn
                ouDescription = $confXML.n.Sites.OUs.OuSiteGlobalGroups.Description
            }
            New-DelegateAdOU @Splat

            $Splat = @{
                ouName        = $SitesGlobalAppAccUserOu
                ouPath        = $SitesGlobalOuDn
                ouDescription = $confXML.n.Sites.OUs.OuSiteGlobalAppAccessUsers.Description
            }
            New-DelegateAdOU @Splat

        } #end If ShouldProcess

        if ($PSCmdlet.ShouldProcess('Create Tier2 Baseline GPOs')) {

            # Update progress to show we're creating GPOs
            $CurrentPhase++
            $ProgressParams['Status'] = $ProgressPhases[$CurrentPhase - 1]
            $ProgressParams['PercentComplete'] = [math]::Min([int](($CurrentPhase / $PhaseCount) * 100), 100)
            $ProgressParams['CurrentOperation'] = 'Creating baseline GPOs for computer and user objects'
            Write-Progress @ProgressParams

            # Create basic GPO for Users and Computers
            $Splat = @{
                gpoDescription = '{0}-Baseline' -f $SitesOu
                gpoLinkPath    = $SitesOuDn
                GpoAdmin       = $sl_GpoAdminRight
                gpoBackupPath  = Join-Path -Path $DMScripts -ChildPath 'SecTmpl' -Resolve
            }
            New-DelegateAdGpo @Splat -gpoScope 'C' -gpoBackupID $confXML.n.Sites.OUs.OuSiteComputer.backupID
            New-DelegateAdGpo @Splat -gpoScope 'U' -gpoBackupID $confXML.n.Sites.OUs.OuSiteUser.backupID

        } #end If ShouldProcess

        if ($PSCmdlet.ShouldProcess('Create Tier2 GPO Restrictions')) {

            # Update progress to show we're configuring GPO restrictions
            $CurrentPhase++
            $ProgressParams['Status'] = $ProgressPhases[$CurrentPhase - 1]
            $ProgressParams['PercentComplete'] = [math]::Min([int](($CurrentPhase / $PhaseCount) * 100), 100)
            $ProgressParams['CurrentOperation'] = 'Configuring logon restrictions'
            Write-Progress @ProgressParams

            $DenyLogon.Clear()
            [void]$DenyLogon.Add($SchemaAdmins)
            [void]$DenyLogon.Add($EnterpriseAdmins)
            [void]$DenyLogon.Add($DomainAdmins)
            [void]$DenyLogon.Add($Administrators)
            [void]$DenyLogon.Add($AccountOperators)
            [void]$DenyLogon.Add($BackupOperators)
            [void]$DenyLogon.Add($PrintOperators)
            [void]$DenyLogon.Add($ServerOperators)
            if ($null -ne $AdminName) {
                [void]$DenyLogon.Add($AdminName)
            }
            if ($null -ne $NewAdminExists) {
                [void]$DenyLogon.Add($NewAdminExists)
            }
            if ($null -ne $SG_Tier0Admins) {
                [void]$DenyLogon.Add($SG_Tier0Admins)
            }
            if ($null -ne $SG_Tier1Admins) {
                [void]$DenyLogon.Add($SG_Tier1Admins)
            }

            $ProgressParams['CurrentOperation'] = 'Setting up privilege rights'
            Write-Progress @ProgressParams

            # Back up files and directories / Bypass traverse checking / Create Global Objects / Create symbolic links
            # Change System Time / Change Time Zone / Force shutdown from a remote system
            # Create Page File / Enable computer and user accounts to be trusted for delegation
            # Impersonate a client after authentication / Load and unload device drivers
            # Increase scheduling priority / Manage auditing and security log
            # Modify firmware environment values / Perform volume maintenance tasks
            # Profile single process / Profile system performance / Restore files and directories
            # Shut down the system / Take ownership of files or other objects
            $ArrayList.Clear()
            [void]$ArrayList.Add($Administrators)
            if ($null -ne $SG_Tier2Admins) {
                [void]$ArrayList.Add($SG_Tier2Admins)
            }
            $Splat = @{
                GpoToModify                = 'C-{0}-Baseline' -f $SitesOu
                DenyInteractiveLogon       = $DenyLogon
                DenyRemoteInteractiveLogon = $DenyLogon
                DenyBatchLogon             = @($SG_Tier0ServiceAccount, $SG_Tier1ServiceAccount)
                DenyServiceLogon           = @($SG_Tier0ServiceAccount, $SG_Tier1ServiceAccount)
                BatchLogon                 = $SG_Tier2ServiceAccount
                ServiceLogon               = $SG_Tier2ServiceAccount
                InteractiveLogon           = $SG_Tier2Admins
                RemoteInteractiveLogon     = $SG_Tier2Admins
                Backup                     = $ArrayList
                MachineAccount             = $ArrayList
                CreateGlobal               = @($ArrayList, 'LOCAL SERVICE', 'NETWORK SERVICE')
                Systemtime                 = @($ArrayList, 'LOCAL SERVICE')
                TimeZone                   = $ArrayList
                CreatePagefile             = $ArrayList
                CreateSymbolicLink         = $ArrayList
                RemoteShutDown             = $ArrayList
                Impersonate                = @($ArrayList, 'LOCAL SERVICE', 'NETWORK SERVICE', 'SERVICE')
                IncreaseBasePriority       = $ArrayList
                LoadDriver                 = $ArrayList
                AuditSecurity              = $ArrayList
                SystemEnvironment          = $ArrayList
                ManageVolume               = $ArrayList
                ProfileSingleProcess       = $ArrayList
                SystemProfile              = $ArrayList
                Restore                    = $ArrayList
                Shutdown                   = $ArrayList
                TakeOwnership              = $ArrayList
            }
            Set-GpoPrivilegeRight @Splat

        } #end If ShouldProcess

        if ($PSCmdlet.ShouldProcess('Create Tier2 Delegations')) {

            # Update progress to show we're setting up delegations
            $CurrentPhase++
            $ProgressParams['Status'] = $ProgressPhases[$CurrentPhase - 1]
            $ProgressParams['PercentComplete'] = [math]::Min([int](($CurrentPhase / $PhaseCount) * 100), 100)
            $ProgressParams['CurrentOperation'] = 'Setting up OU delegations'
            Write-Progress @ProgressParams

            # Sites OU
            # Create/Delete OUs within Sites
            Set-AdAclCreateDeleteOU -Group $SL_InfraRight -LDAPpath $SitesOuDn

            # Sites OU
            # Change OUs
            Set-AdAclChangeOU -Group $SL_AdRight -LDAPpath $SitesOuDn

            $ProgressParams['CurrentOperation'] = 'Setting up application access user delegation'
            Write-Progress @ProgressParams

            Write-Verbose -Message 'START APPLICATION ACCESS USER Global Delegation'

            # USER Site Administrator Delegation
            $Splat = @{
                Group    = $SL_GlobalAppAccUserRight
                LDAPPath = $SitesGlobalAppAccUserOuDn
            }
            Set-AdAclDelegateUserAdmin @Splat

            #### GAL
            Set-AdAclDelegateGalAdmin @Splat

            Add-AdGroupNesting -Identity $SL_GlobalAppAccUserRight -Members $SG_GlobalUserAdmins

            $ProgressParams['CurrentOperation'] = 'Setting up group delegation'
            Write-Progress @ProgressParams

            Write-Verbose -Message 'START GROUP Global Delegation'

            # Create/Delete Groups
            Set-AdAclCreateDeleteGroup -Group $SL_GlobalGroupRight -LDAPpath $SitesGlobalGroupOuDn

            # Nest groups
            Add-AdGroupNesting -Identity $SL_GlobalGroupRight -Members $SG_GlobalGroupAdmins

            #### GAL

            # Change Group Properties
            Set-AdAclChangeGroup -Group $SL_GlobalGroupRight -LDAPpath $SitesGlobalGroupOuDn

        } #end If ShouldProcess

    } #end Process

    End {
        # Complete the progress bar
        $ProgressParams['Status'] = 'Completed'
        $ProgressParams['PercentComplete'] = 100
        $ProgressParams['CurrentOperation'] = 'Tier2 infrastructure setup complete'
        Write-Progress @ProgressParams

        # Clear the progress bar after completion
        Write-Progress -Activity $ProgressParams['Activity'] -Completed

        # Stop transcript if it was started
        if ($EnableTranscript) {
            try {
                Stop-Transcript -ErrorAction Stop
                Write-Verbose -Message 'Transcript stopped successfully'
            } catch {
                Write-Warning -Message ('Failed to stop transcript: {0}' -f $_.Exception.Message)
            } #end Try-Catch
        } #end If

        if ($null -ne $Variables -and
            $null -ne $Variables.Footer) {

            $txt = ($Variables.Footer -f $MyInvocation.InvocationName,
                'creating Tier2 objects.'
            )
            Write-Verbose -Message $txt
        } #end If
    } #end End
} #end Function New-Tier2