ADSyncConfig.psm1

<#
  
.SYNOPSIS
    Prepares Active Directory configuration for various purposes.
 
.DESCRIPTION
 
    AdSyncConfig.psm1 is a Windows PowerShell script module that provides functions that are
    used to prepare your Active Directory forest and domains for Azure AD Connect Sync
    features.
#>


#----------------------------------------------------------
# STATIC VARIABLES
#----------------------------------------------------------
# Well known SIDS
$selfSid = "S-1-5-10"
$enterpriseDomainControllersSid = "S-1-5-9"

# Regex variables
$distinguishedNameRegex = [Regex] '^(?:(?<cn>CN=(?<name>[^,]*)),)?(?:(?<path>(?:(?:CN|OU)=[^,]+,?)+),)?(?<domain>(?:DC=[^,]+,?)+)$'
$defaultADobjProperties = @('UserPrincipalName','ObjectGUID','ObjectSID','mS-DS-ConsistencyGuid','sAMAccountName','objectSid')

# Parameter variables
$commaSeparator =","
$periodSeparator = "."
$colonSeparator = ":"
$atSeparator = "@"
$aclSeparator = '" "'
$inheritanceSubObjectsOnly = 'S'
$inheritanceThisAndSubObjects = 'T'
$inheritanceNone = 'N'

<#
    .SYNOPSIS
        Tighten permissions on an AD object that is not otherwise included in any AD protected security group.
        A typical example is the AD Connect account (MSOL) created by AAD Connect automatically. This account
        has replicate permissions on all domains, however can be easily compromised as it is not protected.
 
    .DESCRIPTION
        The Set-ADSyncRestrictedPermissions Function will tighten permissions oo the
        account provided. Tightening permissions involves the following steps:
        1. Disable inheritance on the specified object
        2. Remove all ACEs on the specific object, except ACEs specific to SELF. We want to keep
           the default permissions intact when it comes to SELF.
        3. Assign these specific permissions:
 
                Type Name Access Applies To
                =============================================================================================
                Allow SYSTEM Full Control This object
                Allow Enterprise Admins Full Control This object
                Allow Domain Admins Full Control This object
                Allow Administrators Full Control This object
 
                Allow Enterprise Domain Controllers List Contents
                                                                    Read All Properties
                                                                    Read Permissions This object
 
                Allow Authenticated Users List Contents
                                                                    Read All Properties
                                                                    Read Permissions This object
 
    .PARAMETER ADConnectorAccountDN
        DistinguishedName of the Active Directory account whose permissions need to be tightened. This is typically the MSOL_nnnnnnnnnn
        account or a custom domain account that is configured in your AD Connector.
 
    .PARAMETER Credential
        Administrator credential that has the necessary privileges to restrict the permissions on the ADConnectorAccountDN account.
        This is typically the Enterprise or Domain administrator.
        Use the fully qualified domain name of the administrator account to avoid account lookup failures. Example: CONTOSO\admin
 
    .PARAMETER DisableCredentialValidation
        When DisableCredentialValidation is used, the function will not check if the credentials provided in -Credential are valid in AD
        and if the account provided has the necessary privileges to restrict the permissions on the ADConnectorAccountDN account.
 
    .PARAMETER TargetForest
        Optional parameter to configure a target forest instead of the one used by the current user context.
 
    .EXAMPLE
       Set-ADSyncRestrictedPermissions -ADConnectorAccountDN "CN=TestAccount1,CN=Users,DC=Contoso,DC=com" -Credential $(Get-Credential)
#>

Function Set-ADSyncRestrictedPermissions
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")]
    Param(
        [Parameter(Mandatory=$True)] 
        [string] $ADConnectorAccountDN,

        [Parameter(Mandatory=$True)] 
        [System.Management.Automation.PSCredential] $Credential,

        [Parameter(Mandatory=$False)] 
        [switch] $DisableCredentialValidation,

        [string] $TargetForest = $null
    )

    # Function init
    [string] $functionMsg = "Set-ADSyncRestrictedPermissions:"

    If (!$DisableCredentialValidation)
    {
        # Validate Credential
        TestCredential $Credential
    }

    If ($PSCmdlet.ShouldProcess($ADConnectorAccountDN, "Set restricted permissions")) 
    {
        $networkCredential = $Credential.GetNetworkCredential()
        If ($TargetForest)
        {
            $path = "LDAP://" + $TargetForest + "/" + $ADConnectorAccountDN    
        }
        Else
        {
            $path = "LDAP://" + $networkCredential.Domain + "/" + $ADConnectorAccountDN        
        }
        
        $de = New-Object System.DirectoryServices.DirectoryEntry($path, $Credential.UserName, $networkCredential.Password)
        $selfName = ConvertSIDtoName $selfSid            

        Try
        {    
            [System.DirectoryServices.DirectoryEntryConfiguration]$deOptions = $de.get_Options()
            $deOptions.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl
        }
        Catch
        {
            Throw "$functionMsg Failure using credential to access Active Directory. Error Details: $($_.Exception.Message)"
        }

        Try
        {
            Write-Output "$functionMsg Setting Restricted permissions on '$ADConnectorAccountDN'..."
            # disable inheritance on the object and remove inherited DACLs
            $de.ObjectSecurity.SetAccessRuleProtection($true, $false);

            # remove all DACLs on the object except SELF
            $acl = $de.ObjectSecurity.GetAccessRules($true, $false, [System.Security.Principal.NTAccount])
            ForEach ($ace in $acl) 
            {
                If ($ace.IdentityReference -ne $selfName) 
                {
                    $de.ObjectSecurity.RemoveAccessRule($ace) > $null
                }
            }

            # Add specific DACLs on the object
            # Add Full Control for SYSTEM
            $systemSid = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null)
            $systemDacl = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($systemSid, [System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AccessControlType]::Allow)
            $de.ObjectSecurity.AddAccessRule($systemDacl)

            # Add Full Control for Enterprise Admins
            $eaSid = GetEnterpriseAdminsSid $Credential
            $eaDacl = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($eaSid, [System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AccessControlType]::Allow)
            $de.ObjectSecurity.AddAccessRule($eaDacl)

            # Add Full Control for Domain Admins
            $daSid = GetDomainAdminsSid $Credential
            $daDacl = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($daSid, [System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AccessControlType]::Allow)
            $de.ObjectSecurity.AddAccessRule($daDacl)

            # Add Full Control for Administrators
            $adminSid = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid, $null)
            $adminDacl = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($adminSid, [System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AccessControlType]::Allow)
            $de.ObjectSecurity.AddAccessRule($adminDacl)

            # Add Generic Read for ENTERPRISE DOMAIN CONTROLLERS
            $edcSid = New-Object System.Security.Principal.SecurityIdentifier($enterpriseDomainControllersSid)
            $edcDacl = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($edcSid, [System.DirectoryServices.ActiveDirectoryRights]::GenericRead, [System.Security.AccessControl.AccessControlType]::Allow)
            $de.ObjectSecurity.AddAccessRule($edcDacl)

            # Add Generic Read for Authenticated Users
            $authenticatedUsersSid = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AuthenticatedUserSid, $null)
            $authenticatedUsersDacl = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($authenticatedUsersSid, [System.DirectoryServices.ActiveDirectoryRights]::GenericRead, [System.Security.AccessControl.AccessControlType]::Allow)
            $de.ObjectSecurity.AddAccessRule($authenticatedUsersDacl)

            $de.CommitChanges()
        }
        Catch [Exception]
        {
            Throw "$functionMsg Setting Restricted permissions on $ADConnectorAccountDN failed. Error Details: $($_.Exception.Message)"
        }
        Finally
        {
            If ($de -ne $null)
            {
                $de.Dispose()
            }            
        }
    }
}

<#
    .SYNOPSIS
        Initialize your Active Directory forest and domain for password hash synchronization.
 
    .DESCRIPTION
        The Set-ADSyncPasswordHashSyncPermissions Function will give required permissions to the AD synchronization account, which include the following:
        1. Replicating Directory Changes
        2. Replicating Directory Changes All
 
        These permissions are given to all domains in the forest.
 
    .PARAMETER ADConnectorAccountName
        The Name of the Active Directory account that will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER ADConnectorAccountDomain
        The Domain of the Active Directory account that will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER ADConnectorAccountDN
        The DistinguishedName of the Active Directory account that will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER TargetForest
        Optional parameter to configure a target forest instead of the one used by the current user context.
 
    .PARAMETER TargetForestCredential
        Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest.
 
    .EXAMPLE
       Set-ADSyncPasswordHashSyncPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com'
 
    .EXAMPLE
       Set-ADSyncPasswordHashSyncPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com'
 
#>

