ExportedFunctions/Set-DryADConfiguration.ps1

Using Namespace System.IO
Using Namespace System.Collections.Generic
# 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.
Function Set-DryADConfiguration {
    [CmdLetBinding(DefaultParameterSetName='Local')]
    Param (
        [Parameter(HelpMessage="Variables are used for replacements in configs and GPOs")]
        [List[PSObject]] 
        $Variables,

        [Parameter(Mandatory,HelpMessage="Path of the configuration directory")]
        [DirectoryInfo] 
        $ConfigurationPath,

        [Parameter(HelpMessage="Shortname (not IP!) of the computer for which the configuration is run")]
        [String] 
        $ComputerName,

        [Parameter(HelpMessage="Active Directory site name for which the configuration is done")]
        [String] 
        $ADSite,

        [Parameter(HelpMessage="Array of one or more components to process; OUs, RightsGroups, RoleGroups, GroupMembers (or 'Groups' for
        all the previous 3), Rights, GPOImports, GPOLinks (or 'GPOs' for both), WMIFilterImports, WMIFilterLinks (or just 'WMIFilters' for
        both), ADSchema, NETLOGON, AdmTemplates, Users, UserGroupMembers. By default, all are processed. Only specify during development to
        limit the scope"
)]
        [ValidateSet('OUs','RightsGroups','RoleGroups','GroupMembers','Groups','Rights','GPOImports','GPOLinks','GPOs','WMIFilters',
        'WMIFilterImports','WMIFilterLinks','ADSchema','NETLOGON','AdmTemplates','Users','UserGroupMembers')]
        [String[]] 
        $Components,

        [Parameter(Mandatory,ParameterSetName='Remote',HelpMessage="Specify a resolvable name or IP to a Domain Controller to remote into")]
        [Parameter(Mandatory,ParameterSetName='Local',HelpMessage="Specify a resolvable name or IP to a Domain Controller to perform AD actions on")]
        [String] 
        $DomainController,

        [Parameter(Mandatory,ParameterSetName='Remote')]
        [PSSession] 
        $PSSession,

        [Parameter(HelpMessage="Should only be `$True when called from DryDeploy. Will allow autogenerated
        passwords for users that are created to be stored in DryDeploy's Credentials store. If you're running
        standalone (not as part of DryDeploy, hence `$DryDeploy = `$False), then passwords of users that are
        configured with the property .password.get_or_generate = 'generate', are autogenerated, but lost. You
        will have to reset the passwords of the created users to access those accounts. Use instead
        .password.get_or_generate = 'get' to be prompted for the credentials."
)]
        [Switch]$DryDeploy
    )
    Try {
        [String]$ExecutionType = $PSCmdlet.ParameterSetName
        If ($ExecutionType -eq 'Remote') {
            $ConfigurationPublicCertificatePath = Join-Path -Path $ConfigurationPath -ChildPath "RemoteSystemPublicCertificate.cer"
            $DomainDN = Get-DryADDomainProperty -Property 'DistinguishedName' -PSSession $PSSession
            $DomainFQDN = Get-DryADDomainProperty -Property 'DNSRoot' -PSSession $PSSession
            $DomainNB = Get-DryADDomainProperty -Property 'NetBIOSName' -PSSession $PSSession
        }
        Else {
            $DomainDN = Get-DryADDomainProperty -Property 'DistinguishedName' -DomainController $DomainController
            $DomainFQDN = Get-DryADDomainProperty -Property 'DNSRoot' -DomainController $DomainController
            $DomainNB = Get-DryADDomainProperty -Property 'NetBIOSName' -DomainController $DomainController
        }

        ol i 'Set-DryADConfiguration started' -sh
        ol i 'Execution Type',    "$ExecutionType"
        ol i 'ConfigurationPath', "$ConfigurationPath"
        ol i 'Domain scope',      "$DomainFQDN"
        ol i 'Site scope',        "$ADSite"
        ol i 'Computer scope',    "$ComputerName"
      
        $SourceGPOsPath = Join-Path -Path $ConfigurationPath -ChildPath "GPOs"
        
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # REPLACEMENT HASH
        # The Replacement Hash is mainly used for string replacement in the module
        # 'GPOManagement', which is not part of DryDeploy or DryActiveDirectory. Only
        # in use if you have access to that module and use 'jsonified' GPOs.
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        $ReplacementHash = @{}
        ForEach ($Var in $Variables) {
            $ReplacementHash.Add("###$($Var.Name)###",$Var.Value)
        }
        
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # ROLE CONFIGURATION OBJECT
        # Pick up all jsons (*.json) and commented jsons (*.jsonc) in
        # $ConfigurationPath, and merge into the $RoleConfiguration
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        Remove-Variable -Name RoleConfiguration,ADConfObject,ConfigurationPathFiles -ErrorAction Ignore
        $RoleConfiguration = New-Object -TypeName PSObject
        $ConfigurationPathFiles = @(Get-ChildItem -Path "$ConfigurationPath\*" -Include "*.jsonc","*.json" -ErrorAction Stop)
        
        ForEach ($ADConfFile in $ConfigurationPathFiles) {
            $ADConfObject = Get-DryADCommentedJson -Path $ADConfFile.FullName -ErrorAction Stop
            $RoleConfiguration = (Merge-DryADPSObjects -FirstObject $RoleConfiguration -SecondObject $ADConfObject)
        }
 
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # CASING
        # Variables that specifies case modification of OUs, GPOs, groups and users.
        # Valid values are 'upper', lower', 'capitalized' and 'ignore'. Defaults
        # to ignore
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        [String]$OUCase      = 'ignore'
        [String]$GPOCase     = 'ignore'
        [String]$GroupCase   = 'ignore'
        [String]$UserCase    = 'ignore'

        If ($RoleConfiguration.casing.ad_organizational_unit_case) {
            [String]$OUCase = $RoleConfiguration.casing.ad_organizational_unit_case
        }
        If ($RoleConfiguration.casing.ad_gpo_case) {
            [String]$GPOCase = $RoleConfiguration.casing.ad_gpo_case
        }
        If ($RoleConfiguration.casing.ad_group_case) { 
            [String]$GroupCase = $RoleConfiguration.casing.ad_group_case
        }
        If ($RoleConfiguration.casing.ad_user_case) { 
            [String]$UserCase = $RoleConfiguration.casing.ad_user_case
        }

        ol v 'Casing - OUs',    "$OUCase"
        ol v 'Casing - GPOs',   "$GPOCase"
        ol v 'Casing - Groups', "$GroupCase"
        ol v 'Casing - Users',  "$UserCase"

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # PROGRESS COUNTERS
        # Counts configurations to process before start, to show
        # a progress bar during configuration
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        [int]$ElementsCounter = 0
        [int]$NumberOfElementsToProcess = 0

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # AD SCHEMA
        # Action: Get and Count
        # Dataset: $RoleConfiguration
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If (Test-Path -Path "$ConfigurationPath\ADSchema" -ErrorAction Ignore) {
            
            If (
                ($Components -icontains 'ADSchema') -or 
                ($Null -eq $Components)
            ) { 
                $ProcessADSchema = $True
                # [Array]$ADSchemaExtensions = $RoleConfiguration.active_directory_schema_updates
                $ADSchemaExtensions = @(Get-ChildItem -Path "$ConfigurationPath\ADSchema\*" -Include "*.LDF")
                $NumberOfElementsToProcess += $ADSchemaExtensions.Count
            } 
            $NumberOfADSchemaExtensions = $ADSchemaExtensions.Count
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # NETLOGON
        # Action: Count
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If (Test-Path -Path "$ConfigurationPath\NETLOGON" -ErrorAction Ignore) {  
            If (
                ($Components -icontains 'NETLOGON') -or 
                ($Null -eq $Components)
            ) { 
                $ProcessNETLOGON = $True
                $NumberOfElementsToProcess++
                $NumberOfNETLOGONs = 1
            } 
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # AdmTemplates
        # Action: Count
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If (Test-Path -Path "$ConfigurationPath\AdmTemplates" -ErrorAction Ignore) {  
            If (
                ($Components -icontains 'AdmTemplates') -or 
                ($Null -eq $Components)
            ) { 
                $ProcessAdmTemplates = $True
                $NumberOfElementsToProcess++
            } 
        }


        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # ORGANIZATIONAL UNITS
        # Action: Get, Count and String Replacement
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If ($RoleConfiguration.active_directory_ou_schema) {
            # Elements have a scope; domain, site or computer.

            [Array]$DomainOUs += @($RoleConfiguration.active_directory_ou_schema | 
            Where-Object {  
                $_.scope -contains 'domain' 
            } )
           
            [Array]$SiteOUs += @($RoleConfiguration.active_directory_ou_schema | 
            Where-Object {  
                $_.scope -contains 'site'
            } )

            [Array]$ComputerOUs += @($RoleConfiguration.active_directory_ou_schema | 
            Where-Object {  
                $_.scope -contains 'computer'
            } )

            If (($Components -icontains 'OUs') -or ($Null -eq $Components)) { 
                $ProcessOUs = $True 
                $NumberOfElementsToProcess += (
                    $DomainOUs.Count + $SiteOUs.Count + $ComputerOUs.Count
                )
            }
        }

        # Replace all replacement patterns
        If ($DomainOUs) {
            $DomainOUs = Resolve-DryADReplacementPatterns -inputobject $DomainOUs -Variables $Variables
        }
        If ($SiteOUs) {
            $SiteOUs = Resolve-DryADReplacementPatterns -inputobject $SiteOUs -Variables $Variables
        }
        If ($ComputerOUs) {
            $ComputerOUs = Resolve-DryADReplacementPatterns -inputobject $ComputerOUs -Variables $Variables
        }
        $NumberOfOUs = $DomainOUs.Count + $SiteOUs.Count + $ComputerOUs.Count

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # ORGANIZATIONAL UNITS PATHS
        # Action: Resolve Path from Paths
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        # DomainOUs that are missing 'path'-property must resolve it from 'paths'
        ForEach ($OU in $DomainOUs | Where-Object { ($Null -eq $_.Path) -and ($Null -ne $_.Paths) }) {

            Remove-Variable -Name Path -ErrorAction Ignore
            $Path = $OU.paths.domain

            If ($Null -eq $Path) {
                ol e "Unable to find `$OU.paths.domain on '$($OU.Tag)'"
                Throw "Unable to find `$OU.paths.domain on '$($OU.Tag)'"
            }
            ElseIf ($Path -is [Array]) {
                ol e "Multiple definitions `$OU.paths.domain on '$($OU.Tag)'"
                Throw "Multiple definitions `$OU.paths.domain on '$($OU.Tag)'"
            }
            
            # Add Path property, then remove .paths
            $OU | Add-Member -MemberType NoteProperty -Name 'Path' -Value $Path -Force
            $OU.PSObject.Properties.Remove('paths')
            
            Remove-Variable -Name Path -ErrorAction Ignore
        }

        # SiteOUs that are missing 'path'-property must resolve it from 'paths'
        ForEach ($OU in $SiteOUs | Where-Object { ($Null -eq $_.Path) -and ($Null -ne $_.Paths) }) {

            Remove-Variable -Name Path -ErrorAction Ignore
            $Path = $OU.paths.site

            If ($Null -eq $Path) {
                ol e "Unable to find `$OU.paths.Site on '$($OU.Tag)'"
                Throw "Unable to find `$OU.paths.Site on '$($OU.Tag)'"
            }
            ElseIf ($Path -is [Array]) {
                ol e "Multiple definitions `$OU.paths.Site on '$($OU.Tag)'"
                Throw "Multiple definitions `$OU.paths.Site on '$($OU.Tag)'"
            }
            
            # Add Path property, then remove .paths
            $OU | Add-Member -MemberType NoteProperty -Name 'Path' -Value $Path -Force
            $OU.PSObject.Properties.Remove('paths')
            
            Remove-Variable -Name Path -ErrorAction Ignore
        }

        # ComputerOUs that are missing 'path'-property must resolve it from 'paths'
        ForEach ($OU in $ComputerOUs | Where-Object { ($Null -eq $_.Path) -and ($Null -ne $_.Paths) }) {

            Remove-Variable -Name Path -ErrorAction Ignore
            $Path = $OU.paths.computer

            If ($Null -eq $Path) {
                ol e "Unable to find `$OU.paths.computer on '$($OU.Tag)'"
                Throw "Unable to find `$OU.paths.computer on '$($OU.Tag)'"
            }
            ElseIf ($Path -is [Array]) {
                ol e "Multiple definitions `$OU.paths.computer on '$($OU.Tag)'"
                Throw "Multiple definitions `$OU.paths.computer on '$($OU.Tag)'"
            }
            
            # Add Path property, then remove .paths
            $OU | Add-Member -MemberType NoteProperty -Name 'Path' -Value $Path -Force
            $OU.PSObject.Properties.Remove('paths')
            
            Remove-Variable -Name Path -ErrorAction Ignore
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # ORGANIZATIONAL UNITS PATHS
        # Action: Resolve Child Paths from Parents
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        
        $AnOUWasResolved = $True
        Do {
            $AnOUWasResolved = $False 
            # Loop through DomainOUs that already have a path
            ForEach ($ResolvedOU in $DomainOUs | 
            Where-Object { 
                $null -ne $_.path 
            }) {
                
                # Loop through DomainOUs that do not have a path
                ForEach ($UnresolvedOU in $DomainOUs | 
                Where-Object { 
                    $null -eq $_.path 
                }) {
                    Try {
                        Remove-Variable -Name Parent -ErrorAction Ignore
                        $Parent = $UnresolvedOU.parents | 
                        Where-Object { 
                            ($_.parentscope -eq 'domain') -and
                            ($_.scope -eq 'domain') -and
                            ($_.tag -eq $ResolvedOU.tag) 
                        }
                        
                        If ($Parent) {
                            $Path = Get-DryADOUPathFromTag -Tag $Parent.tag -OUs $DomainOUs -Scope 'domain' -Child $Parent.child
                            $AnOUWasResolved = $True
                            # Add Path property
                            $UnresolvedOU | Add-Member -MemberType NoteProperty -Name 'Path' -Value $Path -Force
                            # Once resolved, remove parents
                            $UnresolvedOU.PSObject.Properties.Remove('parents')
                        }
                    }
                    Catch {
                        $PSCmdLet.ThrowTerminatingError($_)
                    }
                    Finally {
                        Remove-Variable -Name Path,Parent -ErrorAction Ignore
                    }
                }

                # Loop through SiteOUs that do not have a path
                ForEach ($UnresolvedOU in $SiteOUs | 
                Where-Object { 
                    $null -eq $_.path 
                }) {
                    Try {
                        Remove-Variable -Name Parent -ErrorAction Ignore
                        $Parent = $UnresolvedOU.parents | 
                        Where-Object { 
                            ($_.parentscope -eq 'domain') -and
                            ($_.scope -eq 'site')         -and
                            ($_.tag -eq $ResolvedOU.tag) 
                        }
                        
                        If ($Parent) {
                            $Path = Get-DryADOUPathFromTag -Tag $Parent.tag -OUs $DomainOUs -Scope 'site' -Child $Parent.child
                            $AnOUWasResolved = $True
                            # Add Path property
                            $UnresolvedOU | Add-Member -MemberType NoteProperty -Name 'Path' -Value $Path -Force
                            # Once resolved, remove parents
                            $UnresolvedOU.PSObject.Properties.Remove('parents')
                        }
                    }
                    Catch {
                        $PSCmdLet.ThrowTerminatingError($_)
                    }
                    Finally {
                        Remove-Variable -Name Path,Parent -ErrorAction Ignore
                    }
                }

                # Loop through ComputerOUs that do not have a path
                ForEach ($UnresolvedOU in $ComputerOUs | 
                Where-Object { 
                    $null -eq $_.path 
                }) {
                    Try {
                        Remove-Variable -Name Parent -ErrorAction Ignore
                        $Parent = $UnresolvedOU.parents | 
                        Where-Object { 
                            ($_.parentscope -eq 'domain') -and
                            ($_.scope -eq 'computer') -and
                            ($_.tag -eq $ResolvedOU.tag) 
                        }
                        
                        If ($Parent) {
                            $Path = Get-DryADOUPathFromTag -Tag $Parent.tag -OUs $DomainOUs -Scope 'computer' -Child $Parent.child
                            $AnOUWasResolved = $True
                            # Add Path property
                            $UnresolvedOU | Add-Member -MemberType NoteProperty -Name 'Path' -Value $Path -Force
                            # Once resolved, remove parents
                            $UnresolvedOU.PSObject.Properties.Remove('parents')
                        }
                    }
                    Catch {
                        $PSCmdLet.ThrowTerminatingError($_)
                    }
                    Finally {
                        Remove-Variable -Name Path,Parent -ErrorAction Ignore
                    }
                }
            }

            # Loop through SiteOUs that already have a path
            ForEach ($ResolvedOU in $SiteOUs | 
            Where-Object { 
                $null -ne $_.Path 
            }) {
                # Loop through SiteOUs that do not have a path
                ForEach ($UnresolvedOU in $SiteOUs | 
                Where-Object { 
                    $null -eq $_.path 
                }) {
                    Try {
                        Remove-Variable -Name Parent -ErrorAction Ignore
                        $Parent = $UnresolvedOU.parents | 
                        Where-Object { 
                            ($_.parentscope -eq 'site') -and
                            ($_.scope -eq 'site') -and
                            ($_.tag -eq $ResolvedOU.tag) 
                        }
                        
                        If ($Parent) {
                            $Path = Get-DryADOUPathFromTag -Tag $Parent.tag -OUs $SiteOUs -Scope 'site' -Child $Parent.child
                            $AnOUWasResolved = $True
                            # Add Path property
                            $UnresolvedOU | Add-Member -MemberType NoteProperty -Name 'Path' -Value $Path -Force
                            # Once resolved, remove parents
                            $UnresolvedOU.PSObject.Properties.Remove('parents')
                        }
                    }
                    Catch {
                        $PSCmdLet.ThrowTerminatingError($_)
                    }
                    Finally {
                        Remove-Variable -Name Path,Parent -ErrorAction Ignore
                    }
                }

                # Loop through ComputerOUs that do not have a path
                ForEach ($UnresolvedOU in $ComputerOUs | 
                Where-Object { 
                    $null -eq $_.path 
                }) {
                    Try {
                        Remove-Variable -Name Parent -ErrorAction Ignore
                        $Parent = $UnresolvedOU.parents | 
                        Where-Object { 
                            ($_.parentscope -eq 'site') -and
                            ($_.scope -eq 'computer') -and
                            ($_.tag -eq $ResolvedOU.tag) 
                        }
                        
                        If ($Parent) {
                            $Path = Get-DryADOUPathFromTag -Tag $Parent.tag -OUs $SiteOUs -Scope 'computer' -Child $Parent.child
                            $AnOUWasResolved = $True
                            # Add Path property
                            $UnresolvedOU | Add-Member -MemberType NoteProperty -Name 'Path' -Value $Path -Force
                            # Once resolved, remove parents
                            $UnresolvedOU.PSObject.Properties.Remove('parents')
                        }
                    }
                    Catch {
                        $PSCmdLet.ThrowTerminatingError($_)
                    }
                    Finally {
                        Remove-Variable -Name Path,Parent -ErrorAction Ignore
                    }
                }
            }

            # Loop through ComputerOUs that already have a path
            ForEach ($ResolvedOU in $ComputerOUs | 
            Where-Object { 
                $null -ne $_.Path 
            }) {

                # Loop through ComputerOUs that do not have a path
                ForEach ($UnresolvedOU in $ComputerOUs | 
                Where-Object { 
                    $null -eq $_.path 
                }) {
                    Try {
                        Remove-Variable -Name Parent -ErrorAction Ignore
                        $Parent = $UnresolvedOU.parents | 
                        Where-Object { 
                            ($_.parentscope -eq 'computer') -and
                            ($_.scope -eq 'computer') -and
                            ($_.tag -eq $ResolvedOU.tag) 
                        }
                        
                        If ($Parent) {
                            $Path = Get-DryADOUPathFromTag -Tag $Parent.tag -OUs $ComputerOUs -Scope 'computer' -Child $Parent.child
                            $AnOUWasResolved = $True
                            # Add Path property
                            $UnresolvedOU | Add-Member -MemberType NoteProperty -Name 'Path' -Value $Path -Force
                            # Once resolved, remove parents
                            $UnresolvedOU.PSObject.Properties.Remove('parents')
                        }
                    }
                    Catch {
                        $PSCmdLet.ThrowTerminatingError($_)
                    }
                    Finally {
                        Remove-Variable -Name Path,Parent -ErrorAction Ignore
                    }
                }
            }  
        }
        While ($AnOUWasResolved)

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # ORGANIZATIONAL UNITS PATHS
        # Action: Display resolved paths
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If ($DomainOUs.count -gt 0) {
            ol i "Resolved Domain OU Tags" -sh 
            ForEach ($OU in $DomainOUs) {
                ol i "$($OU.Tag)","$($OU.Path)"
            }
        }
        
        If ($SiteOUs.count -gt 0) {
            ol i "Resolved Site OU Tags" -sh 
            ForEach ($OU in $SiteOUs) {
                ol i "$($OU.Tag)","$($OU.Path)"
            }
        }
        
        If ($ComputerOUs.count -gt 0) {
            ol i "Resolved Computer OU Tags" -sh 
            ForEach ($OU in $ComputerOUs) {
                ol i "$($OU.Tag)","$($OU.Path)"
            }
        }
        
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # ORGANIZATIONAL UNITS PATHS
        # Action: Convert To 'distinguishedName' and Correct Case
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        ForEach ($OU in $DomainOUs) {
            $OU.Path = ConvertTo-DryADDistinguishedName -Name $OU.Path -Case $OUcase
            ol d "Domain OU dN for $($OU.Tag) is: '$($OU.Path)'" 
        }
        ForEach ($OU in $SiteOUs) {
            $OU.Path = ConvertTo-DryADDistinguishedName -Name $OU.Path -Case $OUcase
            ol d "Site OU dN for $($OU.Tag) is: '$($OU.Path)'" 
        }
        ForEach ($OU in $ComputerOUs) {
            $OU.Path = ConvertTo-DryADDistinguishedName -Name $OU.Path -Case $OUcase
            ol d "Site OU dN for $($OU.Tag) is: '$($OU.Path)'" 
        }
        

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # WMI Filter Imports and Links
        # Action: Get, Count and String Replacement
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If (
            ($Components -icontains 'WMIFilters') -or 
            ($Components -icontains 'WMIFilterImports') -or 
            ($Components -icontains 'WMIFilterLinks') -or 
            ($Null -eq $Components)
        ) { 
            If ($RoleConfiguration.wmifilters) {
                
                # Get the elements
                [Array]$DomainWMIFilters += @($RoleConfiguration.wmifilters | Where-Object {  
                    $_.scope -eq 'domain' 
                } )
            
                [Array]$SiteWMIFilters += @($RoleConfiguration.wmifilters | Where-Object {  
                    $_.scope -eq 'site'
                } )

                [Array]$ComputerWMIFilters += @($RoleConfiguration.wmifilters | Where-Object {  
                    $_.scope -eq 'computer'
                } )

                # Replace any replacement pattern
                $DomainWMIFilters   = Resolve-DryADReplacementPatterns -inputobject $DomainWMIFilters -Variables $Variables
                $SiteWMIFilters     = Resolve-DryADReplacementPatterns -inputobject $SiteWMIFilters -Variables $Variables
                $ComputerWMIFilters = Resolve-DryADReplacementPatterns -inputobject $ComputerWMIFilters -Variables $Variables

                # Count the WMIFilters, but only if we're actually importing them
                If (
                    ($Components -icontains 'WMIFilters') -or 
                    ($Components -icontains 'WMIFilterImports') -or 
                    ($Null -eq $Components)
                ) {
                    $ProcessWMIFilterImports = $True
                    $NumberOfElementsToProcess += ($DomainWMIFilters.Count + $SiteWMIFilters.Count + $ComputerWMIFilters.Count)
                    $NumberOfWMIFilters += ($DomainWMIFilters.Count + $SiteWMIFilters.Count + $ComputerWMIFilters.Count)
                }

                If (
                    ($Components -icontains 'WMIFilters') -or 
                    ($Components -icontains 'WMIFilterLinks') -or 
                    ($Null -eq $Components)
                ) {
                    $ProcessWMIFilterLinks = $True

                    $DomainWmiFilterLinksCount = 0
                    $DomainWMIFilters.ForEach({
                        $_.links.ForEach({
                            $DomainWmiFilterLinksCount++
                        })
                    })

                    $SiteWmiFilterLinksCount = 0
                    $SiteWMIFilters.ForEach({
                        $_.links.ForEach({
                            $SiteWmiFilterLinksCount++
                        })
                    })
                    $ComputerWmiFilterLinksCount = 0
                    $ComputerWMIFilters.ForEach({
                        $_.links.ForEach({
                            $ComputerWmiFilterLinksCount++
                        })
                    })
                    $NumberOfWMIFilterLinks = $DomainWmiFilterLinksCount + $SiteWmiFilterLinksCount + $ComputerWmiFilterLinksCount
                    $NumberOfElementsToProcess += $NumberOfWMIFilterLinks
                }
            }
        }


        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # RIGHTS GROUPS
        # Action: Get, Count, String Replacement, Resolve Paths and Convert Case
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        If ($RoleConfiguration.rights_groups) {
            
            # Get domain scoped rights groups
            $DomainRightsGroups = @($RoleConfiguration.rights_groups | Where-Object {  
                $_.scope -eq 'domain' 
            } )
            
            # Get site scoped rights groups
            $SiteRightsGroups = @($RoleConfiguration.rights_groups | Where-Object {  
                $_.scope -eq 'site' 
            } )

            # Get computer scoped rights groups
            $ComputerRightsGroups = @($RoleConfiguration.rights_groups | Where-Object {  
                $_.scope -eq 'computer' 
            } )

            # Count
            If (
                ($Components -icontains 'RightsGroups') -or 
                ($Components -icontains 'Groups') -or 
                ($Null -eq $Components)
            ) { 
                $ProcessRightsGroups = $True
                $NumberOfElementsToProcess += ($DomainRightsGroups.Count + $SiteRightsGroups.Count + $ComputerRightsGroups.Count) 
                $NumberOfRightsGroups = ($DomainRightsGroups.Count + $SiteRightsGroups.Count + $ComputerRightsGroups.Count)
            }
            
            # Replace any replacement pattern
            # Replace regardless of $Components -icontains 'RightsGroups', since RightsGroups are referenced by Rights, GroupMembers and GPOImports
            $DomainRightsGroups   = Resolve-DryADReplacementPatterns -inputobject $DomainRightsGroups -Variables $Variables
            $SiteRightsGroups     = Resolve-DryADReplacementPatterns -inputobject $SiteRightsGroups -Variables $Variables
            $ComputerRightsGroups = Resolve-DryADReplacementPatterns -inputobject $ComputerRightsGroups -Variables $Variables

            # Resolve Domain OU Paths from schema
            ForEach ($RightsGroup in $DomainRightsGroups) {
                If ($NULL -eq $RightsGroup.path) {

                    # Resolve domain paths from OU schema
                    $Path = Get-DryADOUPathFromTag -Tag $RightsGroup.Tag -OUs $DomainOUs -Scope 'domain'
                    $RightsGroup | Add-Member -MemberType NoteProperty -Name Path -Value $Path
                }
                # Convert to $GroupCase
                $RightsGroup.groupname = ConvertTo-DryADCase -Name $RightsGroup.groupname -Case $Groupcase
            }

            # Resolve Site OU Paths from schema
            ForEach ($RightsGroup in $SiteRightsGroups) {
                If ($NULL -eq $RightsGroup.path) {

                    # Resolve domain paths from OU schema
                    $Path = Get-DryADOUPathFromTag -Tag $RightsGroup.Tag -OUs $SiteOUs -Scope 'site'
                    $RightsGroup | Add-Member -MemberType NoteProperty -Name Path -Value $Path
                }
                # Convert to $GroupCase
                $RightsGroup.groupname = ConvertTo-DryADCase -Name $RightsGroup.groupname -Case $Groupcase
            }

            # Resolve Site OU Paths from schema
            ForEach ($RightsGroup in $ComputerRightsGroups) {
                If ($NULL -eq $RightsGroup.path) {

                    # Resolve domain paths from OU schema
                    $Path = Get-DryADOUPathFromTag -Tag $RightsGroup.Tag -OUs $ComputerOUs -Scope 'computer'
                    $RightsGroup | Add-Member -MemberType NoteProperty -Name Path -Value $Path
                }
                # Convert to $GroupCase
                $RightsGroup.groupname = ConvertTo-DryADCase -Name $RightsGroup.groupname -Case $Groupcase
            }
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # ROLE GROUPS
        # Action: Count and String replacement
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If ($RoleConfiguration.role_groups) {
            
            # Get domain scoped rights groups
            $DomainRoleGroups = @($RoleConfiguration.role_groups | Where-Object {  
                $_.scope -eq 'domain' 
            } )
            
            # Get site scoped rights groups
            $SiteRoleGroups = @($RoleConfiguration.role_groups | Where-Object {  
                $_.scope -eq 'site' 
            } )

            # Get computer scoped rights groups
            $ComputerRoleGroups = @($RoleConfiguration.role_groups | Where-Object {  
                $_.scope -eq 'computer' 
            } )

            # Count
            If (
                ($Components -icontains 'RoleGroups') -or 
                ($Components -icontains 'Groups') -or 
                ($Null -eq $Components)
            ) { 
                $ProcessRoleGroups = $True
                $NumberOfElementsToProcess += ($DomainRoleGroups.Count + $SiteRoleGroups.Count + $ComputerRoleGroups.Count)
                $NumberOfRoleGroups = ($DomainRoleGroups.Count + $SiteRoleGroups.Count + $ComputerRoleGroups.Count) 
            }

            # Replace any replacement pattern
            # Replace regardless of $Components -icontains 'RightsGroups', since groups are referenced by -component GroupMembers
            $DomainRoleGroups   = @(Resolve-DryADReplacementPatterns -inputobject $DomainRoleGroups -Variables $Variables)
            $SiteRoleGroups     = @(Resolve-DryADReplacementPatterns -inputobject $SiteRoleGroups -Variables $Variables)
            $ComputerRoleGroups = @(Resolve-DryADReplacementPatterns -inputobject $ComputerRoleGroups -Variables $Variables)


            # Resolve domain OU paths from schema
            ForEach ($RoleGroup in $DomainRoleGroups) {
                If ($NULL -eq $RoleGroup.path) {
                    # Resolve domain paths from OU schema
                    $Path = Get-DryADOUPathFromTag -Tag $RoleGroup.Tag -OUs $DomainOUs -Scope 'domain'
                    $RoleGroup | Add-Member -MemberType NoteProperty -Name Path -Value $Path
                }
                # Convert to $GroupCase
                $RoleGroup.groupname = ConvertTo-DryADCase -Name $RoleGroup.groupname -Case $Groupcase
            }

            # Resolve site OU paths from schema
            ForEach ($RoleGroup in $SiteRoleGroups) {
                If ($NULL -eq $RoleGroup.path) {
                    # Resolve site paths from OU schema
                    $Path = Get-DryADOUPathFromTag -Tag $RoleGroup.Tag -OUs $SiteOUs -Scope 'site'
                    $RoleGroup | Add-Member -MemberType NoteProperty -Name Path -Value $Path
                }
                # Convert to $GroupCase
                $RoleGroup.groupname = ConvertTo-DryADCase -Name $RoleGroup.groupname -Case $Groupcase
            }

            # Resolve computer OU paths from schema
            ForEach ($RoleGroup in $ComputerRoleGroups) {
                If ($NULL -eq $RoleGroup.path) {
                    # Resolve site paths from OU schema
                    $Path = Get-DryADOUPathFromTag -Tag $RoleGroup.Tag -OUs $ComputerOUs -Scope 'computer'
                    $RoleGroup | Add-Member -MemberType NoteProperty -Name Path -Value $Path
                }
                # Convert to $GroupCase
                $RoleGroup.groupname = ConvertTo-DryADCase -Name $RoleGroup.groupname -Case $Groupcase
            }
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # GROUP MEMBERS
        # Action: Count
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        # Count Members and MemberOfs
        $NumberOfMemberAndMemberOf         = 0
        $NumberOfDomainMemberAndMemberOf   = 0
        $NumberOfSiteMemberAndMemberOf     = 0
        $NumberOfComputerMemberAndMemberOf = 0
        If (
            ($Components -icontains 'GroupMembers') -or 
            ($Components -icontains 'Groups') -or 
            ($Null -eq $Components)
        ) { 
            $ProcessGroupMembers = $True
            
            # Domain Scope - Both Role and Rights groups may have .member and .memberof
            ForEach ($DomainRoleGroup in $DomainRoleGroups) {
                $NumberOfDomainMemberAndMemberOf += $DomainRoleGroup.Member.Count
                $NumberOfDomainMemberAndMemberOf += $DomainRoleGroup.MemberOf.Count
            }

            ForEach ($DomainRightsGroup in $DomainRightsGroups) {
                $NumberOfDomainMemberAndMemberOf += $DomainRightsGroup.Member.Count
                $NumberOfDomainMemberAndMemberOf += $DomainRightsGroup.MemberOf.Count
            }
            $NumberOfElementsToProcess += $NumberOfDomainMemberAndMemberOf
            $NumberOfMemberAndMemberOf += $NumberOfDomainMemberAndMemberOf
            
            # Site Scope - Both Role and Rights groups may have .member and .memberof
            ForEach ($SiteRoleGroup in $SiteRoleGroups) {
                $NumberOfSiteMemberAndMemberOf += $SiteRoleGroup.Member.Count
                $NumberOfSiteMemberAndMemberOf += $SiteRoleGroup.MemberOf.Count
            }

            ForEach ($SiteRightsGroup in $SiteRightsGroups) {
                $NumberOfSiteMemberAndMemberOf += $SiteRightsroup.Member.Count
                $NumberOfSiteMemberAndMemberOf += $SiteRightsGroup.MemberOf.Count
            }
            $NumberOfElementsToProcess += $NumberOfSiteMemberAndMemberOf
            $NumberOfMemberAndMemberOf += $NumberOfSiteMemberAndMemberOf

            # Computer Scope - Both Role and Rights groups may have .member and .memberof
            ForEach ($ComputerRoleGroup in $ComputerRoleGroups) {
                $NumberOfComputerMemberAndMemberOf += $ComputerRoleGroup.Member.Count
                $NumberOfComputerMemberAndMemberOf += $ComputerRoleGroup.MemberOf.Count

            }

            ForEach ($ComputerRightsGroup in $ComputerRightsGroups) {
                $NumberOfComputerMemberAndMemberOf += $ComputerRightsGroup.Member.Count
                $NumberOfComputerMemberAndMemberOf += $ComputerRightsGroup.MemberOf.Count

            }
            $NumberOfElementsToProcess += $NumberOfComputerMemberAndMemberOf
            $NumberOfMemberAndMemberOf += $NumberOfComputerMemberAndMemberOf
        }


        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # RIGHTS
        # Action: Count and Resolve Paths
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If (
            ($Components -icontains 'Rights') -or 
            ($Null -eq $Components)
        ) { 
            $ProcessRights = $True
            
            # Count and resolve domain Path
            $NumberOfRights         = 0 
            $NumberOfDomainRights   = 0
            $NumberOfSiteRights     = 0
            $NumberOfComputerRights = 0

            ForEach ($DomainRightsGroup in $DomainRightsGroups) {
                ForEach ($DomainRight in $DomainRightsGroup.Rights) {
                    $NumberOfElementsToProcess++
                    # Update the debug counter as well
                    $NumberOfRights++
                    $NumberOfDomainRights++
                    # Resolve Path
                    If ($Null -eq $DomainRight.Path) {

                        # Resolve paths from OU schema. It is possible to set a domain right
                        # at a site- or computer-scoped OU
                        Switch ($DomainRight.Scope) {
                            'domain' {
                                $Path = Get-DryADOUPathFromTag -Tag $DomainRight.Tag -OUs $DomainOUs -Scope 'domain'
                            }
                            'site' {
                                $Path = Get-DryADOUPathFromTag -Tag $DomainRight.Tag -OUs $SiteOUs -Scope 'site'
                            }
                            'computer' {
                                $Path = Get-DryADOUPathFromTag -Tag $DomainRight.Tag -OUs $ComputerOUs -Scope 'computer'
                            }
                        }
                        $DomainRight | Add-Member -MemberType NoteProperty -Name Path -Value $Path

                        # Once resolved, remove Tag. Keep scope - we need that to determine if the target scope, or
                        # IdentityReeference, is a site or domain path
                        $DomainRight.PSObject.Properties.Remove('Tag')
                    }
                }
            }

            # Count and resolve site Path
            ForEach ($SiteRightsGroup in $SiteRightsGroups) {
                ForEach ($SiteRight in $SiteRightsGroup.Rights) {
                    $NumberOfElementsToProcess++
                    $NumberOfRights++
                    $NumberOfSiteRights++
            
                    # Resolve Path
                    If ($Null -eq $SiteRight.Path) {
                        
                        # Resolve domain paths from OU schema. A right for a site group may
                        # reference an OU in the domain scope, site scope or computer scope
                        Switch ($SiteRight.Scope) {
                            'domain' {
                                $Path = Get-DryADOUPathFromTag -Tag $SiteRight.Tag -OUs $DomainOUs -Scope 'domain'
                            }
                            'site' {
                                $Path = Get-DryADOUPathFromTag -Tag $SiteRight.Tag -OUs $SiteOUs -Scope 'site'
                            }
                            'computer' {
                                $Path = Get-DryADOUPathFromTag -Tag $SiteRight.Tag -OUs $ComputerOUs -Scope 'computer'
                            }
                        }
                        
                        $SiteRight | Add-Member -MemberType NoteProperty -Name Path -Value $Path
                        
                        # Once resolved, remove Tag. Leave Scope, we may need that
                        $SiteRight.PSObject.Properties.Remove('Tag')
                    }
                }
            }

            # Count and resolve computer Path
            ForEach ($ComputerRightsGroup in $ComputerRightsGroups) {
                ForEach ($ComputerRight in $ComputerRightsGroup.Rights) {
                    $NumberOfElementsToProcess++
                    $NumberOfRights++
                    $NumberOfComputerRights++
            
                    # Resolve Path
                    If ($Null -eq $ComputerRight.Path) {
                        
                        # Resolve domain paths from OU schema. A right for a computer group may
                        # reference an OU in the domain scope, or in the site scope
                        Switch ($ComputerRight.Scope) {
                            'domain' {
                                $Path = Get-DryADOUPathFromTag -Tag $ComputerRight.Tag -OUs $DomainOUs -Scope 'domain'
                            }
                            'site' {
                                $Path = Get-DryADOUPathFromTag -Tag $ComputerRight.Tag -OUs $SiteOUs -Scope 'site'
                            }
                            'computer' {
                                $Path = Get-DryADOUPathFromTag -Tag $ComputerRight.Tag -OUs $ComputerOUs -Scope 'computer'
                            }
                        }
                        
                        $ComputerRight | Add-Member -MemberType NoteProperty -Name Path -Value $Path
                        
                        # Once resolved, remove Tag. Leave Scope, we may need that
                        $ComputerRight.PSObject.Properties.Remove('Tag')
                    }
                }
            }
        }


        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # GROUP POLICY IMPORTS
        # Action: Count and String replacement
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If (
            ($Components -icontains 'GPOImports') -or 
            ($Components -icontains 'GPOs') -or
            ($Null -eq $Components)
        ){  
            
            If ($RoleConfiguration.gpo_imports){   
                $ProcessGPOImports = $True   
                $DomainGPOImports = @($RoleConfiguration.gpo_imports | Where-Object {  
                    $_.scope -eq 'domain' 
                })
                
                $SiteGPOImports = @($RoleConfiguration.gpo_imports | Where-Object {  
                    $_.scope -eq 'site' 
                })

                $ComputerGPOImports = @($RoleConfiguration.gpo_imports | Where-Object {  
                    $_.scope -eq 'computer' 
                })

                # If the configuration set contains json-gpos,
                $JsonGPOImports = @($RoleConfiguration.gpo_imports | Where-Object {  
                    $_.type -eq 'json' 
                })
                $RequiresAccessToGPOManagement = $False
                If ($JsonGPOImports.count -gt 0) {
                    $RequiresAccessToGPOManagement = $True
                }
 
                $NumberOfElementsToProcess += ($DomainGPOImports.Count + $SiteGPOImports.Count + $ComputerGPOImports.Count)
                $NumberOfGPOImports += ($DomainGPOImports.Count + $SiteGPOImports.Count + $ComputerGPOImports.Count)

                $DomainGPOImports   = Resolve-DryADReplacementPatterns -inputobject $DomainGPOImports -Variables $Variables
                $SiteGPOImports     = Resolve-DryADReplacementPatterns -inputobject $SiteGPOImports -Variables $Variables
                $ComputerGPOImports = Resolve-DryADReplacementPatterns -inputobject $ComputerGPOImports -Variables $Variables
            }
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # GROUP POLICY LINKS
        # Action: Count, String replacement and Resolve paths from tags
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If (
            ($Components -icontains 'GPOLinks') -or 
            ($Components -icontains 'GPOs') -or
            ($Null -eq $Components)
        ){  
            If ($RoleConfiguration.gpo_links) {  

                $ProcessGPOLinks = $True
                
                $DomainGPOLinks = @($RoleConfiguration.gpo_links | Where-Object {  
                    $_.scope -eq 'domain' 
                } )
                
                $SiteGPOLinks = @($RoleConfiguration.gpo_links | Where-Object {  
                    $_.scope -eq 'site' 
                } )

                $ComputerGPOLinks = @($RoleConfiguration.gpo_links | Where-Object {  
                    $_.scope -eq 'computer' 
                } )
                
                $NumberOfElementsToProcess += ($DomainGPOLinks.Count + $SiteGPOLinks.Count + $ComputerGPOLinks.Count)
                $NumberOfGPOLinks = ($DomainGPOLinks.Count + $SiteGPOLinks.Count + $ComputerGPOLinks.Count)

                # String Replacements
                $DomainGPOLinks   = Resolve-DryADReplacementPatterns -inputobject $DomainGPOLinks -Variables $Variables
                $SiteGPOLinks     = Resolve-DryADReplacementPatterns -inputobject $SiteGPOLinks -Variables $Variables
                $ComputerGPOLinks = Resolve-DryADReplacementPatterns -inputobject $ComputerGPOLinks -Variables $Variables 
                
                # Resolve Domain Paths from OU schema
                ForEach ($DomainGPOLink in $DomainGPOLinks) {
                    If ($NULL -eq $DomainGPOLink.path) {
                        $Path = Get-DryADOUPathFromTag -Tag $DomainGPOLink.Tag -OUs $DomainOUs -Scope 'domain'
                        $DomainGPOLink | Add-Member -MemberType NoteProperty -Name Path -Value $Path
                    }
                }

                # Resolve Site Paths from OU schema
                ForEach ($SiteGPOLink in $SiteGPOLinks) {
                    If ($NULL -eq $SiteGPOLink.path) {
                        $Path = Get-DryADOUPathFromTag -Tag $SiteGPOLink.Tag -OUs $SiteOUs -Scope 'site'
                        $SiteGPOLink | Add-Member -MemberType NoteProperty -Name Path -Value $Path
                    }
                }

                # Resolve Computer Paths from OU schema
                ForEach ($ComputerGPOLink in $ComputerGPOLinks) {
                    If ($NULL -eq $ComputerGPOLink.path) {
                        $Path = Get-DryADOUPathFromTag -Tag $ComputerGPOLink.Tag -OUs $ComputerOUs -Scope 'computer'
                        $ComputerGPOLink | Add-Member -MemberType NoteProperty -Name Path -Value $Path
                    }
                }
            } 
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # USERS
        # Action: Count, String replacement, resolve OUs
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If (
            ($Components -icontains 'Users') -or 
            ($Null -eq $Components)
        ){  
            If ($RoleConfiguration.users) {
                
                # Get domain scoped users
                $DomainUsers = @($RoleConfiguration.users | Where-Object {  
                    $_.scope -eq 'domain' 
                } )
                
                # Get site scoped users
                $SiteUsers = @($RoleConfiguration.users | Where-Object {  
                    $_.scope -eq 'site' 
                } )

                # Get computer scoped users
                $ComputerUsers = @($RoleConfiguration.users | Where-Object {  
                    $_.scope -eq 'computer' 
                } )

                # Count
                If (
                    ($Components -icontains 'Users') -or 
                    ($Null -eq $Components)
                ) { 
                    $ProcessUsers = $True
                    $NumberOfElementsToProcess += ($DomainUsers.Count + $SiteUsers.Count + $ComputerUsers.Count)
                    $NumberOfUsers = ($DomainUsers.Count + $SiteUsers.Count + $ComputerUsers.Count) 
                }

                # Replace any replacement pattern
                If ($DomainUsers.count -gt 0) {
                    $DomainUsers = @(Resolve-DryADReplacementPatterns -inputobject $DomainUsers -Variables $Variables)
                }
                If ($SiteUsers.count -gt 0) {
                    $SiteUsers = @(Resolve-DryADReplacementPatterns -inputobject $SiteUsers -Variables $Variables)
                }
                If ($ComputerUsers.count -gt 0) {
                    $ComputerUsers = @(Resolve-DryADReplacementPatterns -inputobject $ComputerUsers -Variables $Variables)
                }

                # Resolve domain OU paths from schema
                ForEach ($User in $DomainUsers) {
                    If ($NULL -eq $User.path) {
                        # Resolve domain paths from OU schema
                        $Path = Get-DryADOUPathFromTag -Tag $User.Tag -OUs $DomainOUs -Scope 'domain'
                        $User | Add-Member -MemberType NoteProperty -Name Path -Value $Path
                    }
                    # Convert to $GroupCase
                    $User.name = ConvertTo-DryADCase -Name $User.name -Case $UserCase
                }

                # Resolve site OU paths from schema
                ForEach ($User in $SiteUsers) {
                    If ($NULL -eq $User.path) {
                        # Resolve site paths from OU schema
                        $Path = Get-DryADOUPathFromTag -Tag $User.Tag -OUs $SiteOUs -Scope 'site'
                        $User | Add-Member -MemberType NoteProperty -Name Path -Value $Path
                    }
                    # Convert to $GroupCase
                    $User.name = ConvertTo-DryADCase -Name $User.name -Case $UserCase
                }

                # Resolve computer OU paths from schema
                ForEach ($User in $ComputerUsers) {
                    If ($NULL -eq $User.path) {
                        # Resolve site paths from OU schema
                        $Path = Get-DryADOUPathFromTag -Tag $User.Tag -OUs $ComputerOUs -Scope 'computer'
                        $User | Add-Member -MemberType NoteProperty -Name Path -Value $Path
                    }
                    # Convert to $GroupCase
                    $User.name = ConvertTo-DryADCase -Name $User.name -Case $UserCase
                }
            }
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # USER'S GROUP MEMBERSHIPS
        # Action: Count
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If (
            ($Components -icontains 'UserGroupMembers') -or 
            ($Null -eq $Components)
        ){  
            
            $ProcessUserMemberOf = $True
            # Count User's MemberOfs
            $NumberOfDomainUserMemberOf   = 0
            $NumberOfSiteUserMemberOf     = 0
            $NumberOfComputerUserMemberOf = 0
                 
            # Domain Scope
            ForEach ($DomainUser in $DomainUsers) {
                $NumberOfDomainUserMemberOf += $DomainUser.MemberOf.Count
            }
            $NumberOfElementsToProcess += $NumberOfDomainUserMemberOf
            
            # Site Scope
            ForEach ($SiteUser in $SiteUsers) {
                $NumberOfSiteUserMemberOf += $SiteUser.MemberOf.Count
            }
            $NumberOfElementsToProcess += $NumberOfSiteUserMemberOf

            # Computer Scope
            ForEach ($ComputerUser in $ComputerUsers) {
                $NumberOfComputerUserMemberOf += $ComputerUser.MemberOf.Count

            }
            $NumberOfElementsToProcess += $NumberOfComputerUserMemberOf
            
            $NumberOfUserMemberOf = $NumberOfDomainUserMemberOf + $NumberOfSiteUserMemberOf + $NumberOfComputerUserMemberOf
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # AD DRIVE
        # Action: Make sure the AD Drive on the executing system is created when
        # importing the ActiveDirectory module, and that it points to the correct
        # Domain Controller.
        # - If Remote execution, we remote into a Domain Controller, and the AD
        # drive should be pointed to localhost.
        # - If Local execution, the AD Drive should point to $DomainController
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        
        Switch ($ExecutionType) {
            'Remote' {
                ol i "Configuring AD Drive on $DomainController" -sh
                Set-DryADDrive -PSSession $PSSession
            }
            'Local' {
                ol i "Configuring AD Drive on local system" -sh
                Set-DryADDrive -DomainController $DomainController
            }
        }
        
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # AD WEB SERVICES ON DOMAIN CONTROLLER
        # Action: Test, and wait for the service to become available
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If ($ExecutionType -eq 'Remote') {
            ol i "Testing and Waiting for AD Availability" -sh
            Wait-DryADForADWebServices -DomainDN $DomainDN -PSSession $PSSession
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # SCHEMA UPDATES
        # Action: Invoke/Update. Will only work when 'Remote' and $DomainController
        # is the Schema Master
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If ($ProcessADSchema)  {

            ol i "AD Schema Extensions ($($ADSchemaExtensions.count))" -sh
            ForEach ($ADSchemaExtension in $ADSchemaExtensions) {

                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity             = 'Configuring Active Directory'
                    Status               = "Item ($ElementsCounter / $NumberOfElementsToProcess): Schema extension: $($ADSchemaExtension.BaseName)"
                    PercentComplete      = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters
                
                $ADSchemaExtJson = Get-Content -Path (Join-Path -Path (Split-Path -Path $ADSchemaExtension.FullName) -ChildPath "$($ADSchemaExtension.BaseName).json") |
                ConvertFrom-Json -ErrorAction 'Stop'

                $ADSchemaExtContent = Get-Content -Path $ADSchemaExtension.FullName -Raw -ErrorAction 'Stop'

                $ExtendDryADSchemaParams = @{
                    Type         = $ADSchemaExtension.BaseName
                    SuccessCount = $ADSchemaExtJson.success_string_match_count
                    Content      = $ADSchemaExtContent
                    Variables    = $Variables
                    Server       = $DomainController 
                }
                If ($ExecutionType -eq 'Remote') {
                    $ExtendDryADSchemaParams += @{
                        PSSession = $PSSession
                    }
                }
                
                ol i 'Extending AD Schema, type',"$($ADSchemaExtension.BaseName)"
                Set-DryADSchemaExtension @ExtendDryADSchemaParams
            }

            $DebugCounter = $NumberOfADSchemaExtensions
            If ($ElementsCounter -ne $DebugCounter) {
                Throw "Elementscounter is $ElementsCounter, but was supposed to be $DebugCounter"
            }
        } # If ($ProcessSchema)



        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # NETLOGON
        # Action: Configure
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If ($ProcessNETLOGON) {  
            ol i "NETLOGON File Copy" -sh
                # increment the element counter and update progress
            $ElementsCounter++
            $WriteProgressParameters = @{
                Activity             = 'Configuring Active Directory'
                Status               = "Item ($ElementsCounter / $NumberOfElementsToProcess): NETLOGON"
                PercentComplete      = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
            }
            Write-Progress @WriteProgressParameters
            $NETLOGONSourcePath = Join-Path -Path $ConfigurationPath -ChildPath 'NETLOGON'
            If ($ExecutionType -eq 'Remote') {
                
            }
            Switch ($ExecutionType) {
                'Local' {
                    $NETLOGONTargetPath = "$DomainFQDN\NETLOGON\"
                }
                'Remote' {
                    $NETLOGONTargetPath = "C:\Windows\SYSVOL\domain\scripts\"
                }
            }
            ol i "NETLOGON source path","$NETLOGONSourcePath\*"
            ol i "NETLOGON target path","$NETLOGONTargetPath"

            $CopyNETLOGONParams = @{
                Path          = "$NETLOGONSourcePath\*"
                Destination   = "$NETLOGONTargetPath" 
                Recurse       = $True 
                Force         = $True
                ErrorAction   = 'Stop'
            }
            If ($ExecutionType -eq 'Remote') {
                $CopyNETLOGONParams += @{
                    ToSession = $PSSession        
                }
            }
            
            Copy-Item @CopyNETLOGONParams
        
            $DebugCounter += $NumberOfNETLOGONs
            If ($ElementsCounter -ne $DebugCounter) {
                Throw "Elementscounter is $ElementsCounter, but was supposed to be $DebugCounter"
            }
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # AdmTemplates
        # Action: Configure
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If ($ProcessAdmTemplates) {  
            ol i "AdmTemplates File Copy" -sh
                # increment the element counter and update progress
            $ElementsCounter++
            $WriteProgressParameters = @{
                Activity             = 'Configuring Active Directory'
                Status               = "Item ($ElementsCounter / $NumberOfElementsToProcess): Administrative Templates"
                PercentComplete      = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
            }
            Write-Progress @WriteProgressParameters
            
            $AdmTemplatesSourcePath = "$ConfigurationPath\AdmTemplates\*"
            Switch ($ExecutionType) { 
                'Local' {
                    $AdmTemplatesTargetPath = "\\\\$DomainFQDN\\SYSVOL\\$DomainFQDN\\Policies\\PolicyDefinitions\\"
                }
                'Remote' {
                    $AdmTemplatesTargetPath = "C:\\Windows\\SYSVOL\\domain\\Policies\\PolicyDefinitions\\"
                }
            }

            
            ol i "AdmTemplates source path",$AdmTemplatesSourcePath
            ol i "AdmTemplates target path",$AdmTemplatesTargetPath

            $CopyDryFilesToRemoteTargetParams = @{
                SourcePath = "$AdmTemplatesSourcePath" 
                TargetPath = "$AdmTemplatesTargetPath" 
            }
            
            If ($PSSession) {
                $CopyDryFilesToRemoteTargetParams += @{
                    PSSession = $PSSession
                }   
            }
            Copy-DryADFilesToRemoteTarget @CopyDryFilesToRemoteTargetParams | 
            Out-Null
        
            $DebugCounter += 1
            If ($ElementsCounter -ne $DebugCounter) {
                Throw "Elementscounter is $ElementsCounter, but was supposed to be $DebugCounter"
            }
        }


        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # ORGANIZATIONAL UNITS
        # Action: Create
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If ($ProcessOUs) {

            ol i "OUs - Domain scope ($($DomainOUs.count))" -sh
            ForEach ($OU in $DomainOUs) {

                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity             = 'Configuring Active Directory'
                    Status               = "Item ($ElementsCounter / $NumberOfElementsToProcess): Creating OU: $($OU.Path)"
                    PercentComplete      = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters
    
                # Create instance of class OU and invoke method CreateOU()
                ol i "Creating OU (domain '$DomainFQDN')","$($OU.path)"
                
                Switch ($ExecutionType) {
                    'Local' {
                        [OU]$OUObject = [OU]::new("$($OU.path)","$DomainFQDN",$DomainController)
                    }
                    'Remote' {
                        [OU]$OUObject = [OU]::new("$($OU.path)","$DomainFQDN",$PSSession)
                    }
                }
                $OUObject.CreateOU()
                
                Remove-Variable -Name OUObject -ErrorAction Ignore            
            }
            
            # loop through site OUs
            ol i "OUs - Site scope ($($SiteOUs.count))" -sh
            ForEach ($OU in $SiteOUs){ 
                
                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity             = 'Configuring Active Directory'
                    Status               = "Item ($ElementsCounter / $NumberOfElementsToProcess): Creating OU: $($OU.Path)"
                    PercentComplete      = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters
                
                ol i "Creating OU (site '$adsite')","$($OU.Path)"
                Switch ($ExecutionType) {
                    'Local' {
                        [OU]$OUObject = [OU]::new("$($OU.path)","$DomainFQDN",$DomainController)
                    }
                    'Remote' {
                        [OU]$OUObject = [OU]::new("$($OU.path)","$DomainFQDN",$PSSession)
                    }
                }
                $OUObject.CreateOU()
                
                # clean up
                Remove-Variable -name CopyOU -ErrorAction Ignore
            }

            # loop through site OUs
            ol i "OUs - Computer scope ($($ComputerOUs.count))" -sh
            ForEach ($OU in $ComputerOUs){ 
                
                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity             = 'Configuring Active Directory'
                    Status               = "Item ($ElementsCounter / $NumberOfElementsToProcess): Creating OU: $($OU.Path)"
                    PercentComplete      = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters
                
                ol i "Creating OU (computer '$ComputerName')","$($OU.Path)"
                Switch ($ExecutionType) {
                    'Local' {
                        [OU]$OUObject = [OU]::new("$($OU.path)","$DomainFQDN",$DomainController)
                    }
                    'Remote' {
                        [OU]$OUObject = [OU]::new("$($OU.path)","$DomainFQDN",$PSSession)
                    }
                }
                $OUObject.CreateOU()
                
                # clean up
                Remove-Variable -name CopyOU -ErrorAction Ignore
            }

            $DebugCounter += $NumberOfOUs
            If ($ElementsCounter -ne $DebugCounter) {
                Throw "Elementscounter is $ElementsCounter, but was supposed to be $DebugCounter"
            }

        } # If ($ProcessOU)


        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # WMI FILTERS
        # Action: Create
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        If ($ProcessWMIFilterImports) {
            # WMIfilters. Log some info, then loop through each
            ol i "WMIFilters - Domain scope ($($DomainWMIfilters.count))" -sh

            # Make sure 'Allow System Only Change' in registry on domain controller is 1
            # If it isn't, WMIFilter creation will fail with access denied
            ol d "Calling 'Set-DryADRemoteRegistry' to set 'Allow System Only Change' to 1"
            
            $AllowSystemOnlyChangeParameters = @{
                BaseKey                      = 'HKEY_LOCAL_MACHINE' 
                LeafKey                      = 'System\\CurrentControlSet\\Services\\NTDS\\Parameters' 
                ValueName                    = 'Allow System Only Change' 
                ValueData                    = 1 
                ValueType                    = 'DWORD'
                PSSession                    = $PSSession
                ErrorAction                  = 'Stop'
            }
            
            Set-DryADRemoteRegistry @AllowSystemOnlyChangeParameters
            
            # $DomainWMIfilters
            ForEach ($GPOWMIFilter in $DomainWMIfilters) {
                
                # Progress
                $ElementsCounter++  
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Importing WMIFilter '$($GPOWMIFilter.Name)'"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )
                }
                Write-Progress @WriteProgressParameters
            
                # Any GPMC command must run in a remote session, since the cmdlets lack the -credentials parameter
                $NewDryWmiFilterParameters = @{
                    Name                 = $GPOWMIFilter.Name 
                    Description          = $GPOWMIFilter.Description 
                    Query                = [Array]$GPOWMIFilter.Queries 
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $NewDryWmiFilterParameters += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $NewDryWmiFilterParameters += @{
                            PSSession = $PSSession
                        }
                    }
                }
                ol i "Importing WMI Filter (domain '$DomainFQDN')","$($GPOWMIFilter.Name)"
                New-DryADWmiFilter @NewDryWmiFilterParameters
            }

            # $SiteWMIfilters
            ol i "WMIFilters - Site scope ($($SiteWMIfilters.count))" -sh
            
            ForEach ($GPOWMIFilter in $SiteWMIfilters) {
                # Progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity             = 'Configuring Active Directory'
                    Status               = "Item ($ElementsCounter / $NumberOfElementsToProcess): Importing WMIFilter '$($GPOWMIFilter.Name)'"
                    PercentComplete      = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )
                }
                Write-Progress @WriteProgressParameters
            
                # Any GPMC command must run in a remote session, since the cmdlets lack the -credentials parameter
                $NewDryWmiFilterParameters = @{
                    Name                 = $GPOWMIFilter.Name 
                    Description          = $GPOWMIFilter.Description 
                    Query                = [Array]$GPOWMIFilter.Queries 
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $NewDryWmiFilterParameters += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $NewDryWmiFilterParameters += @{
                            PSSession = $PSSession
                        }
                    }
                }
                ol i "Importing WMI Filter (site '$adsite')","$($GPOWMIFilter.Name)"
                New-DryADWmiFilter @NewDryWmiFilterParameters
            }

            # $ComputerWMIfilters
            ol i "WMIFilters - Computer scope ($($ComputerWMIfilters.count))" -sh
            
            ForEach ($GPOWMIFilter in $ComputerWMIfilters) {
                # Progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Importing WMIFilter '$($GPOWMIFilter.Name)'"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )
                }
                Write-Progress @WriteProgressParameters
            
                $NewDryWmiFilterParameters = @{
                    Name        = $GPOWMIFilter.Name 
                    Description = $GPOWMIFilter.Description 
                    Query       = [Array]$GPOWMIFilter.Queries
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $NewDryWmiFilterParameters += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $NewDryWmiFilterParameters += @{
                            PSSession = $PSSession
                        }
                    }
                }
                ol i "Importing WMI Filter (computer '$ComputerName')","$($GPOWMIFilter.Name)"
                New-DryADWmiFilter @NewDryWmiFilterParameters
            }

            $DebugCounter += $NumberOfWMIFilters
            If ($ElementsCounter -ne $DebugCounter) {
                Throw "Elementscounter is $ElementsCounter, but was supposed to be $DebugCounter"
            }
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # RIGHTS GROUPS
        # Action: Create
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If ($ProcessRightsGroups) {
            ol i "Rights Groups - Domain scope ($($DomainRightsGroups.count))" -sh
 
            # loop through domain Rights
            ForEach ($RightsGroup in $DomainRightsGroups) {

                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Rights Group: $($RightsGroup.groupname)"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   

                }
                Write-Progress @WriteProgressParameters

                $NewDryADSecurityGroupParams = @{
                    Name        = $RightsGroup.groupname
                    Path        = $RightsGroup.path
                    Description = $RightsGroup.groupdescription
                    Type        = $RightsGroup.grouptype
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $NewDryADSecurityGroupParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $NewDryADSecurityGroupParams += @{
                            PSSession = $PSSession
                        }
                    }
                }
                ol i "Creating Rights Group (domain '$DomainFQDN')","$($RightsGroup.groupname)" 
                New-DryADSecurityGroup @NewDryADSecurityGroupParams
            }

            # loop through site Rights
            ol i "Rights Groups - Site scope ($($SiteRightsGroups.count))" -sh
        
            ForEach ($RightsGroup in $SiteRightsGroups) { 
                
                # Add site to the description
                $RightsGroup.groupdescription = $RightsGroup.groupdescription + " (site '$adsite')"

                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Rights Group: $($RightsGroup.groupname)"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters

                $NewDryADSecurityGroupParams=@{
                    Name        = $RightsGroup.groupname
                    Path        = $RightsGroup.path
                    Description = $RightsGroup.groupdescription
                    Type        = $RightsGroup.grouptype
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $NewDryADSecurityGroupParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $NewDryADSecurityGroupParams += @{
                            PSSession = $PSSession
                        }
                    }
                }
                ol i "Creating Rights Group (site '$adsite')","$($RightsGroup.groupname)"
                New-DryADSecurityGroup @NewDryADSecurityGroupParams   
            }

            # loop through site Rights
            ol i "Rights Groups - Computer scope ($($ComputerRightsGroups.count))" -sh
        
            ForEach ($RightsGroup in $ComputerRightsGroups) { 
                
                # Add site to the description
                $RightsGroup.groupdescription = $RightsGroup.groupdescription + " (computer '$ComputerName')"

                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Rights Group: $($RightsGroup.groupname)"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters

                $NewDryADSecurityGroupParams=@{
                    Name        = $RightsGroup.groupname
                    Path        = $RightsGroup.path
                    Description = $RightsGroup.groupdescription
                    Type        = $RightsGroup.grouptype
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $NewDryADSecurityGroupParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $NewDryADSecurityGroupParams += @{
                            PSSession = $PSSession
                        }
                    }
                }
                ol i "Creating Rights Group (computer '$ComputerName')","$($RightsGroup.groupname)"
                New-DryADSecurityGroup @NewDryADSecurityGroupParams   
            }

            $DebugCounter += $NumberOfRightsGroups
            If ($ElementsCounter -ne $DebugCounter) {
                Throw "Elementscounter is $ElementsCounter, but was supposed to be $DebugCounter"
            }
        } # If ($ProcessRightsGroups)


        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # ROLE GROUPS
        # Action: Create
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If ($ProcessRoleGroups) {
            ol i "Role Groups - Domain scope ($($DomainRoleGroups.count))" -sh
 
            # loop through domain Role Groups
            ForEach ($RoleGroup in $DomainRoleGroups) {
                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Role Group: $($RoleGroup.groupname)"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   

                }
                Write-Progress @WriteProgressParameters

                $NewDryADSecurityGroupParams = @{
                    Name        = $RoleGroup.groupname
                    Path        = $RoleGroup.path
                    Description = $RoleGroup.groupdescription
                    Type        = $RoleGroup.grouptype
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $NewDryADSecurityGroupParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $NewDryADSecurityGroupParams += @{
                            PSSession = $PSSession
                        }
                    }
                }
                ol i "Creating Role Group (domain '$DomainFQDN')","$($RoleGroup.groupname)" 
                New-DryADSecurityGroup @NewDryADSecurityGroupParams
            }

            # loop through site Role Groups
            ol i "Role Groups - Site scope ($($SiteRoleGroups.count))" -sh

            ForEach ($RoleGroup in $SiteRoleGroups) { 
                
                # Add site to the description
                $RoleGroup.groupdescription = $RoleGroup.groupdescription + " (site '$adsite')"

                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Role Group: $($RoleGroup.groupname)"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters

                $NewDryADSecurityGroupParams = @{
                    Name        = $RoleGroup.groupname
                    Path        = $RoleGroup.path
                    Description = $RoleGroup.groupdescription
                    Type        = $RoleGroup.grouptype
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $NewDryADSecurityGroupParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $NewDryADSecurityGroupParams += @{
                            PSSession = $PSSession
                        }
                    }
                }
                ol i "Creating Role Group (site '$adsite')","$($RoleGroup.groupname)"
                New-DryADSecurityGroup @NewDryADSecurityGroupParams   
            }

            # loop through site Role Groups
            ol i "Role Groups - Computer scope ($($ComputerRoleGroups.count))" -sh

            ForEach ($RoleGroup in $ComputerRoleGroups) { 
                
                # Add computername to the description
                $RoleGroup.groupdescription = $RoleGroup.groupdescription + " (computer '$ComputerName')"

                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Role Group: $($RoleGroup.groupname)"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters

                $NewDryADSecurityGroupParams = @{
                    Name        = $RoleGroup.groupname
                    Path        = $RoleGroup.path
                    Description = $RoleGroup.groupdescription
                    Type        = $RoleGroup.grouptype
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $NewDryADSecurityGroupParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $NewDryADSecurityGroupParams += @{
                            PSSession = $PSSession
                        }
                    }
                }
                ol i "Creating Role Group (computer '$ComputerName')","$($RoleGroup.groupname)"
                New-DryADSecurityGroup @NewDryADSecurityGroupParams   
            }

            $DebugCounter += $NumberOfRoleGroups
            If ($ElementsCounter -ne $DebugCounter) {
                Throw "Elementscounter is $ElementsCounter, but was supposed to be $DebugCounter"
            }
                 
        } # If ($ProcessRoleGroups)


        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # GROUP'S GROUP MEMBERS
        # Action: Add Role Groups to RightsGroups
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If ($ProcessGroupMembers) {
            ol i "Group Members - Domain scope ($NumberOfDomainMemberAndMemberOf)" -sh
            
            # Both Role and Rights groups may have .member and .memberof
            ForEach ($DomainRoleGroup in $DomainRoleGroups) {
                ForEach ($DomainRoleGroupMember in $DomainRoleGroup.Member) {
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Group Member: $($DomainRoleGroup.groupname)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters

                    # Add Members to $DomainRoleGroup
                    $AddDryADGroupMemberParams = @{
                        Group     = $DomainRoleGroup.groupname
                        Member    = $DomainRoleGroupMember
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (domain '$DomainFQDN')","Adding '$DomainRoleGroupMember' to '$($DomainRoleGroup.groupname)'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
                ForEach ($DomainRoleGroupMemberOf in $DomainRoleGroup.MemberOf) {
                    
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Group Member Of: $($DomainRoleGroup.groupname)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters

                    # Add $DomainRoleGroup to each MemberOf
                    $AddDryADGroupMemberParams = @{
                        Group     = $DomainRoleGroupMemberOf
                        Member    = $DomainRoleGroup.groupname
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (domain '$DomainFQDN')","Adding '$($DomainRoleGroup.groupname)' to '$DomainRoleGroupMemberOf'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
            }


            ForEach ($DomainRightsGroup in $DomainRightsGroups) {
                ForEach ($DomainRightsGroupMember in $DomainRightsGroup.Member) {
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Group Member: $($DomainRightsGroup.groupname)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters

                    # Add Members to $DomainRightsGroup
                    $AddDryADGroupMemberParams = @{
                        Group     = $DomainRightsGroup.groupname
                        Member    = $DomainRightsGroupMember
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (domain '$DomainFQDN')","Adding '$DomainRightsGroupMember' to '$($DomainRightsGroup.groupname)'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
                ForEach ($DomainRightsGroupMemberOf in $DomainRightsGroup.MemberOf) {
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Group Member Of: $($DomainRightsGroup.groupname)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters

                    # Add $DomainRightsGroup to each MemberOf
                    $AddDryADGroupMemberParams = @{
                        Group     = $DomainRightsGroupMemberOf
                        Member    = $DomainRightsGroup.groupname
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (domain '$DomainFQDN')","Adding '$($DomainRightsGroup.groupname)' to '$DomainRightsGroupMemberOf'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
            }

            ol i "Group Members - Site scope ($NumberOfSiteMemberAndMemberOf)" -sh
            
            ForEach ($SiteRoleGroup in $SiteRoleGroups) {
                ForEach ($SiteRoleGroupMember in $SiteRoleGroup.Member) {
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Group Member: $($SiteRoleGroup.groupname)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters

                    # Add Members to $SiteRoleGroup
                    $AddDryADGroupMemberParams = @{
                        Group     = $SiteRoleGroup.groupname
                        Member    = $SiteRoleGroupMember
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (site '$adsite')","Adding '$SiteRoleGroupMember' to '$($SiteRoleGroup.groupname)'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
                ForEach ($SiteRoleGroupMemberOf in $SiteRoleGroup.MemberOf) {
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Group Member Of: $($SiteRoleGroup.groupname)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters

                    # Add $SiteRoleGroup to each MemberOf
                    $AddDryADGroupMemberParams = @{
                        Group     = $SiteRoleGroupMemberOf
                        Member    = $SiteRoleGroup.groupname
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (site '$adsite')","Adding '$($SiteRoleGroup.groupname)' to '$SiteRoleGroupMemberOf'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
            }

            ForEach ($SiteRightsGroup in $SiteRightsGroups) { 
                ForEach ($SiteRightsGroupMember in $SiteRightsGroup.Member) {
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Group Member: $($SiteRightsGroup.groupname)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters

                    # Add Members to $SiteRightsGroup
                    $AddDryADGroupMemberParams = @{
                        Group     = $SiteRightsGroup.groupname
                        Member    = $SiteRightsGroupMember
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (site '$adsite')","Add '$SiteRightsGroupMember' to '$($SiteRightsGroup.groupname)'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
                ForEach ($SiteRightsGroupMemberOf in $SiteRightsGroup.MemberOf) {
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Group Member Of: $($SiteRightsGroup.groupname)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters

                    # Add $SiteRightsGroup to each MemberOf
                    $AddDryADGroupMemberParams = @{
                        Group     = $SiteRightsGroupMemberOf
                        Member    = $SiteRightsGroup.groupname
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (site '$adsite')","Add '$($SiteRightsGroup.groupname)' to '$SiteRightsGroupMemberOf'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
            }

            ol i "Group Members - Computer scope ($NumberOfComputerMemberAndMemberOf)" -sh
            ForEach ($ComputerRoleGroup in $ComputerRoleGroups) {
                ForEach ($ComputerRoleGroupMember in $ComputerRoleGroup.Member) {
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Group Member: $($ComputerRoleGroup.groupname)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters

                    # Add Members to $ComputerRoleGroup
                    $AddDryADGroupMemberParams = @{
                        Group     = $ComputerRoleGroup.groupname
                        Member    = $ComputerRoleGroupMember
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (Computer '$ComputerName')","Add '$ComputerRoleGroupMember' to '$($ComputerRoleGroup.groupname)'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
                ForEach ($ComputerRoleGroupMemberOf in $ComputerRoleGroup.MemberOf) {
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Group Member Of: $($ComputerRoleGroup.groupname)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters

                    # Add $ComputerRoleGroup to each MemberOf
                    $AddDryADGroupMemberParams = @{
                        Group     = $ComputerRoleGroupMemberOf
                        Member    = $ComputerRoleGroup.groupname
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (Computer '$ComputerName')","Add '$($ComputerRoleGroup.groupname)' to '$ComputerRoleGroupMemberOf'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
            }

            ForEach ($ComputerRightsGroup in $ComputerRightsGroups) {  
                ForEach ($ComputerRightsGroupMember in $ComputerRightsGroup.Member) {
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Group Member: $($ComputerRightsGroup.groupname)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters

                    # Add Members to $ComputerRightsGroup
                    $AddDryADGroupMemberParams = @{
                        Group     = $ComputerRightsGroup.groupname
                        Member    = $ComputerRightsGroupMember
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (Computer '$ComputerName'): Add '$ComputerRightsGroupMember' to '$($ComputerRightsGroup.groupname)'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
                ForEach ($ComputerRightsGroupMemberOf in $ComputerRightsGroup.MemberOf) {
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Group Member Of: $($ComputerRightsGroup.groupname)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters

                    # Add $ComputerRightsGroup to each MemberOf
                    $AddDryADGroupMemberParams = @{
                        Group     = $ComputerRightsGroupMemberOf
                        Member    = $ComputerRightsGroup.groupname
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (Computer '$CompterName')","Add '$($ComputerRightsGroup.groupname)' to '$ComputerRightsGroupMemberOf'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
            }

            $DebugCounter += $NumberOfMemberAndMemberOf
            If ($ElementsCounter -ne $DebugCounter) {
                Throw "Elementscounter is $ElementsCounter, but was supposed to be $DebugCounter"
            }
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # USERS
        # Action: Getting the connection point's public certificate.
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If ($ProcessUsers) {
            If (
                ($NumberOfUsers.Count -gt 0) -and 
                ($ExecutionType -eq 'Remote')
            ) {
                ol i "Users - Getting the Connection Point's public certificate" -sh
                Try {
                    Get-DryADRemotePublicCertificate -PSSession $PSSession -CertificateFile $ConfigurationPublicCertificatePath
                }
                Catch {
                    $PSCmdLet.ThrowTerminatingError($_)
                }
            }
            

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # USERS
        # Action: Create
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
            ol i "Users - Domain scope ($($DomainUsers.count))" -sh
 
            # loop through domain Users
            ForEach ($User in $DomainUsers) {

                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): User: $($User.name)"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   

                }
                Write-Progress @WriteProgressParameters

                $NewDryADUserParams = @{
                    User = $User
                    DomainNB = $DomainNB
                    DryDeploy = $DryDeploy
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $NewDryADUserParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $NewDryADUserParams += @{
                            PSSession = $PSSession
                            DCPublicCertificateFilePath = $ConfigurationPublicCertificatePath
                        }
                    }
                }
                ol i "Creating User (domain '$DomainFQDN')","$($User.name)" 
                New-DryADUser @NewDryADUserParams
            }

            # loop through site Users
            ol i "Users - Site scope ($($SiteUsers.count))" -sh
        
            ForEach ($User in $SiteUsers) { 

                # Add site to the description
                $User.description = $User.description + " (site '$adsite')"

                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): User: $($User.name)"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters

                $NewDryADUserParams = @{
                    User = $User
                    DomainNB = $DomainNB
                    DryDeploy = $DryDeploy
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $NewDryADUserParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $NewDryADUserParams += @{
                            PSSession = $PSSession
                            DCPublicCertificateFilePath = $ConfigurationPublicCertificatePath
                        }
                    }
                }
                ol i "Creating User (site '$adsite')","$($User.name)"
                New-DryADUser @NewDryADUserParams
            }

            # loop through site Users
            ol i "Users - Computer scope ($($ComputerUsers.count))" -sh
        
            ForEach ($User in $ComputerUsers) { 
                
                # Add site to the description
                $User.description = $User.description + " (computer '$ComputerName')"

                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): User: $($User.name)"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters

                $NewDryADUserParams = @{
                    User = $User
                    DomainNB = $DomainNB
                    DryDeploy = $DryDeploy
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $NewDryADUserParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $NewDryADUserParams += @{
                            PSSession = $PSSession
                            DCPublicCertificateFilePath = $ConfigurationPublicCertificatePath
                        }
                    }
                }
                ol i "Creating User (computer '$ComputerName')","$($User.name)"
                New-DryADUser @NewDryADUserParams
            }

            $DebugCounter += $NumberOfUsers
            If ($ElementsCounter -ne $DebugCounter) {
                Throw "Elementscounter is $ElementsCounter, but was supposed to be $DebugCounter"
            }
        } # If ($ProcessUsers)


        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # USER'S GROUP MEMBERSHIPS
        # Action: Add Users to Groups
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If ($ProcessUserMemberOf) {
            ol i "User Group Memberships Domain scope ($NumberOfDomainUserMemberOf)" -sh
            
            # Both Role and Rights groups may have .member and .memberof
            ForEach ($DomainUser in $DomainUsers) {
                ForEach ($DomainUserMemberOf in $DomainUser.MemberOf) {
                    
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): $($DomainUser.name) member of: $DomainUserMemberOf"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters
        
                    # Add $DomainUser to each MemberOf
                    $AddDryADGroupMemberParams = @{
                        Group     = $DomainUserMemberOf
                        Member    = $DomainUser.name
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (domain '$DomainFQDN')","Adding '$($DomainUser.name)' to '$DomainUserMemberOf'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
            }
        
            ol i "User Group Memberships Site scope ($NumberOfSiteUserMemberOf)" -sh
            
            ForEach ($SiteUser in $SiteUsers) {
                ForEach ($SiteUserMemberOf in $SiteUser.MemberOf) {
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): $($SiteUser.name) member of: $SiteUserMemberOf"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters
        
                    # Add $SiteUser to each MemberOf
                    $AddDryADGroupMemberParams = @{
                        Group     = $SiteUserMemberOf
                        Member    = $SiteUser.name
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (site '$adsite')","Adding '$($SiteUser.name)' to '$SiteUserMemberOf'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
            }
        
            ol i "User Group Memberships Computer scope ($NumberOfComputerUserMemberOf)" -sh
            ForEach ($ComputerUser in $ComputerUsers) {
                ForEach ($ComputerUserMemberOf in $ComputerUser.MemberOf) {
                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): $($ComputerUser.name) member of: $ComputerUserMemberOf"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters
        
                    # Add $ComputerUser to each MemberOf
                    $AddDryADGroupMemberParams = @{
                        Group     = $ComputerUserMemberOf
                        Member    = $ComputerUser.name
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $AddDryADGroupMemberParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $AddDryADGroupMemberParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Group Members (computer '$ComputerName')","Add '$($ComputerUser.name)' to '$ComputerUserMemberOf'"
                    Add-DryADGroupMember @AddDryADGroupMemberParams
                    Remove-Variable -Name AddDryADGroupMemberParams -ErrorAction Ignore
                }
            }
        
            $DebugCounter += $NumberOfUserMemberOf
            If ($ElementsCounter -ne $DebugCounter) {
                Throw "Elementscounter is $ElementsCounter, but was supposed to be $DebugCounter"
            }
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # ACTIVE DIRECTORY RIGHTS
        # Action: Delegate Rights in AD
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        If ($ProcessRights) {
            ol i "Rights - Domain scope ($NumberOfDomainRights)" -sh

            # Domain Rights
            ForEach ($DomainRightsGroup in $DomainRightsGroups) {
                ForEach ($DomainRight in $DomainRightsGroup.Rights) {
                    ol v "Setting rights for group '$($DomainRightsGroup.groupname)'"
                    
                    # create hash from properties of the object
                    Remove-Variable -Name SetDryADAccessRuleParams -ErrorAction Ignore
                    $SetDryADAccessRuleParams = @{}
                    
                    # Add all properties but site and scope to the rights hash
                    $DomainRight.PSObject.Properties | ForEach-Object {
                        If ($_.Name -notin @('scope')) {
                            $SetDryADAccessRuleParams.Add($_.Name,$_.Value)
                        }
                    }

                    # Add the groupname as 'Group' - the owner of the right
                    $SetDryADAccessRuleParams.Add('Group',$DomainRightsGroup.groupname)

                    Switch ($ExecutionType) {
                        'Local' {
                            $SetDryADAccessRuleParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $SetDryADAccessRuleParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }

                    # Debug logging
                    ol d -hash $SetDryADAccessRuleParams

                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Domain Right: $($DomainRight.Path)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters
                    
                    # Set the right
                    ol i "Rights (domain '$DomainFQDN')","Group '$($DomainRightsGroup.groupname)'"
                    ol i "Rights (domain '$DomainFQDN')","Target '$($DomainRight.Path)'"
                    If ((Set-DryADAccessRule @SetDryADAccessRuleParams) -eq $True) {
                        ol i "Rights (domain '$DomainFQDN')",''
                    } 
                    Else {
                        ol e @("Rights (domain '$DomainFQDN')","Group '$($DomainRightsGroup.groupname)', Target '$($DomainRight.Path)'")
                        Throw "Failed: domain '$DomainFQDN'): Group '$($DomainRightsGroup.groupname)', Target '$($DomainRight.Path)'"
                    }

                    # Clean up
                    Remove-Variable -Name SetDryADAccessRuleParams,WriteProgressParameters -ErrorAction Ignore   
                }
            }

            # Site Rights
            ol i "Rights - Site scope ($NumberOfSiteRights)" -sh
            ForEach ($SiteRightsGroup in $SiteRightsGroups) {
                ForEach ($SiteRight in $SiteRightsGroup.Rights) {

                    # create hash from properties of the object
                    Remove-Variable -Name SetDryADAccessRuleParams -ErrorAction Ignore
                    $SetDryADAccessRuleParams = @{}
                    
                    # Add all properties but site and scope to the rights hash
                    $SiteRight.PSObject.Properties | ForEach-Object {
                        If ($_.Name -notin @('scope')) {
                            $SetDryADAccessRuleParams.Add($_.Name,$_.Value)
                        }
                    }

                    # Add the groupname as 'Group' - the owner of the right
                    $SetDryADAccessRuleParams.Add('Group',$SiteRightsGroup.groupname)

                    Switch ($ExecutionType) {
                        'Local' {
                            $SetDryADAccessRuleParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $SetDryADAccessRuleParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    
                    # Debug logging
                    ol d -hash $SetDryADAccessRuleParams 

                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Site Right: $($SiteRight.Path)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters
                    
                    # Set the right
                    ol i "Rights (site '$adsite')","Group '$($SiteRightsGroup.groupname)'"
                    ol i "Rights (site '$adsite')","Target '$($SiteRight.Path)'"
                    If ((Set-DryADAccessRule @SetDryADAccessRuleParams) -eq $True) {
                        ol i "Rights (site '$adsite')",''
                    }
                    Else {
                        ol e @("Rights (site '$adsite')","Group '$($SiteRightsGroup.groupname)', Target '$($SiteRight.Path)'")
                        Throw "Failed: Rights (site '$adsite'): Group '$($SiteRightsGroup.groupname)', Target '$($SiteRight.Path)'"
                    }

                    # Clean up
                    Remove-Variable -Name SetDryADAccessRuleParams,WriteProgressParameters -ErrorAction Ignore
                }
            }

            # Computer scoped Rights
            ol i "Rights - Computer scope ($NumberOfComputerRights)" -sh
            ForEach ($ComputerRightsGroup in $ComputerRightsGroups) {
                ForEach ($ComputerRight in $ComputerRightsGroup.Rights) {

                    # create hash from properties of the object
                    Remove-Variable -Name SetDryADAccessRuleParams -ErrorAction Ignore
                    $SetDryADAccessRuleParams = @{}
                    
                    # Add all properties but site and scope to the rights hash
                    $ComputerRight.PSObject.Properties | ForEach-Object {
                        If ($_.Name -notin @('scope')) {
                            $SetDryADAccessRuleParams.Add($_.Name,$_.Value)
                        }
                    }

                    # Add the groupname as 'Group' - the owner of the right
                    $SetDryADAccessRuleParams.Add('Group',$ComputerRightsGroup.groupname)

                    Switch ($ExecutionType) {
                        'Local' {
                            $SetDryADAccessRuleParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $SetDryADAccessRuleParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    
                    # Debug logging
                    ol d -hash $SetDryADAccessRuleParams 

                    # increment the element counter and update progress
                    $ElementsCounter++
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Computer Right: $($ComputerRight.Path)"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                    }
                    Write-Progress @WriteProgressParameters
                    
                    # Set the right
                    ol i "Rights (Computer '$ComputerName')","Group '$($ComputerRightsGroup.groupname)' =>"
                    ol i "Rights (Computer '$ComputerName')","Target '$($ComputerRight.Path)'"
                    If ((Set-DryADAccessRule @SetDryADAccessRuleParams) -eq $True) {
                        ol i "Rights (Computer '$ComputerName')",''
                    } 
                    Else {
                        ol e @("Rights (Computer '$ComputerName')","Group '$($ComputerRightsGroup.groupname)', Target '$($ComputerRight.Path)'")
                        Throw "Failed: Rights (Computer '$ComputerName')","Group '$($ComputerRightsGroup.groupname)', Target '$($ComputerRight.Path)'"
                    }

                    # Clean up
                    Remove-Variable -Name SetDryADAccessRuleParams,WriteProgressParameters -ErrorAction Ignore
                }
            }
            
            $DebugCounter += $NumberOfRights
            If ($ElementsCounter -ne $DebugCounter) {
                Throw "Elementscounter is $ElementsCounter, but was supposed to be $DebugCounter"
            }
        }

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # GROUP POLICIES
        # Action: Define paths, copy helper modules and GPOs to remote target if
        # ExecutionType 'Remote'
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        If ($ProcessGPOImports) {
            Switch ($ExecutionType) {
                'Remote' {
                    [String]$RemoteRootPath = "C:\DryDeploy\$ComputerName\"
                    [String]$GPOsPath = Join-Path -Path $RemoteRootPath -ChildPath 'GPOs'

                    # Only invoke if json-gpos in configuration
                    If ($RequiresAccessToGPOManagement) {
                        ol i "GPO Imports - Copying helper module to remote target" -sh
                        Copy-DryADModulesToRemoteTarget -PSSession $PSSession -RemoteRootPath $RemoteRootPath -Modules @('GPOManagement') | 
                        Out-Null
                    }

                    ol i "GPO Imports - Copying GPOs to remote target" -sh
                    Copy-DryADFilesToRemoteTarget -PSSession $PSSession -TargetPath $RemoteRootPath -SourcePath $SourceGPOsPath | 
                    Out-Null
                }
                'Local' {
                    [String]$GPOsPath = $SourceGPOsPath
                }
            }  

        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # GROUP POLICIES
        # Action: Import
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

            ol i "GPO Imports - Domain scope ($($DomainGPOImports.count))" -sh
            ForEach ($GPO in $DomainGPOImports) {
                # Ensure TargetName exists, and is converted to the desired case
                If ($Null -eq $GPO.TargetName) { 
                    $GPO | 
                    Add-Member -MemberType NoteProperty -Name 'TargetName' -Value $(ConvertTo-DryADCase -Name $GPO.Name -Case $GPOcase)
                }
                Else {
                    $GPO.TargetName = ConvertTo-DryADCase -Name $GPO.TargetName -Case $GPOcase
                }

                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Importing GPO '$($GPO.TargetName)'"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters
                
                $ImportDryADGPOParams = @{
                    GPO             = $GPO 
                    GPOsPath        = $GPOsPath 
                    Scope           = 'domain'
                    ReplacementHash = $ReplacementHash
                }

                Switch ($ExecutionType) {
                    'Local' {
                        $ImportDryADGPOParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $ImportDryADGPOParams += @{
                            PSSession = $PSSession
                        }
                    }
                }
                ol i "GPO Import (domain '$DomainFQDN')","Importing '$($GPO.Name)'"
                Import-DryADGPO @ImportDryADGPOParams      
            }

            ol i "GPO Imports - Site scope ($($SiteGPOImports.count))" -sh
            ForEach ($GPO in $SiteGPOImports) {
                # Ensure TargetName exists, and is converted to the desired case
                If ($Null -eq $GPO.TargetName) { 
                    $GPO | 
                    Add-Member -MemberType NoteProperty -Name 'TargetName' -Value $(ConvertTo-DryADCase -Name $GPO.Name -Case $GPOcase)
                }
                Else {
                    $GPO.TargetName = ConvertTo-DryADCase -Name $GPO.TargetName -Case $GPOcase
                }
                
                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Importing GPO '$($GPO.Name)'"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters

                $ImportDryADGPOParams = @{
                    GPO             = $GPO 
                    GPOsPath        = $GPOsPath 
                    Scope           = 'site'
                    ReplacementHash = $ReplacementHash
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $ImportDryADGPOParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $ImportDryADGPOParams += @{
                            PSSession = $PSSession
                        }
                    }
                }
                ol i "GPO Import (site '$ADSite')","Importing '$($GPO.Name)'"
                Import-DryADGPO @ImportDryADGPOParams
            }

            ol i "GPO Imports - Computer scope ($($ComputerGPOImports.count))" -sh
            ForEach ($GPO in $ComputerGPOImports) {
                # Ensure TargetName exists, and is converted to the desired case
                If ($Null -eq $GPO.TargetName) { 
                    $GPO | 
                    Add-Member -MemberType NoteProperty -Name 'TargetName' -Value $(ConvertTo-DryADCase -Name $GPO.Name -Case $GPOcase)
                }
                Else {
                    $GPO.TargetName = ConvertTo-DryADCase -Name $GPO.TargetName -Case $GPOcase
                }
                
                # Increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Importing GPO '$($GPO.Name)'"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters

                $ImportDryADGPOParams = @{
                    GPO             = $GPO 
                    GPOsPath        = $GPOsPath 
                    Scope           = 'computer'
                    ReplacementHash = $ReplacementHash
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $ImportDryADGPOParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $ImportDryADGPOParams += @{
                            PSSession = $PSSession
                        }
                    }
                }

                ol i "GPO Import (Computer '$ComputerName')","Importing '$($GPO.Name)'"
                Import-DryADGPO @ImportDryADGPOParams     
            }

            $DebugCounter += $NumberOfGPOImports
            If ($ElementsCounter -ne $DebugCounter) {
                Throw "Elementscounter is $ElementsCounter, but was supposed to be $DebugCounter"
            }

        } # If ($ProcessGPO)


        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # GROUP POLICIES
        # Action: Links
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        If ($ProcessGPOLinks) {
            ol i "GPO Links - Domain scope ($($DomainGPOLinks.count))" -sh

            ForEach ($DomainGPOLink in $DomainGPOLinks | Where-Object { $_.defined_in -eq 'OS'}) {
                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Linking GPOs to '$($DomainGPOLink.Path)'"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters
                 
                # Call Set-DryADGPLink. Let that function do the Information Logging
                $SetDryGPLinkParams = @{
                    GPOLinkObject = $DomainGPOLink
                    DomainDN      = $DomainDN
                    DomainFQDN    = $DomainFQDN
                }

                Switch ($ExecutionType) {
                    'Local' {
                        $SetDryGPLinkParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $SetDryGPLinkParams += @{
                            PSSession = $PSSession
                        }
                    }
                }

                If (($DomainGPOLink.Path).Trim() -eq '') {
                    ol i "Link GPOs (domain '$DomainFQDN') to","(Domain Root)"
                } 
                Else {
                    ol i "Link GPOs (domain '$DomainFQDN') to","$($DomainGPOLink.Path)"
                }
                Set-DryADGPLink @SetDryGPLinkParams 
            }

            ForEach ($DomainGPOLink in $DomainGPOLinks | Where-Object { $_.defined_in -ne 'OS'}) {
                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Linking GPOs to '$($DomainGPOLink.Path)'"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters
                 
                # Call Set-DryADGPLink. Let that function do the Information Logging
                $SetDryGPLinkParams = @{
                    GPOLinkObject = $DomainGPOLink
                    DomainDN      = $DomainDN
                    DomainFQDN    = $DomainFQDN
                }
                Switch ($ExecutionType) {
                    'Local' {
                        $SetDryGPLinkParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $SetDryGPLinkParams += @{
                            PSSession = $PSSession
                        }
                    }
                }

                If (($DomainGPOLink.Path).Trim() -eq '') {
                    ol i "Link GPOs (domain '$DomainFQDN') to","(Domain Root)"
                } 
                Else {
                    ol i "Link GPOs (domain '$DomainFQDN') to","$($DomainGPOLink.Path)"
                }
                Set-DryADGPLink @SetDryGPLinkParams 
            }

           
            ol i "GPO Links - Site scope ($($SiteGPOLinks.count))" -sh
            
            # loop through Site GPOLinks defined by the OS Config
            ForEach ($SiteGPOLink in $SiteGPOLinks | Where-Object { $_.defined_in -eq 'OS'}) {
                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Linking GPOs to '$($SiteGPOLink.Path)'"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters
                
                # Call Set-DryADGPLink. Let that function do the Information Logging
                $SetDryGPLinkParams = @{
                    GPOLinkObject = $SiteGPOLink
                    DomainDN      = $DomainDN
                    DomainFQDN    = $DomainFQDN
                }

                Switch ($ExecutionType) {
                    'Local' {
                        $SetDryGPLinkParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $SetDryGPLinkParams += @{
                            PSSession = $PSSession
                        }
                    }
                }

                If (($SiteGPOLink.Path).Trim() -eq '') {
                    ol i "Link GPOs (site '$adsite') to","(Root of Domain)"
                } 
                Else {
                    ol i "Link GPOs (site '$adsite') to","$($SiteGPOLink.Path)"
                }
                Set-DryADGPLink @SetDryGPLinkParams 
            }

            # loop through Site GPOLinks defined by the Role
            ForEach ($SiteGPOLink in $SiteGPOLinks | Where-Object { $_.defined_in -ne 'OS'}) {
                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Linking GPOs to '$($SiteGPOLink.Path)'"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters
                
                # Call Set-DryADGPLink. Let that function do the Information Logging
                $SetDryGPLinkParams = @{
                    GPOLinkObject = $SiteGPOLink
                    DomainDN      = $DomainDN
                    DomainFQDN    = $DomainFQDN
                }

                Switch ($ExecutionType) {
                    'Local' {
                        $SetDryGPLinkParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $SetDryGPLinkParams += @{
                            PSSession = $PSSession
                        }
                    }
                }

                If (($SiteGPOLink.Path).Trim() -eq '') {
                    ol i "Link GPOs (site '$adsite') to","(Root of Domain)"
                } 
                Else {
                    ol i "Link GPOs (site '$adsite') to","$($SiteGPOLink.Path)"
                }
                Set-DryADGPLink @SetDryGPLinkParams 
            }
            
            ol i "GPO Links - Computer scope ($($ComputerGPOLinks.count))" -sh
            
            # loop through Computer GPOLinks defined by the OS Config
            ForEach ($ComputerGPOLink in $ComputerGPOLinks | Where-Object { $_.defined_in -eq 'OS'}) {
                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity        = 'Configuring Active Directory'
                    Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Linking GPOs to '$($ComputerGPOLink.Path)'"
                    PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters
                
                # Call Set-DryADGPLink. Let that function do the Information Logging
                $SetDryGPLinkParams = @{
                    GPOLinkObject = $ComputerGPOLink
                    DomainDN      = $DomainDN
                    DomainFQDN    = $DomainFQDN
                }

                Switch ($ExecutionType) {
                    'Local' {
                        $SetDryGPLinkParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $SetDryGPLinkParams += @{
                            PSSession = $PSSession
                        }
                    }
                }

                If (($ComputerGPOLink.Path).Trim() -eq '') {
                    ol i "Link GPOs (Computer '$ComputerName') to","(Root of Domain)"
                } 
                Else {
                    ol i "Link GPOs (Computer '$ComputerName') to","$($ComputerGPOLink.Path)"
                }
                Set-DryADGPLink @SetDryGPLinkParams 
            }

            # loop through Computer GPOLinks defined by the Role
            ForEach ($ComputerGPOLink in $ComputerGPOLinks | Where-Object { $_.defined_in -ne 'OS'}) {
                # increment the element counter and update progress
                $ElementsCounter++
                $WriteProgressParameters = @{
                    Activity           = 'Configuring Active Directory'
                    Status             = "Item ($ElementsCounter / $NumberOfElementsToProcess): Linking GPOs to '$($ComputerGPOLink.Path)'"
                    PercentComplete    = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )   
                }
                Write-Progress @WriteProgressParameters
                
                # Call Set-DryADGPLink. Let that function do the Information Logging
                $SetDryGPLinkParams = @{
                    GPOLinkObject = $ComputerGPOLink
                    DomainDN      = $DomainDN
                    DomainFQDN    = $DomainFQDN
                }

                Switch ($ExecutionType) {
                    'Local' {
                        $SetDryGPLinkParams += @{
                            DomainController = $DomainController
                        }
                    }
                    'Remote' {
                        $SetDryGPLinkParams += @{
                            PSSession = $PSSession
                        }
                    }
                }
                
                If (($ComputerGPOLink.Path).Trim() -eq '') {
                    ol i "Link GPOs (Computer '$ComputerName') to","(Root of Domain)"
                } 
                Else {
                    ol i "Link GPOs (Computer '$ComputerName') to","$($ComputerGPOLink.Path)"
                }
                Set-DryADGPLink @SetDryGPLinkParams 
            }

            $DebugCounter += $NumberOfGPOLinks
            If ($ElementsCounter -ne $DebugCounter) {
                Throw "Elementscounter is $ElementsCounter, but was supposed to be $DebugCounter"
            }
        } # If ($ProcessGPOLinks)
        
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
        #
        # Wmi Filter Links
        # Action: Link to GPOs
        #
        # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

        If ($ProcessWMIFilterLinks) {

            ol i "WMIFilterLinks - Domain scope ($DomainWmiFilterLinksCount)" -sh
            ForEach ($GPOWMIFilter in $DomainWMIFilters) {
                ForEach ($GPOWMIFilterLink in $GPOWMIFilter.links) {
                    # Progress
                    $ElementsCounter++  
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Linking WMIFilter '$($GPOWMIFilter.Name)'"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )
                    }
                    Write-Progress @WriteProgressParameters
                
                    $SetDryWmiFilterLinkParams = @{
                        GPOName       = $GPOWMIFilterLink
                        WMIFilterName = $GPOWMIFilter.Name
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $SetDryWmiFilterLinkParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $SetDryWmiFilterLinkParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Linking WMI Filter (domain '$DomainFQDN')","$($GPOWMIFilter.Name)"
                    Set-DryADWmiFilterLink @SetDryWmiFilterLinkParams
                }
            }

            ol i "WMIFilters - Site scope ($SiteWmiFilterLinksCount)" -sh
            ForEach ($GPOWMIFilter in $SiteWMIFilters) {
                ForEach ($GPOWMIFilterLink in $GPOWMIFilter.links) {
                    # Progress
                    $ElementsCounter++  
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Linking WMIFilter '$($GPOWMIFilter.Name)'"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )
                    }
                    Write-Progress @WriteProgressParameters
                
                    $SetDryWmiFilterLinkParams = @{
                        GPOName       = $GPOWMIFilterLink
                        WMIFilterName = $GPOWMIFilter.Name
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $SetDryWmiFilterLinkParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $SetDryWmiFilterLinkParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Linking WMI Filter (site '$adsite')","$($GPOWMIFilter.Name)"
                    Set-DryADWmiFilterLink @SetDryWmiFilterLinkParams
                }
            }

            ol i "WMIFilters - Computer scope ($ComputerWmiFilterLinksCount)" -sh
            ForEach ($GPOWMIFilter in $ComputerWMIFilters) {
                ForEach ($GPOWMIFilterLink in $GPOWMIFilter.links) {
                    # Progress
                    $ElementsCounter++  
                    $WriteProgressParameters = @{
                        Activity        = 'Configuring Active Directory'
                        Status          = "Item ($ElementsCounter / $NumberOfElementsToProcess): Linking WMIFilter '$($GPOWMIFilter.Name)'"
                        PercentComplete = (($ElementsCounter / $NumberOfElementsToProcess) * 100 )
                    }
                    Write-Progress @WriteProgressParameters
                
                    $SetDryWmiFilterLinkParams = @{
                        GPOName       = $GPOWMIFilterLink
                        WMIFilterName = $GPOWMIFilter.Name
                    }
                    Switch ($ExecutionType) {
                        'Local' {
                            $SetDryWmiFilterLinkParams += @{
                                DomainController = $DomainController
                            }
                        }
                        'Remote' {
                            $SetDryWmiFilterLinkParams += @{
                                PSSession = $PSSession
                            }
                        }
                    }
                    ol i "Linking WMI Filter (computer '$ComputerName')","$($GPOWMIFilter.Name)"
                    Set-DryADWmiFilterLink @SetDryWmiFilterLinkParams
                }
            }

            $DebugCounter += $NumberOfWMIFilterLinks
            If ($ElementsCounter -ne $DebugCounter) {
                Throw "Elementscounter is $ElementsCounter, but was supposed to be $DebugCounter"
            }
        } 
        ol i "ConfigAD ran successfully" -sh
    }
    Catch {
        $PSCmdLet.ThrowTerminatingError($_)
    }
    Finally {
        $PSSession | Remove-PSSession -ErrorAction SilentlyContinue
        
        # End the progress bar
        Write-Progress -Completed -Activity "Configuring AD objects"
        ol i "Set-DryADConfiguration ended" -sh
    }
}