Function Set-ADSyncPasswordHashSyncPermissions
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")]
    Param
    (
        # AD Connector Account Name used by Azure AD Connect Sync
        [Parameter(ParameterSetName='UserDomain', Mandatory=$True)]
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountName,

        # AD Connector Account Domain used by Azure AD Connect Sync
        [Parameter(ParameterSetName='UserDomain', Mandatory=$True)]
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountDomain,

        # AD Connector Account DistinguishedName used by Azure AD Connect Sync
        [Parameter(ParameterSetName='DistinguishedName', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountDN,

        # Forest to set the permissions on
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [string] $TargetForest = $null,

        # Credentials used to configure the permissions
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [System.Management.Automation.PSCredential] $TargetForestCredential = $null
    )

    # Resolve AD Connector account identity on AD
    $ADConnectorIdentity = ResolveADobject -IdentityName $ADConnectorAccountName -IdentityDomain $ADConnectorAccountDomain -IdentityDN $ADConnectorAccountDN `
                            -IdentityParameterSet $($PSCmdlet.ParameterSetName) -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential

    $ADConnectorAccountSid = $ADConnectorIdentity.objectSid

    # Define AD ACL
    $acls = "`"$ADConnectorAccountSid" + ":CA;Replicating Directory Changes`"" + " " 
    $acls += "`"$ADConnectorAccountSid" + ":CA;Replicating Directory Changes All`""

    # Set root permissions for all Domains in the Forest
    $message = "Grant Password Hash Synchronization permissions"
    GrantADPermissionsOnAllDomains -ACL $acls -Message $message -Inheritance $inheritanceNone -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
}

<#
    .SYNOPSIS
        Initialize your Active Directory forest and domain for password write-back from Azure AD.
 
    .DESCRIPTION
        The Set-ADSyncPasswordWritebackPermissions Function will give required permissions to the AD synchronization account, which include the following:
        1. Reset Password on descendant user objects
        2. Write Property access on lockoutTime attribute for all descendant user objects
        3. Write Property access on pwdLastSet attribute for all descendant user objects
 
        These permissions are applied to all domains in the forest.
        Optionally you can provide a DistinguishedName in ADobjectDN parameter to set these permissions on that AD Object only (including inheritance to sub objects).
 
    .PARAMETER ADConnectorAccountName
        The Name of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER ADConnectorAccountDomain
        The Domain of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER ADConnectorAccountDN
        The DistinguishedName of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER IncludeAdminSDHolders
        Optional parameter to indicate if AdminSDHolder container should be updated with these permissions
 
    .PARAMETER TargetForest
        Optional parameter to configure a target forest instead of the one used by the current user context.
 
    .PARAMETER TargetForestCredential
        Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest.
 
    .EXAMPLE
       Set-ADSyncPasswordWritebackPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com'
 
    .EXAMPLE
       Set-ADSyncPasswordWritebackPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com'
 
    .EXAMPLE
       Set-ADSyncPasswordWritebackPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' -IncludeAdminSDHolders
 
    .EXAMPLE
       Set-ADSyncPasswordWritebackPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' -ADobjectDN 'OU=AzureAD,DC=Contoso,DC=com'
#>

Function Set-ADSyncPasswordWritebackPermissions
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")]
    Param
    (
        # AD Connector Account Name used by Azure AD Connect Sync
        [Parameter(ParameterSetName='UserDomain', Mandatory=$True)]
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountName,

        # AD Connector Account Domain used by Azure AD Connect Sync
        [Parameter(ParameterSetName='UserDomain', Mandatory=$True)]
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountDomain,

        # AD Connector Account DistinguishedName used by Azure AD Connect Sync
        [Parameter(ParameterSetName='DistinguishedName', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountDN,

        # DistinguishedName of the target AD object to set permissions (optional)
        [string] $ADobjectDN = $null,
        
        # Update permissions on the AdminSdHolders container (optional)
        [switch] $IncludeAdminSDHolders = $false,

        # Forest to set the permissions on
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [string] $TargetForest = $null,

        # Credentials used to configure the permissions
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [System.Management.Automation.PSCredential] $TargetForestCredential = $null
    )

    # Resolve AD Connector account identity on AD
    $ADConnectorIdentity = ResolveADobject -IdentityName $ADConnectorAccountName -IdentityDomain $ADConnectorAccountDomain -IdentityDN $ADConnectorAccountDN `
                            -IdentityParameterSet $($PSCmdlet.ParameterSetName) -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential

    $ADConnectorAccountSid = $ADConnectorIdentity.objectSid

    # Define AD ACL
    $acls = "`"$ADConnectorAccountSid" + ":CA;Reset Password;user`"" + " "
    $acls += "`"$ADConnectorAccountSid" + ":WP;lockoutTime;user`"" + " "
    $acls += "`"$ADConnectorAccountSid" + ":WP;pwdLastSet;user`""

    # Check if setting permissions on a AD object only
    $message = "Grant Password Writeback permissions"

    If (![string]::IsNullOrEmpty($ADobjectDN))
    {
        # Get target AD Object
        Write-Verbose "Set-ADSyncPasswordWritebackPermissions: Get AD user object > Get-ADObject -SearchBase $ADobjectDN -SearchScope Base -Filter * "
        $errorMsg = "Set-ADSyncPasswordWritebackPermissions: Unable to find Active Directory object '$ADobjectDN'. "
        $object = Get-ADSyncADObject $ADobjectDN $errorMsg

        # Define Inheritance Type
        $objectInheritanceType = $inheritanceThisAndSubObjects
        If (($object.objectclass -eq 'container') -or ($object.objectclass -eq 'organizationalUnit') -or ($object.objectclass -eq 'domainDNS'))
        {
            $objectInheritanceType = $inheritanceSubObjectsOnly
        }

        # Set AD Permissions on a target AD object
        Write-Verbose "Set-ADSyncPasswordWritebackPermissions: Calling GrantADPermissionsOnADobject on target object '$ADobjectDN' with inheritanceType = '$objectInheritanceType'..."
        GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $objectInheritanceType -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential

        # In case target ADObjectDN is a domain root, set Unexpire Password for the domain
        # Note: Unexpire Password permissions are only applicable on a domain root object (i.e. domainDNS objectClass)
        if ($object.objectclass -eq 'domainDNS')
        {
            # Define Inheritance Type for Unexpire Password
            $objectInheritanceType = $inheritanceThisAndSubObjects

            # Set ACL for Unexpire Password on the domain's root (domainDNS)
            $acls = "`"$ADConnectorAccountSid" + ":CA;Unexpire Password;`""
            $message = "Grant Password Writeback permission for Unexpire Password extended right"
            GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $objectInheritanceType -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
        }
    }
    Else
    {
        # Set root permissions for all Domains in the Forest
        Write-Verbose "Set-ADSyncPasswordWritebackPermissions: Calling GrantADPermissionsOnAllDomains on all domain's root"
        GrantADPermissionsOnAllDomains -ACL $acls -Message $message -IncludeAdminSDHolders:$IncludeAdminSDHolders -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential

        # Define Inheritance Type for Unexpire Password
        $objectInheritanceType = $inheritanceThisAndSubObjects

        # Set ACL for Unexpire Password on all domains in the forest
        $acls = "`"$ADConnectorAccountSid" + ":CA;Unexpire Password;`""
        $message = "Grant Password Writeback permission for Unexpire Password extended right"
        GrantADPermissionsOnAllDomains -ACL $acls -Message $message -Inheritance $objectInheritanceType -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
    }
}

<#
    .SYNOPSIS
        Initialize your Active Directory forest and domain for Group writeback from Azure AD.
 
    .DESCRIPTION
        The Set-ADSyncUnifiedGroupWritebackPermissions Function will give required permissions to the AD synchronization account, which include the following:
        1. Generic Read/Write, Delete, Delete Tree and Create\Delete Child for Group Object types and SubObjects
 
        These permissions are applied to all domains in the forest.
        Optionally you can provide a DistinguishedName in ADobjectDN parameter to set these permissions on that AD Object only (including inheritance to sub objects).
        In this case, ADobjectDN will be the Distinguished Name of the Container that you desire to link with the GroupWriteback feature.
 
    .PARAMETER ADConnectorAccountName
        The Name of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER ADConnectorAccountDomain
        The Domain of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER ADConnectorAccountDN
        The DistinguishedName of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER includeAdminSDHolders
        Optional parameter to indicate if AdminSDHolder container should be updated with these permissions
 
    .PARAMETER TargetForest
        Optional parameter to configure a target forest instead of the one used by the current user context.
 
    .PARAMETER TargetForestCredential
        Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest.
 
    .EXAMPLE
       Set-ADSyncUnifiedGroupWritebackPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com'
 
    .EXAMPLE
       Set-ADSyncUnifiedGroupWritebackPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com'
 
    .EXAMPLE
       Set-ADSyncUnifiedGroupWritebackPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' -IncludeAdminSDHolders
 
    .EXAMPLE
       Set-ADSyncUnifiedGroupWritebackPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' -ADobjectDN 'OU=AzureAD,DC=Contoso,DC=com'
#>

Function Set-ADSyncUnifiedGroupWritebackPermissions
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")]
    Param
    (
        # AD Connector Account Name used by Azure AD Connect Sync
        [Parameter(ParameterSetName='UserDomain', Mandatory=$True)]
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountName,

        # AD Connector Account Domain used by Azure AD Connect Sync
        [Parameter(ParameterSetName='UserDomain', Mandatory=$True)]
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountDomain,

        # AD Connector Account DistinguishedName used by Azure AD Connect Sync
        [Parameter(ParameterSetName='DistinguishedName', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountDN,

        # DistinguishedName of the target AD object to set permissions (optional)
        [string] $ADobjectDN = $null,
        
        # Update permissions on the AdminSdHolders container (optional)
        [switch] $IncludeAdminSDHolders = $false,

        # Forest to set the permissions on
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [string] $TargetForest = $null,

        # Credentials used to configure the permissions
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [System.Management.Automation.PSCredential] $TargetForestCredential = $null
    )

    # Ensure the function is being run as Administrator
    $isRunningAsAdmin = [bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")
    If (!$isRunningAsAdmin)
    {
        Write-Error "This cmdlet must be run in an elevated PowerShell window."
        return
    }

    # Resolve AD Connector account identity on AD
    $ADConnectorIdentity = ResolveADobject -IdentityName $ADConnectorAccountName -IdentityDomain $ADConnectorAccountDomain -IdentityDN $ADConnectorAccountDN `
                            -IdentityParameterSet $($PSCmdlet.ParameterSetName) -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential

    $ADConnectorAccountSid = $ADConnectorIdentity.objectSid

    # Check if setting permissions on a AD object only
    $message = "Grant Group Writeback permissions"

    If (![string]::IsNullOrEmpty($ADobjectDN))
    {
        # Get target AD Object
        Write-Verbose "Set-ADSyncUnifiedGroupWritebackPermissions: Get AD user object > Get-ADObject -SearchBase $ADobjectDN -SearchScope Base -Filter * "
        $errorMsg = "Set-ADSyncUnifiedGroupWritebackPermissions: Unable to find Active Directory object '$ADobjectDN'. "
        $object = Get-ADSyncADObject $ADobjectDN $errorMsg        

        # Set AD Permissions on a target AD object and apply different inheritance flags as necessary
        If (($object.objectclass -eq 'container') -or ($object.objectclass -eq 'organizationalUnit') -or ($object.objectclass -eq 'domainDNS'))
        {
            # Create/Delete Child group objects applies to the AD object and all children
            Write-Verbose "Set-ADSyncUnifiedGroupWritebackPermissions: Calling GrantADPermissionsOnADobject on target object '$ADobjectDN' with inheritanceType = '$inheritanceThisAndSubObjects'..."
            $acls  = "`"$ADConnectorAccountSid" + ":CCDC;group;`""
            GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $inheritanceThisAndSubObjects -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential

            # Generic Read/Write, Delete object, and Delete tree applies to child inherited group objects of the AD object
            Write-Verbose "Set-ADSyncUnifiedGroupWritebackPermissions: Calling GrantADPermissionsOnADobject on target object '$ADobjectDN' with inheritanceType = '$inheritanceSubObjectsOnly'..."
            $acls = "`"$ADConnectorAccountSid" + ":GRGWSDDT;;group`""
            GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $inheritanceSubObjectsOnly -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
        }
        Else
        {
            Throw "Group writeback permissions can only be set on a Active Directory Domain, Organizational Unit or Container object."
        }
    }
    Else
    {
        # Define AD ACL
        # Create/Delete Child group objects applies to the AD object and all children
        $acls = "`"$ADConnectorAccountSid" + ":CCDC;group;`"" + " "
        # Generic Read/Write, Delete object, and Delete tree applies to child inherited group objects of the AD object
        $acls += "`"$ADConnectorAccountSid" + ":GRGWSDDT;;group`""

        # Set root permissions for all Domains in the Forest
        Write-Verbose "Set-ADSyncUnifiedGroupWritebackPermissions: Calling GrantADPermissionsOnAllDomains on all domain's root"
        GrantADPermissionsOnAllDomains -ACL $acls -Message $message -IncludeAdminSDHolders:$IncludeAdminSDHolders -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
    }
}

<#
    .SYNOPSIS
        Initialize your Active Directory forest and domain for mS-DS-ConsistencyGuid feature.
 
    .DESCRIPTION
        The Set-ADSyncMsDsConsistencyGuidPermissions Function will give required permissions to the AD synchronization account, which include the following:
        1. Read/Write Property access on mS-DS-ConsistencyGuid attribute for all descendant user and group objects
 
        These permissions are applied to all domains in the forest.
        Optionally you can provide a DistinguishedName in ADobjectDN parameter to set these permissions on that AD Object only (including inheritance to sub objects).
 
    .PARAMETER ADConnectorAccountName
        The Name of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER ADConnectorAccountDomain
        The Domain of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER ADConnectorAccountDN
        The DistinguishedName of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER includeAdminSDHolders
        Optional parameter to indicate if AdminSDHolder container should be updated with these permissions
 
    .PARAMETER TargetForest
        Optional parameter to configure a target forest instead of the one used by the current user context.
 
    .PARAMETER TargetForestCredential
        Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest.
 
    .EXAMPLE
       Set-ADSyncMsDsConsistencyGuidPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com'
 
    .EXAMPLE
       Set-ADSyncMsDsConsistencyGuidPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com'
 
    .EXAMPLE
       Set-ADSyncMsDsConsistencyGuidPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' -IncludeAdminSDHolders
 
    .EXAMPLE
       Set-ADSyncMsDsConsistencyGuidPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' -ADobjectDN 'OU=AzureAD,DC=Contoso,DC=com'
#>

Function Set-ADSyncMsDsConsistencyGuidPermissions
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")]
    Param(
        # AD Connector Account Name used by Azure AD Connect Sync
        [Parameter(ParameterSetName='UserDomain', Mandatory=$True)]
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountName,

        # AD Connector Account Domain used by Azure AD Connect Sync
        [Parameter(ParameterSetName='UserDomain', Mandatory=$True)]
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountDomain,

        # AD Connector Account DistinguishedName used by Azure AD Connect Sync
        [Parameter(ParameterSetName='DistinguishedName', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountDN,

        # DistinguishedName of the target AD object to set permissions (optional)
        [string] $ADobjectDN = $null,
        
        # Update permissions on the AdminSdHolders container (optional)
        [switch] $IncludeAdminSDHolders = $false,

        # Forest to set the permissions on
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [string] $TargetForest = $null,

        # Credentials used to configure the permissions
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [System.Management.Automation.PSCredential] $TargetForestCredential = $null
    )    

    # Resolve AD Connector account identity on AD
    $ADConnectorIdentity = ResolveADobject -IdentityName $ADConnectorAccountName -IdentityDomain $ADConnectorAccountDomain -IdentityDN $ADConnectorAccountDN `
                            -IdentityParameterSet $($PSCmdlet.ParameterSetName) -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential

    $ADConnectorAccountSid = $ADConnectorIdentity.objectSid

    # Define AD ACL
    $acls = "`"$ADConnectorAccountSid" + ":RPWP;mS-DS-ConsistencyGuid;user`"" + " "
    $acls += "`"$ADConnectorAccountSid" + ":RPWP;mS-DS-ConsistencyGuid;group`""

    # Check if setting permissions on a AD object only
    $message = "Grant mS-DS-ConsistencyGuid permissions"

    If (![string]::IsNullOrEmpty($ADobjectDN))
    {
        # Get target AD Object
        Write-Verbose "Set-ADSyncMsDsConsistencyGuidPermissions: Get AD user object > Get-ADObject -SearchBase $ADobjectDN -SearchScope Base -Filter * "
        $errorMsg = "Set-ADSyncMsDsConsistencyGuidPermissions: Unable to find Active Directory object '$ADobjectDN'. "
        $object = Get-ADSyncADObject $ADobjectDN $errorMsg

        # Define Inheritance Type
        $objectInheritanceType = $inheritanceThisAndSubObjects
        If (($object.objectclass -eq 'container') -or ($object.objectclass -eq 'organizationalUnit') -or ($object.objectclass -eq 'domainDNS'))
        {
            $objectInheritanceType = $inheritanceSubObjectsOnly
        }

        # Set AD Permissions on a target AD object
        Write-Verbose "Set-ADSyncMsDsConsistencyGuidPermissions: Calling GrantADPermissionsOnADobject on target object '$ADobjectDN' with inheritanceType = '$objectInheritanceType'..."
        GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $objectInheritanceType -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
    }
    Else
    {
        # Set root permissions for all Domains in the Forest
        Write-Verbose "Set-ADSyncMsDsConsistencyGuidPermissions: Calling GrantADPermissionsOnAllDomains on all domain's root"
        GrantADPermissionsOnAllDomains -ACL $acls -Message $message -IncludeAdminSDHolders:$IncludeAdminSDHolders -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
    }
}

<#
    .SYNOPSIS
        Initialize your Active Directory forest and domain for Exchange Mail Public Folder feature.
 
    .DESCRIPTION
        The Set-ADSyncExchangeMailPublicFolderPermissions Function will give required permissions to the AD synchronization account, which include the following:
        1. Read Property access on all attributes for all descendant publicfolder objects
 
        These permissions are applied to all domains in the forest.
        Optionally you can provide a DistinguishedName in ADobjectDN parameter to set these permissions on that AD Object only (including inheritance to sub objects).
 
    .PARAMETER ADConnectorAccountName
        The Name of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER ADConnectorAccountDomain
        The Domain of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER ADConnectorAccountDN
        The DistinguishedName of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER includeAdminSDHolders
        Optional parameter to indicate if AdminSDHolder container should be updated with these permissions
 
    .PARAMETER TargetForest
        Optional parameter to configure a target forest instead of the one used by the current user context.
 
    .PARAMETER TargetForestCredential
        Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest.
 
    .EXAMPLE
       Set-ADSyncExchangeMailPublicFolderPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com'
 
    .EXAMPLE
       Set-ADSyncExchangeMailPublicFolderPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com'
 
    .EXAMPLE
       Set-ADSyncExchangeMailPublicFolderPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' -IncludeAdminSDHolders
 
    .EXAMPLE
       Set-ADSyncExchangeMailPublicFolderPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' -ADobjectDN 'OU=AzureAD,DC=Contoso,DC=com'
#>

Function Set-ADSyncExchangeMailPublicFolderPermissions
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")]
    Param(
        # AD Connector Account Name used by Azure AD Connect Sync
        [Parameter(ParameterSetName='UserDomain', Mandatory=$True)]
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountName,

        # AD Connector Account Domain used by Azure AD Connect Sync
        [Parameter(ParameterSetName='UserDomain', Mandatory=$True)]
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountDomain,

        # AD Connector Account DistinguishedName used by Azure AD Connect Sync
        [Parameter(ParameterSetName='DistinguishedName', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountDN,

        # DistinguishedName of the target AD object to set permissions (optional)
        [string] $ADobjectDN = $null,
        
        # Update permissions on the AdminSdHolders container (optional)
        [switch] $IncludeAdminSDHolders = $false,

        # Forest to set the permissions on
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [string] $TargetForest = $null,

        # Credentials used to configure the permissions
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [System.Management.Automation.PSCredential] $TargetForestCredential = $null
    )

    # Resolve AD Connector account identity on AD
    $ADConnectorIdentity = ResolveADobject -IdentityName $ADConnectorAccountName -IdentityDomain $ADConnectorAccountDomain -IdentityDN $ADConnectorAccountDN `
                            -IdentityParameterSet $($PSCmdlet.ParameterSetName) -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential

    $ADConnectorAccountSid = $ADConnectorIdentity.objectSid

    # Define AD ACL
    $acls = "`"$ADConnectorAccountSid" + ":RP;;publicFolder`""

    # Check if setting permissions on a AD object only
    $message = "Grant Exchange Mail Public Folder permissions"
    If (![string]::IsNullOrEmpty($ADobjectDN))
    {
        # Get target AD Object
        Write-Verbose "Set-ADSyncExchangeMailPublicFolderPermissions: Get AD user object > Get-ADObject -SearchBase $ADobjectDN -SearchScope Base -Filter * "
        $errorMsg = "Set-ADSyncExchangeMailPublicFolderPermissions: Unable to find Active Directory object '$ADobjectDN'. "
        $object = Get-ADSyncADObject $ADobjectDN $errorMsg

        # Define Inheritance Type
        $objectInheritanceType = $inheritanceThisAndSubObjects
        If (($object.objectclass -eq 'container') -or ($object.objectclass -eq 'organizationalUnit') -or ($object.objectclass -eq 'domainDNS'))
        {
            $objectInheritanceType = $inheritanceSubObjectsOnly
        }

        # Set AD Permissions on a target AD object
        Write-Verbose "Set-ADSyncExchangeMailPublicFolderPermissions: Calling GrantADPermissionsOnADobject on target object '$ADobjectDN' with inheritanceType = '$objectInheritanceType'..."
        GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $objectInheritanceType -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
    }
    Else
    {
        # Set root permissions for all Domains in the Forest
        Write-Verbose "Set-ADSyncExchangeMailPublicFolderPermissions: Calling GrantADPermissionsOnAllDomains on all domain's root"
        GrantADPermissionsOnAllDomains -ACL $acls -Message $message -IncludeAdminSDHolders:$IncludeAdminSDHolders -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
    }
}

<#.SYNOPSIS
        Initialize your Active Directory forest and domain for Exchange Hybrid feature.
 
    .DESCRIPTION
        The Set-ADSyncExchangeHybridPermissions Function will give required permissions to the
        AD synchronization account, which include the following:
        1. Read/Write Property access on all attributes for all descendant user objects
        2. Read/Write Property access on all attributes for all descendant inetOrgPerson objects
        3. Read/Write Property access on all attributes for all descendant group objects
        4. Read/Write Property access on all attributes for all descendant contact objects
 
        These permissions are applied to all domains in the forest.
        Optionally you can provide a DistinguishedName in ADobjectDN parameter to set these permissions on that AD Object only (including inheritance to sub objects).
 
    .PARAMETER ADConnectorAccountName
        The Name of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER ADConnectorAccountDomain
        The Domain of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER ADConnectorAccountDN
        The DistinguishedName of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER includeAdminSDHolders
        Optional parameter to indicate if AdminSDHolder container should be updated with these permissions
 
    .PARAMETER TargetForest
        Optional parameter to configure a target forest instead of the one used by the current user context.
 
    .PARAMETER TargetForestCredential
        Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest.
 
    .EXAMPLE
       Set-ADSyncExchangeHybridPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com'
 
    .EXAMPLE
       Set-ADSyncExchangeHybridPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com'
 
    .EXAMPLE
       Set-ADSyncExchangeHybridPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' -IncludeAdminSDHolders
 
    .EXAMPLE
       Set-ADSyncExchangeHybridPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' -ADobjectDN 'OU=AzureAD,DC=Contoso,DC=com'
#>

Function Set-ADSyncExchangeHybridPermissions
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")]
    Param
    (
        # AD Connector Account Name used by Azure AD Connect Sync
        [Parameter(ParameterSetName='UserDomain', Mandatory=$True)]
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountName,

        # AD Connector Account Domain used by Azure AD Connect Sync
        [Parameter(ParameterSetName='UserDomain', Mandatory=$True)]
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountDomain,

        # AD Connector Account DistinguishedName used by Azure AD Connect Sync
        [Parameter(ParameterSetName='DistinguishedName', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountDN,

        # DistinguishedName of the target AD object to set permissions (optional)
        [string] $ADobjectDN = $null,
        
        # Update permissions on the AdminSdHolders container (optional)
        [switch] $IncludeAdminSDHolders = $false,

        # Forest to set the permissions on
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [string] $TargetForest = $null,

        # Credentials used to configure the permissions
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [System.Management.Automation.PSCredential] $TargetForestCredential = $null
    )

    # Resolve AD Connector account identity on AD
    $ADConnectorIdentity = ResolveADobject -IdentityName $ADConnectorAccountName -IdentityDomain $ADConnectorAccountDomain -IdentityDN $ADConnectorAccountDN `
                            -IdentityParameterSet $($PSCmdlet.ParameterSetName) -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
    
    $ADConnectorAccountSid = $ADConnectorIdentity.objectSid

    # Define AD ACL
    $acls  = "`"$ADConnectorAccountSid" + ":RPWP;;user`"" + " "
    $acls += "`"$ADConnectorAccountSid" + ":RPWP;;inetOrgPerson`"" + " "
    $acls += "`"$ADConnectorAccountSid" + ":RPWP;;group`"" + " "
    $acls += "`"$ADConnectorAccountSid" + ":RPWP;;contact`""

    # Check if setting permissions on a AD object only
    $message = "Grant Exchange Hybrid permissions"
    If (![string]::IsNullOrEmpty($ADobjectDN))
    {
        # Get target AD Object
        Write-Verbose "Set-ADSyncExchangeHybridPermissions: Get AD user object > Get-ADObject -SearchBase $ADobjectDN -SearchScope Base -Filter * "
        $errorMsg = "Set-ADSyncExchangeHybridPermissions: Unable to find Active Directory object '$ADobjectDN'. "
        $object = Get-ADSyncADObject $ADobjectDN $errorMsg

        # Define Inheritance Type
        $objectInheritanceType = $inheritanceThisAndSubObjects
        If (($object.objectclass -eq 'container') -or ($object.objectclass -eq 'organizationalUnit') -or ($object.objectclass -eq 'domainDNS'))
        {
            $objectInheritanceType = $inheritanceSubObjectsOnly
        }

        # Set AD Permissions on a target AD object
        Write-Verbose "Set-ADSyncExchangeHybridPermissions: Calling GrantADPermissionsOnADobject on target object '$ADobjectDN' with inheritanceType = '$objectInheritanceType'..."
        GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $objectInheritanceType -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
    }
    Else
    {
        # Set root permissions for all Domains in the Forest
        Write-Verbose "Set-ADSyncExchangeHybridPermissions: Calling GrantADPermissionsOnAllDomains on all domain's root"
        GrantADPermissionsOnAllDomains -ACL $acls -Message $message -IncludeAdminSDHolders:$IncludeAdminSDHolders -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
    }
}

<#
    .SYNOPSIS
        Initialize your Active Directory forest and domain for basic read permissions.
 
    .DESCRIPTION
        The Set-ADSyncBasicReadPermissions Function will give required permissions to the AD synchronization account, which include the following:
        1. Read Property access on all attributes for all descendant computer objects
        2. Read Property access on all attributes for all descendant device objects
        3. Read Property access on all attributes for all descendant foreignsecurityprincipal objects
        5. Read Property access on all attributes for all descendant user objects
        6. Read Property access on all attributes for all descendant inetOrgPerson objects
        7. Read Property access on all attributes for all descendant group objects
        8. Read Property access on all attributes for all descendant contact objects
 
        These permissions are applied to all domains in the forest.
        Optionally you can provide a DistinguishedName in ADobjectDN parameter to set these permissions on that AD Object only (including inheritance to sub objects).
 
    .PARAMETER ADConnectorAccountName
        The Name of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER ADConnectorAccountDomain
        The Domain of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER ADConnectorAccountDN
        The DistinguishedName of the Active Directory account that is or will be used by Azure AD Connect Sync to manage objects in the directory.
 
    .PARAMETER includeAdminSDHolders
        Optional parameter to indicate if AdminSDHolder container should be updated with these permissions
 
    .PARAMETER TargetForest
        Optional parameter to configure a target forest instead of the one used by the current user context.
 
    .PARAMETER TargetForestCredential
        Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest.
 
    .EXAMPLE
       Set-ADSyncBasicReadPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com'
 
    .EXAMPLE
       Set-ADSyncBasicReadPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com'
 
    .EXAMPLE
       Set-ADSyncBasicReadPermissions -ADConnectorAccountDN 'CN=ADConnector,OU=AzureAD,DC=Contoso,DC=com' -IncludeAdminSDHolders
 
    .EXAMPLE
       Set-ADSyncBasicReadPermissions -ADConnectorAccountName 'ADConnector' -ADConnectorAccountDomain 'Contoso.com' -ADobjectDN 'OU=AzureAD,DC=Contoso,DC=com'
#>

Function Set-ADSyncBasicReadPermissions
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")]
    Param(
        # AD Connector Account Name used by Azure AD Connect Sync
        [Parameter(ParameterSetName='UserDomain', Mandatory=$True)]
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountName,

        # AD Connector Account Domain used by Azure AD Connect Sync
        [Parameter(ParameterSetName='UserDomain', Mandatory=$True)]
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountDomain,

        # AD Connector Account DistinguishedName used by Azure AD Connect Sync
        [Parameter(ParameterSetName='DistinguishedName', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [string] $ADConnectorAccountDN,

        # DistinguishedName of the target AD object to set permissions (optional)
        [string] $ADobjectDN = $null,
        
        # Update permissions on the AdminSdHolders container (optional)
        [switch] $IncludeAdminSDHolders = $false,

        # Forest to set the permissions on
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [string] $TargetForest = $null,

        # Credentials used to configure the permissions
        [Parameter(ParameterSetName='UserDomainWithCredential', Mandatory=$True)]
        [Parameter(ParameterSetName='DistinguishedNameWithCredential', Mandatory=$True)]
        [System.Management.Automation.PSCredential] $TargetForestCredential = $null
    )

    # Resolve AD Connector account identity on AD
    $ADConnectorIdentity = ResolveADobject -IdentityName $ADConnectorAccountName -IdentityDomain $ADConnectorAccountDomain -IdentityDN $ADConnectorAccountDN `
                            -IdentityParameterSet $($PSCmdlet.ParameterSetName) -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential

    $ADConnectorAccountSid = $ADConnectorIdentity.objectSid

    # Define AD ACL
    $acls =  "`"$ADConnectorAccountSid" + ":RP;;computer`"" + " "
    $acls += "`"$ADConnectorAccountSid" + ":RP;;device`"" + " "
    $acls += "`"$ADConnectorAccountSid" + ":RP;;foreignsecurityprincipal`"" + " "
    $acls += "`"$ADConnectorAccountSid" + ":RP;;user`"" + " "
    $acls += "`"$ADConnectorAccountSid" + ":RP;;inetOrgPerson`"" + " "
    $acls += "`"$ADConnectorAccountSid" + ":RP;;group`"" + " "
    $acls += "`"$ADConnectorAccountSid" + ":RP;;contact`""
    
    # Check if setting permissions on a AD object only
    $message = "Grant basic read permissions"
    If (![string]::IsNullOrEmpty($ADobjectDN))
    {
        # Get target AD Object
        Write-Verbose "Set-ADSyncBasicReadPermissions: Get AD user object > Get-ADObject -SearchBase $ADobjectDN -SearchScope Base -Filter * "
        $errorMsg = "Set-ADSyncBasicReadPermissions: Unable to find Active Directory object '$ADobjectDN'. "
        $object = Get-ADSyncADObject $ADobjectDN $errorMsg

        # Define Inheritance Type
        $objectInheritanceType = $inheritanceThisAndSubObjects
        If (($object.objectclass -eq 'container') -or ($object.objectclass -eq 'organizationalUnit') -or ($object.objectclass -eq 'domainDNS'))
        {
            $objectInheritanceType = $inheritanceSubObjectsOnly
        }

        # Set AD Permissions on a target AD object
        Write-Verbose "Set-ADSyncBasicReadPermissions: Calling GrantADPermissionsOnADobject on target object '$ADobjectDN' with inheritanceType = '$objectInheritanceType'..."
        GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -InheritanceType $objectInheritanceType -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential

        # In case target ADObjectDN is a domain root, set Replicating Directory Changes/All permission for the domain
        # Note: Replicating Directory Changes/All permissions are only applicable on a domain root object (i.e. domainDNS objectClass)
        if ($object.objectclass -eq 'domainDNS')
        {
            # Set Replicating Directory Changes root permission for all Domains in the Forest.
            $acls = "`"$ADConnectorAccountSid" + ":CA;Replicating Directory Changes`"" + " "
            $acls += "`"$ADConnectorAccountSid" + ":CA;Replicating Directory Changes All`""

            $message += " > replicating directory changes"
            Write-Verbose "Set-ADSyncBasicReadPermissions: Calling GrantADPermissionsOnADobject for 'Replicating Directory Changes' permissions on target object '$ADobjectDN'"
            GrantADPermissionsOnADobject -ACL $acls -ADobjectDN $ADobjectDN -Message $message -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
        }
    }
    Else
    {
        # Set root permissions for all Domains in the Forest.
        Write-Verbose "Set-ADSyncBasicReadPermissions: Calling GrantADPermissionsOnAllDomains on all domain's root"
        GrantADPermissionsOnAllDomains -ACL $acls -Message $message -IncludeAdminSDHolders:$IncludeAdminSDHolders -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential

        # Set Replicating Directory Changes/All root permissions for all Domains in the Forest.
        # Note: Replicating Directory Changes/All permissions are only applicable on a domain root object (i.e. domainDNS objectClass)
        $acls = "`"$ADConnectorAccountSid" + ":CA;Replicating Directory Changes`"" + " "
        $acls += "`"$ADConnectorAccountSid" + ":CA;Replicating Directory Changes All`""

        $functionMsg = 'Grant permissions on all Domains:'
        $message += " > replicating directory changes"
        Write-Verbose "Set-ADSyncBasicReadPermissions: Calling GrantADPermissionsOnAllDomains for 'Replicating Directory Changes' permissions on all domain's root"
        GrantADPermissionsOnAllDomains -ACL $acls -Message $message -Inheritance $inheritanceNone -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
    }
}

<#
    .SYNOPSIS
        Gets AD objects with permission inheritance disabled
 
    .DESCRIPTION
        Searches in AD starting from the SearchBase parameter and returns all objects, filtered by ObjectClass parameter, that have the ACL Inheritance currently disabled.
 
    .PARAMETER SearchBase
        The SearchBase for the LDAP query that can be an AD Domain DistinguishedName or a FQDN
 
    .PARAMETER ObjectClass
        The class of the objects to search that can be '*' (for any object class), 'user', 'group', 'container', etc. By default, this function will search for 'organizationalUnit' object class.
 
    .PARAMETER TargetForest
        Optional parameter to configure a target forest instead of the one used by the current user context.
 
    .PARAMETER TargetForestCredential
        Required if the TargetForest parameter is used. Credentials that will be used to perform operations on the target forest.
 
    .EXAMPLE
        Find objects with disabled inheritance in 'Contoso' domain (by default returns 'organizationalUnit' objects only)
        Get-ADSyncObjectsWithInheritanceDisabled -SearchBase 'Contoso'
 
    .EXAMPLE
        Find 'user' objects with disabled inheritance in 'Contoso' domain
        Get-ADSyncObjectsWithInheritanceDisabled -SearchBase 'Contoso' -ObjectClass 'user'
 
    .EXAMPLE
        Find all types of objects with disabled inheritance in a OU
        Get-ADSyncObjectsWithInheritanceDisabled -SearchBase OU=AzureAD,DC=Contoso,DC=com -ObjectClass '*'
#>

Function Get-ADSyncObjectsWithInheritanceDisabled
{
    [CmdletBinding()]
    Param
    (
        [Parameter(ParameterSetName='WithoutCredential', Mandatory=$True,Position=0)]
        [Parameter(ParameterSetName='WithCredential', Mandatory=$True,Position=0)]
        [String] $SearchBase,

        [Parameter(Mandatory=$False,Position=1)] 
        [String] $ObjectClass = 'organizationalUnit',

        [Parameter(ParameterSetName='WithCredential', Mandatory=$True)]
        [string] $TargetForest = $null,

        [Parameter(ParameterSetName='WithCredential', Mandatory=$True)]
        [System.Management.Automation.PSCredential] $TargetForestCredential = $null
    )

    # Function init
    [string] $functionMsg = "Get-ADSyncObjectsWithInheritanceDisabled:"

    Try
    {
        # Init a DirectorySearcher object
        If ($TargetForest)
        {
            $networkCredential = $TargetForestCredential.GetNetworkCredential()

            $directoryEntry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$TargetForest/$SearchBase", $TargetForestCredential.UserName, $networkCredential.Password)

            $ldapFilter = "(&(ObjectClass=$ObjectClass))"
            $directorySearcher = New-Object System.DirectoryServices.DirectorySearcher($directoryEntry, $ldapFilter)
            $directorySearcher.PageSize = 1000
        }
        Else
        {
            $ldapFilter = "(&(ObjectClass=$ObjectClass))"
            $directorySearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$SearchBase", $ldapFilter)
            $directorySearcher.PageSize = 1000
        }
    }
    Catch
    {
        Throw "$functionMsg Unable to search Active Directory. Error Details: $($_.Exception.Message)"
    }

    Try
    {
        # Search All Objects
        Write-Verbose "$functionMsg Searching for objects in AD with LDAP Filter: $ldapFilter"
        $allADobjects = $directorySearcher.FindAll()
    }
    Catch
    {
        Throw "$functionMsg Unable to query Active Directory. Error Details: $($_.Exception.Message)"
    }
    Finally
    {
        If ($directorySearcher -ne $null)
        {
            $directorySearcher.Dispose()
        }

        If ($directoryEntry -ne $null)
        {
            $directoryEntry.Dispose()
        }
    }

    # Check if Inheritance is Disabled
    Foreach ($object in $allADobjects) 
    {
        $object = $object.GetDirectoryEntry()
        Write-Verbose "$functionMsg $($object.DistinguishedName)"
        If ($object.ObjectSecurity.AreAccessRulesProtected -eq $True)
        {
            ResolveADobject -IdentityDN $object.DistinguishedName -IdentityParameterSet 'DistinguishedName' -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
        }
    }
}

<#
.Synopsis
   Shows permissions of a specified AD object.
.DESCRIPTION
   This function retuns all the AD permissions currently set for a given AD object provided in the parameter -ADobjectDN.
   The ADobjectDN must be provided in a DistinguishedName format.
 .PARAMETER TargetForestCredential
   Optional paramter if the target object is on a different forest than the current user context.
.EXAMPLE
   Show-ADSyncADObjectPermissions -ADobjectDN 'DC=Contoso,DC=com'
#>

Function Show-ADSyncADObjectPermissions
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$True,Position=0)] 
        [string] $ADobjectDN,

        [System.Management.Automation.PSCredential] $TargetForestCredential = $null
    )

    # Function init
    [string] $functionMsg = "Get-ADSyncADObjectPermissions:"

    # See more on dsacls.exe here: https://ss64.com/nt/dsacls.html
    $cmd = "dsacls.exe `"$ADobjectDN`""
    Write-Verbose "$functionMsg Executing command : $cmd"

    If ($TargetForestCredential)
    {
        $networkCredential = $TargetForestCredential.GetNetworkCredential()

        $domain = $networkCredential.Domain
        $user = $networkCredential.UserName
        $passwd = $networkCredential.Password

        $passwd = ReplaceReservedCharcters -PreReplace $passwd

        $result = dsacls.exe "`"$ADobjectDN`"" /domain:"`"$domain"`" /user:"`"$user"`" /passwd:"`"$passwd`""
    }
    Else
    {
        $result = dsacls.exe "`"$ADobjectDN`""
    }

    If ($lastexitcode -eq 0)
    {
        $result
    }
    Else
    {
        Throw "$functionMsg $result"
    }
}

<#
.Synopsis
   Gets the account name and domain that is configured in each AD Connector
.DESCRIPTION
   This function uses the 'Get-ADSyncConnector' cmdlet that is present in AAD Connect to retrieve from Connectivity Parameters a table showing the AD Connector(s) account.
.EXAMPLE
   Get-ADSyncADConnectorAccount
#>

Function Get-ADSyncADConnectorAccount
{
    [CmdletBinding()]
    $cmdlet = 'Get-ADSyncConnector'
    Try
    {
        Get-Command $cmdlet -ErrorAction Stop | Out-Null
    }
    Catch
    {
        Write-Error "Failure calling '$cmdlet' cmdlet. This function can only be executed with AAD Connect installed."
        Return $null
    }

    $adConnectors = Get-ADSyncConnector | where {$_.ConnectorTypeName -eq 'AD'}
    $adSyncADConnectorAccount = @()
    ForEach ($connector in $adConnectors)
    {
        $connectorAccount = "" | Select ADConnectorName, ADConnectorForest, ADConnectorAccountName, ADConnectorAccountDomain
        $connectorAccount.ADConnectorName = $connector.Name
        $connectorAccount.ADConnectorForest = ($connector.ConnectivityParameters | Where {$_.Name -eq 'forest-name'}).Value
        $connectorAccount.ADConnectorAccountName = ($connector.ConnectivityParameters | Where {$_.Name -eq 'forest-login-user'}).Value
        $connectorAccount.ADConnectorAccountDomain = ($connector.ConnectivityParameters | Where {$_.Name -eq 'forest-login-domain'}).Value
        $adSyncADConnectorAccount += $connectorAccount
    }
    $adSyncADConnectorAccount

}


# Gets an object from Active Directory based on its DN
Function Get-ADSyncADObject
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$True,Position=0)] 
        [string] $ADobjectDN,

        [Parameter(Mandatory=$True,Position=1)] 
        [string] $ErrorMsg
    )

    # Get target AD Object
    Try
    {
        Get-ADObject -SearchBase $ADobjectDN -SearchScope Base -Filter * -ErrorAction Stop
    }
    Catch [Microsoft.ActiveDirectory.Management.ADReferralException]
    {
        $ErrorMsg += "Try logging in with a Domain Administrator account in the same domain as the target object or use TargetForest/TargetForestCredential parameters. "
        Throw $ErrorMsg += "Error Details: $($_.Exception.Message)"
    }
    Catch
    {
        Throw $ErrorMsg += "Error Details: $($_.Exception.Message)"
    }

}

# Grants permissions to specified ObjectDN
Function GrantAcls
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$True,Position=0)] 
        [string] $ObjectDN,

        [Parameter(Mandatory=$True,Position=1)] 
        [string[]] $ACLs,

        [Parameter(Mandatory=$True,Position=2)] 
        [char] $InheritFlag,

        [System.Management.Automation.PSCredential] $TargetForestCredential
    )

    # See more on dsacls.exe here: https://ss64.com/nt/dsacls.html
    If ($TargetForestCredential)
    {
        $networkCredential = $TargetForestCredential.GetNetworkCredential()

        $domain = $networkCredential.Domain
        $user = $networkCredential.UserName
        $passwd = $networkCredential.Password

        If (!$domain) 
        {
            Write-Error "You must enter your Administrator credentials in a domain.com\username format."
            return
        }

        $passwd = ReplaceReservedCharcters -PreReplace $passwd
        
        $cmd = "dsacls.exe `"$ObjectDN`" /G $ACLs /I:$InheritFlag /domain:`"$domain`" /user:`"$user`""
        $cmdExecute = $cmd + " /passwd:`"$passwd`""
        $cmdTrace = $cmd + " /passwd:`"********`""
        Write-Verbose "Grant ACLs : Executing command : $cmdTrace"

        $result = Invoke-Expression $cmdExecute
    }
    Else
    {
        $cmd = "dsacls.exe `"$ObjectDN`" /G $ACLs /I:$InheritFlag"
        Write-Verbose "Grant ACLs : Executing command : $cmd"

        $result = Invoke-Expression $cmd
    }

    If ($lastexitcode -eq 0)
    {
        $result
        Write-Warning "You will need to perform a full import to include any objects that may have come in scope as a result of this operation."
    }
    Else
    {
        $noExchangeShemaError = $result | Where {$_ -like "*No GUID Found for publicFolder*"}
        If (($noExchangeShemaError | Measure-Object).Count -gt 0)
        {
            Write-Error "AD Schema for Exchange not present : $result"
        }
        Else
        {
            Write-Error "$result"
        }
    }
}

# Grants permissions to specified ObjectDN
Function GrantAclsNoInheritance
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$True,Position=0)] 
        [string] $ObjectDN,

        [Parameter(Mandatory=$True,Position=1)] 
        [string[]] $ACLs,

        [System.Management.Automation.PSCredential] $TargetForestCredential
    )

    # See more on dsacls.exe here: https://ss64.com/nt/dsacls.html
    If ($TargetForestCredential)
    {
        $networkCredential = $TargetForestCredential.GetNetworkCredential()

        $domain = $networkCredential.Domain
        $user = $networkCredential.UserName
        $passwd = $networkCredential.Password

        If (!$domain) 
        {
            Write-Error "You must enter your Administrator credentials in a domain.com\username format."
            return
        }

        $passwd = ReplaceReservedCharcters -PreReplace $passwd

        $cmd = "dsacls.exe `"$ObjectDN`" /G $ACLs /domain:`"$domain`" /user:`"$user`""
        $cmdExecute = $cmd + " /passwd:`"$passwd`""
        $cmdTrace = $cmd + " /passwd:`"********`""
        Write-Verbose "Grant ACLs without Inheritance : Executing command : $cmdTrace"

        $result = Invoke-Expression $cmdExecute
    }
    Else
    {
        $cmd = "dsacls.exe `"$ObjectDN`" /G $ACLs"
        Write-Verbose "Grant ACLs without Inheritance : Executing command : $cmd"

        $result = Invoke-Expression $cmd
    }

    If ($lastexitcode -eq 0)
    {
        $result
        Write-Warning "You will need to perform a full import to include any objects that may have come in scope as a result of this operation."
    }
    Else
    {
        Write-Error "$result"
    }

}

# Converts a FQDN to domain DistinguishedName
Function ConvertFQDNtoDN
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$True,Position=0)] 
        [String] 
        $FQDN
    )

    # Init of variable is required to prevent use of a session variable.
    $dn = ""

    # Convert each period separated domain name into a DistinguishedName
    ForEach ($domain in $($FQDN.Split($periodSeparator)))
    {
        $dn = $dn + $connector + "DC=" + $domain
        $connector = $commaSeparator
    }
    Return $dn
}

# Converts a domain DistinguishedName to FQDN
Function ConvertDNtoFQDN
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$True,Position=0)] 
        [String] 
        $DN
    )

    # Init of variable is required to prevent use of a session variable.
    $fqdn = ""

    # Convert each commma separated domain into a FQDN
    ForEach ($domain in $($DN  -split $commaSeparator))
    {
        $fqdn += "$($domain.Substring(3))."
    }
    
    # Remove the last dot
    $fqdn = $fqdn.Substring(0,$fqdn.Length -1)
    
    Return $fqdn
}

# Get list of Domains in the Forest
Function GetADDomains
{
    [CmdletBinding()]
    Param(
        [String] $TargetForest,

        [System.Management.Automation.PSCredential] $TargetForestCredential
    )

    # Function init
    [string] $functionMsg = "Get AD Domains:"
    
    Try
    {
        If ($TargetForestCredential)
        {
            Write-Verbose "$functionMsg Retrieving list of Domains in Forest '$TargetForest'..."
            $forest = Get-ADForest -Identity $TargetForest -Credential $TargetForestCredential -ErrorAction Stop
        }
        Else
        {
            Write-Verbose "$functionMsg Retrieving list of Domains in the Forest..."
            $forest = Get-ADForest -ErrorAction Stop
        }
    }
    Catch
    {
        Throw "$functionMsg Unable to get list of Domains in the Forest. Exception Details: $($_.Exception.Message)"
    }

    Return $($forest.Domains)
}


Function ResolveADobject
{
    [CmdletBinding()]
    Param
    (
        [string] $IdentityName,

        [string] $IdentityDomain,

        [string] $IdentityDN,

        [string] $IdentityParameterSet,

        [switch] $ReturnADobject,

        [string] $TargetForest,

        [System.Management.Automation.PSCredential] $TargetForestCredential
    )
    
    # Check if AD PowerShell Module is installed
    ImportADmodule

    # Init
    [string] $functionMsg = "Resolve AD object:"
    Write-Verbose "ParameterSetName: $IdentityParameterSet"

    switch ($IdentityParameterSet)
    {
        {($_ -eq 'UserDomain') -or ($_ -eq 'UserDomainWithCredential')}
        {
            # Search for AD object using object name and target domain
            Write-Verbose "$functionMsg Resolving AD object '$IdentityName' in Domain '$IdentityDomain'..."
            Try
            {   
                If ($TargetForestCredential)
                {
                    $targetDomain = Get-ADDomain -Identity $IdentityDomain -Credential $TargetForestCredential -ErrorAction Stop
                }
                Else
                {
                    $targetDomain = Get-ADDomain -Identity $IdentityDomain -ErrorAction Stop
                }
            }
            Catch
            {
                Throw "$functionMsg Unable to find Domain '$IdentityDomain' in AD Forest. Exception Details: $($_.Exception.Message)"
            }

            $ldapFilter = "(|(Name=$IdentityName)(sAMAccountName=$IdentityName))"
            $targetDomainDNSname = $targetDomain.DNSRoot
            Write-Verbose "$functionMsg LDAP Filter to search object: $ldapFilter"
            Write-Verbose "$functionMsg Target Domain to search object: $targetDomainDNSname"

            Try
            {
                If ($TargetForestCredential)
                {
                    $adObject = Get-ADObject -LDAPFilter $ldapFilter -Server $targetDomainDNSname -Properties $defaultADobjProperties -Credential $TargetForestCredential -ErrorAction Stop
                }
                Else
                {
                    $adObject = Get-ADObject -LDAPFilter $ldapFilter -Server $targetDomainDNSname -Properties $defaultADobjProperties -ErrorAction Stop
                }
            }
            Catch
            {
                Throw "$functionMsg Unable to get object '$IdentityName' in AD Forest. Exception Details: $($_.Exception.Message)"
            }
        }
        {($_ -eq 'DistinguishedName') -or ($_ -eq 'DistinguishedNameWithCredential')}
        {
            # Search for AD object using DistinguishedName against an AD Global Catalog (AD Forest wide search)
            Write-Verbose "$functionMsg Resolving AD object DN $IdentityDN..."
            # Encode reserved characters to Hex values
            $IdentityDN = Convert-DNtoHex $IdentityDN
            If ($IdentityDN -match $distinguishedNameRegex)
            {
                Write-Verbose "$functionMsg Matches in DistinguishedName input: $($Matches.Values | %{"$_;"})"
                $IdentityDomainDN = $Matches.domain
                $IdentityName = $Matches.name

                Try
                {
                    # Get an array of Domain Controllers running Global Catalog service
                    $globalCatalogSrv = @(Get-ADDomainController -Discover -DomainName $TargetForest -Service GlobalCatalog -ErrorAction Stop)
                }
                Catch
                {
                    Throw "$functionMsg Unable to find a Global Catalog DC in AD. Exception Details: $($_.Exception.Message)"
                }
    
                If (($globalCatalogSrv | Measure-Object).Count -gt 0)
                {
                    # Pick the first DC in the array and add the GC port
                    $globalCatalogHost = [string] $globalCatalogSrv[0].HostName[0].ToString() + ":3268"
                    Write-Verbose "$functionMsg Target Global Catalog Server to search object: $globalCatalogHost"
                }
                Else
                {
                    Throw "$functionMsg Could not find any available Global Catalog DC in AD."
                }

                Try
                {
                    # Get the AD object based on the DistinguishedName from a GC server in AD
                    If ($TargetForestCredential)
                    {
                        $adObject = Get-ADObject -Identity $IdentityDN -Server $globalCatalogHost -Properties $defaultADobjProperties -Credential $TargetForestCredential -ErrorAction Stop
                    }
                    Else
                    {
                        $adObject = Get-ADObject -Identity $IdentityDN -Server $globalCatalogHost -Properties $defaultADobjProperties -ErrorAction Stop
                    }
                }
                Catch
                {
                    Throw "$functionMsg Unable to get object '$IdentityDN' in AD Forest. Exception Details: $($_.Exception.Message)"
                }

                # If going to return Domain\Username, need to get the AD Domain object
                If (-not $ReturnADobject)
                {
                    $IdentityDomain = ConvertDNtoFQDN $IdentityDomainDN

                    Try
                    {
                        Write-Verbose "$functionMsg Resolving AD Domain '$IdentityDomain'..."

                        If ($TargetForestCredential)
                        {
                            $targetDomain = Get-ADDomain -Identity $IdentityDomain -Credential $TargetForestCredential -ErrorAction Stop
                        }
                        Else
                        {
                            $targetDomain = Get-ADDomain -Identity $IdentityDomain -ErrorAction Stop
                        }
                    }
                    Catch
                    {
                        Throw "$functionMsg Unable to find Domain '$IdentityDomain' in AD Forest. Exception Details: $($_.Exception.Message)"
                    }
                }
            }
            Else
            {
                Throw "$functionMsg Cannot validate argument on parameter 'ADConnectorAccountDN'. The argument '$IdentityDN' does not match a DistinguishedName format."
            }
        }
    }

    # If found, return the identity
    If ($adObject -ne $null)
    {
        If ($ReturnADobject) 
        {
            # Return AD object
            Write-Verbose "$functionMsg Returning AD object: $($adObject.Name)"
            Return $adObject
        }
        Else
        {
            # Get the domain NetBIOS name and return Domain\Username
            $targetDomainNetBIOS = $targetDomain.NetBIOSName
            $targetUsername = $adObject.sAMAccountName
            Write-Verbose "$functionMsg Returning NetBIOS Domain name: $targetDomainNetBIOS"
            Write-Verbose "$functionMsg Returning Username: $targetUsername"
            $identity = [String] $targetDomainNetBIOS + "\" + $targetUsername
            Return $identity
        }
    }
    Else
    {
        Throw "$functionMsg Unable to find '$IdentityName' in AD Forest."
    }
}

# Sets permissions on a target AD object
Function GrantADPermissionsOnADobject
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")]
    Param
    (
        # AD permission ACEs to set
        [Parameter(Mandatory=$True)] 
        [ValidateNotNullOrEmpty()] 
        [String] $ACL,

        # DistinguishedName of the target AD object to set permissions
        [Parameter(Mandatory=$True)]
        [ValidateNotNullOrEmpty()] 
        [String] $ADobjectDN,

        # Permissions description message for console output
        [String] $Message = "Grant Active Directory Permissions",

        # Set permission Inheritance for AD objects - Defaults to 'This object and subobjects'
        [String] $InheritanceType = $inheritanceThisAndSubObjects,

        [String] $TargetForest,

        [System.Management.Automation.PSCredential] $TargetForestCredential
    )

    # Function init
    ImportADmodule
    [string] $functionMsg = "Grant permissions on AD object:"
    Write-Verbose "$functionMsg AD permissions ACEs to add: $ACL"

    # Search for AD object using DistinguishedName
    $targetADObj = ResolveADobject -IdentityDN $ADobjectDN -IdentityParameterSet 'DistinguishedName' -ReturnADobject -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential
    $targetADObjClass = $targetADObj.ObjectClass
    Write-Verbose "$functionMsg Target AD Object Class: $targetADObjClass"

    # Set inheritance type
    $Inheritance = $inheritanceType

    If ($targetADObjClass -eq 'organizationalUnit' -or $targetADObjClass -eq 'container' -or $targetADObjClass -eq 'domainDNS')
    {
        # Set permissions for standard object types
        $finalACL = $ACL
    }
    Else
    {
        # Adapt ACL to target the specific AD object
        $ACEarray = $ACL -split $aclSeparator
        $targetADObjACL = @()

        # For each ACE, check if applies to an ObjectClass and if the ObjectClass matches the target AD object
        ForEach ($ace in $ACEarray)
        {
            $indexOfObjClass = $ace.LastIndexOf(";")
            If ($indexOfObjClass -lt 0)
            {
                # No ObjectClass defined in ACE - applies to all objects
                $targetADObjACL += $ace
            }
            Else
            {
                # Specific ObjectClass defined in ACE - applies only if the same as the target object
                $aceTargetClass = $ace.Substring($indexOfObjClass)
                If ($aceTargetClass.Contains($targetADObjClass))
                {
                    # Take this ACE but remove the InheritedObjectType since we will apply ACL to a non-container object
                    $targetADObjACL += $ace -replace ";$targetADObjClass", ""
                }
            }
        }

        # Finalize the filtered ACEs for the target AD object
        If (($targetADObjACL | Measure-Object).Count -eq 0)
        {
             Throw "$functionMsg Permissions are not applicable to AD ObjectClass '$targetADObjClass': $ACL"
        }
        Else
        {
            # Glue all the ACEs in a string back again separated by $aclSeparator
            $finalACL = $null
            for ($i = 0; $i -lt $targetADObjACL.Count-1; $i++)
            { 
                $finalACL += [String] $targetADObjACL[$i]+ $aclSeparator
            }
            $finalACL += [String] $targetADObjACL[$targetADObjACL.Count-1]
        }
        
        # Enclose all the ACEs in double quotes
        If ($finalACL[0] -ne '"') 
        {
            $finalACL =  '"' + $finalACL
        }
        If ($finalACL[$finalACL.Length-1] -ne '"') 
        {
            $finalACL =  $finalACL + '"'
        }
        Write-Verbose  "$functionMsg AD permissions ACEs for target AD object: $finalACL"
    }

    # Set AD Permissions on a target AD object
    If ($PSCmdlet.ShouldProcess($targetADObj.Name, $Message)) 
    {
        Write-Output "$functionMsg Setting permissions on AD object '$($targetADObj.DistinguishedName)'..."
        GrantAcls $targetADObj.DistinguishedName $finalACL $Inheritance -TargetForestCredential $TargetForestCredential
    }
    Else
    {
        Write-Verbose "$functionMsg Operation canceled."
    }
}

# Sets permissions on all Domains in the Forest
Function GrantADPermissionsOnAllDomains
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="high")]
    Param
    (
        # AD permission ACEs to set
        [Parameter(Mandatory=$True)] 
        [ValidateNotNullOrEmpty()] 
        [String] $ACL,

        # Update permissions on the AdminSdHolders container
        [Switch] $IncludeAdminSDHolders,

        # Permission Inheritance
        [String] $Inheritance = $inheritanceSubObjectsOnly,

        # Permissions description message for console output
        [String] $Message = "Grant Active Directory Permissions",

        [String] $TargetForest,

        [System.Management.Automation.PSCredential] $TargetForestCredential
    )

    # Function init
    ImportADmodule
    [string] $functionMsg = "Grant permissions on all Domains:"
    Write-Verbose "$functionMsg AD permissions ACEs to add: $ACL"

    # Get list of all Domains in the Forest
    $domains = GetADDomains -TargetForest $TargetForest -TargetForestCredential $TargetForestCredential

    # Set root permissions for all Domains in the Forest
    ForEach($domain in $domains) 
    {
        If ($PSCmdlet.ShouldProcess($domain, $Message)) 
        {
            $domainDN = ConvertFQDNtoDN -FQDN $domain
            Write-Output "$functionMsg AD Domain '$domainDN'..."
            If ($Inheritance -eq $inheritanceNone)
            {
                GrantAclsNoInheritance $domainDN $ACL -TargetForestCredential $TargetForestCredential
            }
            Else
            {
                GrantAcls $domainDN $ACL $Inheritance -TargetForestCredential $TargetForestCredential
            }

            # Check if setting permissions on AdminSdHolder container
            If ($IncludeAdminSDHolders) 
            {
                Try 
                {
                    Write-Output "$functionMsg Setting permissions on AdminSDHolder container of '$domainDN'"

                    If ($TargetForestCredential)
                    {
                        $adminSDHolderDN = (Get-ADObject -Server $domain -Filter "Name -like 'adminsdholder'" -Credential $TargetForestCredential).DistinguishedName
                    }
                    Else
                    {
                        $adminSDHolderDN = (Get-ADObject -Server $domain -Filter "Name -like 'adminsdholder'").DistinguishedName
                    }
                }
                Catch 
                {
                    Throw "$functionMsg Unable to get AdminSDHolder container of '$domainDN'. Exception details: $($_.Exception)"
                }
                # Setting permissions on AdminSdHolder container
                GrantAcls $adminSDHolderDN $ACL $Inheritance -TargetForestCredential $TargetForestCredential
            }
        }
        Else
        {
            Write-Verbose "$functionMsg Operation canceled."
        }
    }
}

Function TestCredential
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$True)] 
        [System.Management.Automation.PSCredential] $Credential
    )

    # Function init
    [string] $functionMsg = "Test Credential:"
    $networkCredential = $Credential.GetNetworkCredential()

    If ($networkCredential.UserName.Contains("@") -or $networkCredential.Domain.ToString() -eq "")
    {
        Throw [System.ArgumentException] "$functionMsg Validating credential parameter failed. Credential should use the fully qualified domain name of the administrator account. Example: CONTOSO\admin"
    }
    
    Try
    {
        # $Credential.UserName == FQDN\Username, $networkCredential.UserName == just the Username portion. We need to use the FQDN from here
        $dc = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain, $networkCredential.Domain, $Credential.UserName, $networkCredential.Password) -ErrorAction Stop
    }
    Catch
    {
        Throw [System.ArgumentException] "$functionMsg Validating credential parameter failed. Unable to use credentials to access AD. Error Details: $($_.Exception.Message)"
    }

    Try
    {
        # Validating credential
        $credentialValid = $false
        $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($dc)
        $enterpriseAdminsSid = GetEnterpriseAdminsSid $Credential
        $domainAdminsSid = GetDomainAdminsSid $Credential

        $domainDE = $domain.GetDirectoryEntry()
        $searchFilter = "(samAccountName=$($networkCredential.UserName))"
        $directorySearcher = New-Object System.DirectoryServices.DirectorySearcher($domainDE, $searchFilter)

        $searchResult = $directorySearcher.FindOne()
        If ($searchResult -eq $null)
        {
            Throw [System.ArgumentException] "$functionMsg Validating credential parameter failed. Admin account '$($Credential.UserName)' could not be found."
        }

        $adminDE = $searchResult.GetDirectoryEntry()

        [string[]] $propertyName = @("tokenGroups")
        $adminDE.RefreshCache($propertyName)
        ForEach ($resultBytes in $adminDE.Properties["tokenGroups"])
        {
            $sid = New-Object System.Security.Principal.SecurityIdentifier($resultBytes, 0)
            If ($sid.Equals($enterpriseAdminsSid) -or $sid.Equals($domainAdminsSid))
            {
                $credentialValid = $true
                Break
            }
        }

        If (!$credentialValid)
        {
            Throw [System.ArgumentException] "$functionMsg Validating credential parameter failed. Enterprise Admin or Domain Admin credential is required to restrict permissions on the account."
        }
    }
    Catch [Exception]
    {
        Throw "$functionMsg Validating credential parameter failed. Error Details: $($_.Exception.Message)"
    }
    Finally
    {
        # Clean up memory
        If ($domain -ne $null)
        {
            $domain.Dispose()
        }

        If ($domainDE -ne $null)
        {
            $domainDE.Dispose()
        }

        If ($directorySearcher -ne $null)
        {
            $directorySearcher.Dispose()
        }

        If ($adminDE -ne $null)
        {
            $adminDE.Dispose()
        }
    }
}

Function GetEnterpriseAdminsSid
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$True)] 
        [System.Management.Automation.PSCredential] $Credential
    )

    $networkCredential = $Credential.GetNetworkCredential()
    $dc = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain, $networkCredential.Domain, $Credential.UserName, $networkCredential.Password)            

    try
    {
        $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($dc)        
        try
        {
            $de = $domain.Forest.RootDomain.GetDirectoryEntry()
            $rootDomainSidInBytes = $de.Properties["ObjectSID"].Value
            $domainSid = New-Object System.Security.Principal.SecurityIdentifier($rootDomainSidInBytes, 0)
            $eaSid = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountEnterpriseAdminsSid, $domainSid)
            return $eaSid
        }
        finally
        {
            if ($de -ne $null)
            {
                $de.Dispose()
            }
        }
    }
    finally
    {
        if ($domain -ne $null)
        {
            $domain.Dispose()
        }
    }
}

Function GetDomainAdminsSid
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$True)] 
        [System.Management.Automation.PSCredential] $Credential
    )

    $networkCredential = $Credential.GetNetworkCredential()
    $dc = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain, $networkCredential.Domain, $Credential.UserName, $networkCredential.Password)

    try
    {
        $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($dc)        
        try
        {
            $de = $domain.GetDirectoryEntry()
            $domainSidInBytes = $de.Properties["ObjectSID"].Value
            $domainSid = New-Object System.Security.Principal.SecurityIdentifier($domainSidInBytes, 0)
            $domainAdminsSid = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountDomainAdminsSid, $domainSid)
            return $domainAdminsSid
        }
        finally
        {
            if ($de -ne $null)
            {
                $de.Dispose()
            }
        }
    }
    finally
    {
        if ($domain -ne $null)
        {
            $domain.Dispose()
        }
    }
}

# Convert SIDs to readable names
Function ConvertSIDtoName
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$True,Position=0)] 
        [string] $SID
    )

    $ID = New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList $sid
    $User = $ID.Translate([System.Security.Principal.NTAccount])
    $User.Value
}

# Confirm if ActiveDirectory PowerShell Module is present and load it
Function ImportADmodule
{
    [CmdletBinding()]
    Param()

    If (-not (Get-Module ActiveDirectory))
    {
        Try
        {
            # Load ActiveDirectory module
            Import-Module ActiveDirectory -ErrorAction Stop
        }
        Catch
        {

            Throw "Import-Module : Unable to import ActiveDirectory PowerShell Module. Run 'Install-WindowsFeature RSAT-AD-Tools' to install Active Directory RSAT."
        }
    }
}

# Encode reserved characters in DistinguishedName to Hexadecimal values based on MSDN documentation:
# The following table lists reserved characters that cannot be used in an attribute value without being escaped.
# From: https://msdn.microsoft.com/en-us/windows/desktop/aa366101
Function Convert-DNtoHex
{
    [CmdletBinding()]
    Param 
    (
        [Parameter(Mandatory=$True,Position=0)] [String] $DN
    )

    $encodedDN = $DN -replace '\\#','\23' `
                    -replace '\\,','\2C' `
                    -replace '\\"','\22' `
                    -replace '\\<','\3C' `
                    -replace '\\>','\3E' `
                    -replace '\\;','\3B' `
                    -replace '\\=','\3D' `
                    -replace '\\/','\2F'
    return $encodedDN
}

# Replace reserved characters for command line arguments
Function ReplaceReservedCharcters
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$True,Position=0)] [String] $PreReplace
    )    

    $postReplace = $PreReplace -replace '\"','\"'

    return $postReplace
}


# Export ADSyncConfig Module Functions
Export-ModuleMember -Function Set-ADSyncRestrictedPermissions
Export-ModuleMember -Function Set-ADSyncPasswordHashSyncPermissions
Export-ModuleMember -Function Set-ADSyncExchangeHybridPermissions
Export-ModuleMember -Function Set-ADSyncMsDsConsistencyGuidPermissions
Export-ModuleMember -Function Set-ADSyncPasswordWritebackPermissions
Export-ModuleMember -Function Set-ADSyncUnifiedGroupWritebackPermissions
Export-ModuleMember -Function Set-ADSyncExchangeMailPublicFolderPermissions
Export-ModuleMember -Function Set-ADSyncBasicReadPermissions
Export-ModuleMember -Function Get-ADSyncObjectsWithInheritanceDisabled
Export-ModuleMember -Function Get-ADSyncADConnectorAccount
Export-ModuleMember -Function Show-ADSyncADObjectPermissions

# SIG # Begin signature block
# MIInwgYJKoZIhvcNAQcCoIInszCCJ68CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC044O1QrO/mXzo
# vfAGyyj0C1vNPHfykaZ23vytrGQ046CCDY0wggYLMIID86ADAgECAhMzAAACXqZD
# 7dFf8ftHAAAAAAJeMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMzA5WhcNMjIwOTAxMTgzMzA5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQCzfDfI9jKbw9bCSGnQicvizL4xwig71vYg8uDRqmJJCfJ2x/37vUd/AMAvd459
# ldcgTp5HYW5fgEZEqxomTjtNppHLXpG0WdZyFEInEXxd7ElQ24uhiUYLaObhvBKp
# ovPuq/7KgaESBqK1EDBQIXU8oLHK8Ogge0+u8LzyxqU7vz9L0b6eup5U3xXsd6Ch
# mWLxeuOMbEuQoUBJvIVSCCWfvo68kARAbL6Ej8RJsgoRMom3/AYvJxOecqA8r3ot
# vu0rsexkJXT4YziXA+f+7vsS/GqglfmkuV9pOVMu0iTZ3vejRnOUAxF7kkQ4DOHo
# sgij+ZtE1k61huu20DNorfAJAgMBAAGjggGKMIIBhjArBgNVHSUEJDAiBgorBgEE
# AYI3TBMBBgorBgEEAYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU9DkzBncOLXhk
# OEiKpkDl6LSCzBgwUAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBP
# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzMxMTArNDY3NDk4MB8G
# A1UdIwQYMBaAFEhuZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWG
# Q2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BD
# QTIwMTFfMjAxMS0wNy0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAC
# hkVodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNp
# Z1BDQTIwMTFfMjAxMS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0B
# AQsFAAOCAgEAYd4ugkdgOHoz39d8bKUzw0Z9q0BW8Nn6J7+Q7Tszoj9j4PUzxYFP
# lAZmi8WhH/tA+BRjSrxrCEczjqRkvg81nGB6a2Kcxg1H84Q/jY2u/xhPKzIoMEqA
# GokCY1bd53Ohae88Wwo/iQ+fL0QVL9RWQ9Hf0/6+LOmAF9YOCiMP+rSImg6w5448
# bzGClo2/TTaVPL8XoyDzZFP96PInlqkhyqd7i5+z1z30EKxfuY6Vw7bqM2Jdvh8H
# ozjz1DpVbGkgS2bNOoJWWs4ORXiME+8NJSqND04F00gKbH5cof235y0TTfT8nhDJ
# nnf5qebpeEq8KHZY4mzZt5yeiy8GTei7Jn+pXv8KzLhXvFZF0uwnJHuBhQG8TiqM
# MiLd5VikZaOPZDPoWMo/cwpjQHU/J+q2z0ufYeKlnTky5ya6E/vQ/u7WCX9lEWBh
# RCb/jdVapNv3GpxBAZadtVNL1wF+A/69bn0GyMHFudmEtePWw0+2M6dZJYI6Fg0y
# Z4ZY2s1j9jT6dpfVM7JYh3QU+R5OXRVCjiuATMnCyapgmknUWrDHG6ckeF1RL6Wj
# 9uS9hRN4V5er+rEbSbgyG7SRyjIJdZ/ocXfkpaxZeSczcOEA86a3K0PLboM+63Wn
# cEhTeRQgh4wtRUoN+g3vo9TkF4GiH9oG/nRRXDJwFwLiugSl46RvOG4wggd6MIIF
# YqADAgECAgphDpDSAAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9v
# dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0y
# NjA3MDgyMTA5MDlaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIi
# MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZY
# IZ9CGypr6VpQqrgGOBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+
# lGAkbK+eSZzpaF7S35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDP
# s0S3XdjELgN1q2jzy23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJ
# KecNvqATd76UPe/74ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJ
# T4Qa8qEvWeSQOy2uM1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qc
# D60ZI4TL9LoDho33X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm
# 7GEfauEoSZ1fiOIlXdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/b
# wBWzvRvUVUvnOaEP6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKer
# jt/sW5+v/N2wZuLBl4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHt
# bcMojyyPQDdPweGFRInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70
# lrC8RqBsmNLg1oiMCwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYD
# VR0OBBYEFEhuZOVQBdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1
# AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaA
# FHItOgIxkEO5FAVO4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9j
# cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIw
# MTFfMjAxMV8wM18yMi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJo
# dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIw
# MTFfMjAxMV8wM18yMi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGD
# MD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Rv
# Y3MvcHJpbWFyeWNwcy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8A
# cABvAGwAaQBjAHkAXwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQEL
# BQADggIBAGfyhqWY4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFt
# g/6+P+gKyju/R6mj82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/Wvj
# PgcuKZvmPRul1LUdd5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvt
# aPpoLpWgKj8qa1hJYx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+Z
# KJeYTQ49C/IIidYfwzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x
# 9Cf43iw6IGmYslmJaG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3bl
# QCplo8NdUmKGwx1jNpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8U
# vmFhtfDcxhsEvt9Bxw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGb
# pT9Fdx41xtKiop96eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNo
# deav+vyL6wuA6mk7r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uo
# zKRdwaGIm1dxVk5IRcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZizCCGYcC
# AQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYG
# A1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAl6mQ+3R
# X/H7RwAAAAACXjANBglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYB
# BAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0B
# CQQxIgQgAXKNE1pS56v8h3CBQzQTgE8KxtOzUWP9Dajecn7cJPgwQgYKKwYBBAGC
# NwIBDDE0MDKgFIASAE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbTANBgkqhkiG9w0BAQEFAASCAQBbiMDXszbSR1epUSlpbtrHKrDD
# jYNTVo32gN3xQfRc5IH7De5C6gCZUUmKbQG+DaeL/3lTjd19/aGTfgYEiDWfO0p0
# UymB+nBimnfqXLob7tNCjD2CtHll2WrhPqTFW4CCj3j82Zxcsyeo+gUHuFW27d61
# XToflt1TPd0Sw8D1xLo0EhrlsynyXoPnlwn/ilEublhQgZIvX8eB+Ml8Ww0X5MAK
# ShlJUScrKCf0jL1uohEtA+qQevg+QKq5TODklrLCdWOMT7YzydbpqSMIkLVNau4n
# Zf1NPGLZwAaGqQFgTxivt2vqRYHfFzcuDJvcctefRlaw+YA6JwbfQkqcOU9UoYIX
# FTCCFxEGCisGAQQBgjcDAwExghcBMIIW/QYJKoZIhvcNAQcCoIIW7jCCFuoCAQMx
# DzANBglghkgBZQMEAgEFADCCAVgGCyqGSIb3DQEJEAEEoIIBRwSCAUMwggE/AgEB
# BgorBgEEAYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEINenI/Wi1KwfJHolCx/XKwXE
# dCFqUvxeJeBWAeaH/bW+AgZh/V29+h0YEjIwMjIwMjE4MDcyOTUwLjQ1WjAEgAIB
# 9KCB2KSB1TCB0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEt
# MCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYw
# JAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoyQUQ0LTRCOTItRkEwMTElMCMGA1UEAxMc
# TWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEWUwggcUMIIE/KADAgECAhMz
# AAABhnjlGYn4JEvMAAEAAAGGMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1l
# LVN0YW1wIFBDQSAyMDEwMB4XDTIxMTAyODE5MjczOVoXDTIzMDEyNjE5MjczOVow
# gdIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsT
# JE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMd
# VGhhbGVzIFRTUyBFU046MkFENC00QjkyLUZBMDExJTAjBgNVBAMTHE1pY3Jvc29m
# dCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
# AoICAQDAjcbZam/oHgiMB+uB8mmd0849g7Vh3z6+V+gjExbeB0INP7Mhtp+DXik6
# 7S3R6RRDHrSns9p0fg6Oeo0gTWrqV0f2e2PWAh2Xgerit0QdNnokV0TbgNJtWiqp
# H5HgjDjDcY9t9zZDeR/LIXKP4M6GYJbD8VmJNVOVPht16PIBbqv8mfh+vfEuNu+E
# hNq2vfpXLLOBDRjhavvcfeBRwuNi7SqIe60MNvr6n7IMEaYoXOc5bzBW3sP67ZUQ
# mgTomUrQSlUtm6x1LOF5y5TAlfFva7KABleWxr98eXBb1ieUGowcn6Kb0e4rlfjH
# z/kHl2S4ihfmVYaMUxsPYDou78+ZQHiErQIXkbVhpS0GswTvcMAqTKmTtISbcGUl
# fBj8atWhdZhQYQfJ+uQuTCzRGgQymggSB5tk0qqNHKdEmBHh88IqsSHASJNMBzgN
# cZyLgcc6brgRDWD9IMcwWogpVLGhRuQZt0o0oeGZqG4isDLjB72zutkmyS95lhmI
# Oa0C0G3+BCiPFtnW870LXVK2GSuaSRMwtB/1wPOVUQF67oqYdfZLN7qCCd7cjhzL
# /khQucdneszhmklzSzYqkYsdpWsRDLjH+YCfjJph+B4fcwQBaRWPL+pMOHpwMIX+
# DLPdNpAO28WcArvQuq1sS8E90Gl4Ib+GT2XSVpjPCLLIZj8eowIDAQABo4IBNjCC
# ATIwHQYDVR0OBBYEFBm2o0UD72Z0S7+HfdSEcw3rCFwuMB8GA1UdIwQYMBaAFJ+n
# FV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAl
# MjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKG
# UGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0
# JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAw
# EwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZIhvcNAQELBQADggIBAMPuZ6Eljd9M
# coLiGP7AFYHznji5omIwwgeeEr041MztWtpNjPHRT9NwsnqDHDW1HMzm67ySzAk2
# Uc2ntF52wCLC+JVBlX0AvwhtlEslPA16ELCT4FVxjaCHdkZmbHy5q09mtG57KGFN
# MPY+8VUut/CHaWIMb90Q80gdMqPv0OURw8hag4JSnunQ5EzBD3mRVqJulfz2m+OE
# +XYWbQIE7eldcmDRvJ2lDl0MNO/+pvT5ZgX+81URT8ygwRCqVRZa5cQJOrHpNrIm
# 4snq5TsrlDJORD+XbgiEaMPN/kARk6sg1jORZXI19Q6kjGcqxZME3aKOln9O6fmq
# uaj280gNPSWhuCe6Vp7Xs1oQ72iIQkkfW1Dfnd2G5GL4DTQ9HvzWJiXMXklTUOsR
# 8TI3HwJaARGL3QsqxiCFkEIONDcOImN9Rkuo414esl9yaHPn9t+bz5oBpQ+lkV4/
# SDQiid3pc2ThiJhtY8Wih9zQvBypIAu24gDLPp/d35RplmynjVTiEIigaPqGgMi5
# Tzf1uj+Zn8CARLAbEhezSBlToD7aohR7rRB0D3r3BZLO5wo6KyeD0cJJksXV2pzd
# BRrCvQLRTjXvzgqj29yQAbdqTBi5UZyzqEz9KoSGh72MfB7henzUKtMHWX34Qh26
# QJs/STLPHRZnO156IM3mt2KJBH2YEm6WMIIHcTCCBVmgAwIBAgITMwAAABXF52ue
# AptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
# aWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgz
# MjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxO
# dcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQ
# GOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq
# /XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVW
# Te/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7
# mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De
# +JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM
# 9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEz
# OUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2
# ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqv
# UAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q
# 4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcV
# AgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXS
# ZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcC
# ARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRv
# cnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1
# AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaA
# FNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9j
# cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8y
# MDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6
# Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAt
# MDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8
# qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7p
# Zmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2C
# DPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BA
# ljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJ
# eBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1
# MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz
# 138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1
# V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLB
# gqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0l
# lOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFx
# BmoQtB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IEly
# ZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVT
# TjoyQUQ0LTRCOTItRkEwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAAa7YNHNaQqWOZfJJfWSiscvh8yeggYMw
# gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUF
# AAIFAOW5p+wwIhgPMjAyMjAyMTgxMzA2NTJaGA8yMDIyMDIxOTEzMDY1MlowdDA6
# BgorBgEEAYRZCgQBMSwwKjAKAgUA5bmn7AIBADAHAgEAAgIIfTAHAgEAAgIRXzAK
# AgUA5br5bAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB
# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBABRB/HhLaqL+ZTuC
# atDtF6DnfvmQ3iju1yVBQDvcQIFvf2Dttgr/Yq0VIB1Q+BoRr4z2zsaUt/mFp9Ec
# suIbNNf65OC4evXnk3xhQpok7ZcGzETNz+PedHnLjVL9PP6d4jp375fswNXczGyo
# 8Bz5RVjuxlg8OUSFznwhi9vw6ek3MYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAGGeOUZifgkS8wAAQAAAYYwDQYJYIZIAWUD
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
# CQQxIgQgfqC0ljdFePOwqh85rh/ZEtUK1ovlTpStz35ckwQpOYEwgfoGCyqGSIb3
# DQEJEAIvMYHqMIHnMIHkMIG9BCAamYjgsiwIVMaJjJ9EBHubsVraC7FU0jDXuZwC
# KrxCfjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
# hnjlGYn4JEvMAAEAAAGGMCIEIGzdfAAI9HojUquYgz+XJ5UCJjoQEuLb0srRJJEC
# Zsy1MA0GCSqGSIb3DQEBCwUABIICAGiRsbiD3clv0vpd5fTNTKaLpWm5gxI5JDn9
# ZIyzUF+Lu/wrZM6vbPp3F0JzC5J1bLxCVPWEUhP8djCIiIU+c1D/MctsOhcfxr59
# xyuc6zUQ7xraM98rIEliN8a3OrumDvSi4ZN7cBQYAav8X79LHLHS2XRFXr+Q3sPM
# mxawn6qxjF1UTWN+dkusxKNr0pbvk8CxC5PSlNdIR8m2MCsmMEtLnmhtHglvQkob
# L9U/zPQryXa4biD9TeAEY2preM7Vmd1d8TF9D1mUOqQaqGLYCJ+XY9XOnBshdGOc
# +/zCAEeHqZBDJM2I6cNIcxTEiRmwcS7bg+pEerS3piTE5jZduOroImA4yW7W3/4D
# BZKNo2HLm+Gx2mWIqPIg+5titUlPZdIVuD0OonFPmRTy8LiYx+JwdtW/o+Ma+mHM
# +yHXcNt+FMYEShTy64huQ5KWMdQJMn44emzB3IApRLklJz1tyg5TblxW2UnRPqzU
# h8GUNJfj3LD9GDJeUghzUtVbpq2EqRF1mLInADrA3I6aI57gFgF9luYC6tGbUZrR
# Yu+84rYhXP7aNs+nuWgADNcf80+g+aCAEJzj3sd4LyCCTfmBbvU5yXXVTfZhcOhC
# RUnfGppnUVGrcu1ftHtoOxnXM8Z6FMcM/wfJlR869vGI/ValC1vuddo7Tv5LSjFk
# IUY5Dw34
# SIG # End signature block