Public/AdTopology/Set-AdDirectoryReplication.ps1

function Set-AdDirectoryReplication {
    <#
        .SYNOPSIS
            Delegates directory replication permissions to a specified group across all naming contexts.

        .DESCRIPTION
            The Set-AdDirectoryReplication function delegates comprehensive Active Directory replication
            permissions to a specified security group. It configures multiple replication-related
            rights across all naming contexts in the forest.

            The function grants the following specific permissions:
            - Monitor Active Directory Replication
            - Replicating Directory Changes
            - Replicating Directory Changes All
            - Replicating Directory Changes In Filtered Set
            - Manage Replication Topology
            - Replication Synchronization
            - msDS-NC-Replica-Locations management

            These permissions enable the delegated group to perform essential replication tasks including
            monitoring replication health, initiating replication, and managing replication topology.

            Important Security Note: On Windows Server 2022 and newer versions, this operation may
            require additional privileges beyond Domain Admin and Enterprise Admin due to enhanced
            security controls.

        .PARAMETER Group
            Identity of the group getting the delegation. Can be specified as SamAccountName,
            DistinguishedName, ObjectGUID, or SID. The group must exist in Active Directory
            and should be dedicated to replication management.

        .PARAMETER RemoveRule
            If present, the access rules will be removed instead of being added. Use with
            caution as this affects critical replication capabilities.

        .PARAMETER Force
            If present, the function will not ask for confirmation when performing actions.
            Use with caution as this affects critical directory replication settings.

        .EXAMPLE
            Set-AdDirectoryReplication -Group "SG_ReplicationAdmins"

            Delegates comprehensive replication permissions to the group "SG_ReplicationAdmins",
            enabling them to manage and monitor Active Directory replication.

        .EXAMPLE
            Set-AdDirectoryReplication -Group "SG_ReplicationAdmins" -RemoveRule -Force

            Removes all previously delegated replication permissions from the group without
            prompting for confirmation.

        .EXAMPLE
            $Splat = @{
                Group = "SG_ReplicationAdmins"
                Force = $true
            }
            Set-AdDirectoryReplication @Splat

            Delegates replication permissions using splatting without confirmation prompts.

        .INPUTS
            System.String for the Group parameter.

        .OUTPUTS
            System.Void

        .NOTES
            Used Functions:
                Name ║ Module/Namespace
                ═══════════════════════════════════════════╬══════════════════════════════
                Set-AclConstructor4 ║ EguibarIT.DelegationPS
                Get-AttributeSchemaHashTable ║ EguibarIT.DelegationPS
                Get-ExtendedRightHashTable ║ EguibarIT.DelegationPS
                Get-AdObjectType ║ EguibarIT.DelegationPS
                Test-AdminPrivilege ║ EguibarIT.DelegationPS
                Write-Verbose ║ Microsoft.PowerShell.Utility
                Write-Error ║ Microsoft.PowerShell.Utility

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

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

        .COMPONENT
            Active Directory

        .ROLE
            Security, Replication

        .FUNCTIONALITY
            AD Replication Management, Delegation of Control
                Write-Error ║ Microsoft.PowerShell.Utility

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


    [CmdletBinding(
        SupportsShouldProcess = $true,
        ConfirmImpact = 'High'
    )]
    [OutputType([void])]

    param (
        [Parameter(Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Identity of the group getting the delegation',
            Position = 0)]
        [ValidateNotNullOrEmpty()]
        [Alias('IdentityReference', 'Identity', 'Trustee', 'GroupID')]
        $Group,

        # PARAM2 SWITCH If present, the access rule will be removed.
        [Parameter(Mandatory = $false,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'If present, the access rule will be removed.',
            Position = 1)]
        [Switch]
        $RemoveRule,

        [Parameter(Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false,
            HelpMessage = 'If present, the function will not ask for confirmation when performing actions.',
            Position = 2)]
        [Switch]
        $Force
    )

    Begin {

        Set-StrictMode -Version Latest

        # Display function header if variables exist
        if ($null -ne $Variables -and $null -ne $Variables.HeaderDelegation) {
            $txt = ($Variables.HeaderDelegation -f
                (Get-Date).ToString('dd/MMM/yyyy'),
                $MyInvocation.Mycommand,
                (Get-FunctionDisplay -HashTable $PsBoundParameters -Verbose:$False)
            )
            Write-Verbose -Message $txt
        } #end if

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

        ##############################
        # Variables Definition
        [Hashtable]$Splat = [hashtable]::New([StringComparer]::OrdinalIgnoreCase)

        # Check for administrative rights
        # Get current user and role
        $CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        #$WindowsPrincipal = New-Object System.Security.Principal.WindowsPrincipal($CurrentUser)
        $WindowsPrincipal = [System.Security.Principal.WindowsPrincipal]::New($CurrentUser)

        # Check if running as administrator
        if (-not $WindowsPrincipal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
            $ErrorMsg = 'Error: This function requires elevation. Please run PowerShell as Administrator.'
            Write-Error -Message $ErrorMsg
            throw $ErrorMsg
        } #end if

        # $Variables.GuidMap is empty. Call function to fill it up
        Write-Verbose -Message 'Variable $Variables.GuidMap is empty. Calling function to fill it up.'
        Get-AttributeSchemaHashTable

        Write-Verbose -Message 'Checking variable $Variables.ExtendedRightsMap. In case is empty a function is called to fill it up.'
        Get-ExtendedRightHashTable

        # Verify Group exist and return it as Microsoft.ActiveDirectory.Management.AdGroup
        $CurrentGroup = Get-AdObjectType -Identity $PSBoundParameters['Group']

    } #end Begin

    Process {
        try {
            # Iterate through all naming contexts
            Foreach ($CurrentContext in $Variables.namingContexts) {
                Write-Verbose -Message ('Processing naming context: {0}' -f $CurrentContext)

                ####################
                # Monitor Active Directory Replication
                <#
                    ACENumber :
                    DistinguishedName : Current Naming Context
                    IdentityReference : EguibarIT\XXX
                    ActiveDirectoryRights : ExtendedRight
                    AccessControlType : Allow
                    ObjectType : Monitor Active Directory Replication [Extended Rights]
                    InheritanceType : None
                    InheritedObjectType : GuidNULL
                    IsInherited : False
                #>

                $Splat = @{
                    Id                = $CurrentGroup
                    LDAPPath          = $CurrentContext
                    AdRight           = 'ExtendedRight'
                    AccessControlType = 'Allow'
                    ObjectType        = $Variables.ExtendedRightsMap['Monitor Active Directory Replication']
                }
                # Check if RemoveRule switch is present.
                If ($PSBoundParameters['RemoveRule']) {

                    # Add the parameter to remove the rule
                    $Splat.Add('RemoveRule', $true)

                } #end If

                If ($Force -or
                    $PSCmdlet.ShouldProcess($PSBoundParameters['Group'],
                        'Delegate the permissions to Monitor Active Directory Replication?')) {

                    Set-AclConstructor4 @Splat

                } #end If

                ####################
                # Replicating Directory Changes
                <#
                ACENumber :
                DistinguishedName : Current Naming Context
                IdentityReference : EguibarIT\XXX
                ActiveDirectoryRights : ExtendedRight
                AccessControlType : Allow
                ObjectType : Replicating Directory Changes [Extended Rights]
                InheritanceType : None
                InheritedObjectType : GuidNULL
                IsInherited : False
            #>

                $Splat = @{
                    Id                = $CurrentGroup
                    LDAPPath          = $CurrentContext
                    AdRight           = 'ExtendedRight'
                    AccessControlType = 'Allow'
                    ObjectType        = $Variables.ExtendedRightsMap['Replicating Directory Changes']
                }
                # Check if RemoveRule switch is present.
                If ($PSBoundParameters['RemoveRule']) {

                    # Add the parameter to remove the rule
                    $Splat.Add('RemoveRule', $true)

                } #end If

                If ($Force -or
                    $PSCmdlet.ShouldProcess($PSBoundParameters['Group'],
                        'Delegate the permissions to Replicating Directory Changes?')) {

                    Set-AclConstructor4 @Splat -ErrorAction Stop

                } #end If

                ####################
                # Replicating Directory Changes All
                <#
                ACENumber :
                DistinguishedName : Current Naming Context
                IdentityReference : EguibarIT\XXX
                ActiveDirectoryRights : ExtendedRight
                AccessControlType : Allow
                ObjectType : Replicating Directory Changes All [Extended Rights]
                InheritanceType : None
                InheritedObjectType : GuidNULL
                IsInherited : False
            #>

                $Splat = @{
                    Id                = $CurrentGroup
                    LDAPPath          = $CurrentContext
                    AdRight           = 'ExtendedRight'
                    AccessControlType = 'Allow'
                    ObjectType        = $Variables.ExtendedRightsMap['Replicating Directory Changes All']
                }
                # Check if RemoveRule switch is present.
                If ($PSBoundParameters['RemoveRule']) {

                    # Add the parameter to remove the rule
                    $Splat.Add('RemoveRule', $true)

                } #end If

                If ($Force -or
                    $PSCmdlet.ShouldProcess($PSBoundParameters['Group'],
                        'Delegate the permissions to Replicating Directory Changes All?')) {

                    Set-AclConstructor4 @Splat

                } #end If

                ####################
                # Replicating Directory Changes In Filtered Set
                <#
                ACENumber :
                DistinguishedName : Current Naming Context
                IdentityReference : EguibarIT\XXX
                ActiveDirectoryRights : ExtendedRight
                AccessControlType : Allow
                ObjectType : Replicating Directory Changes In Filtered Set [Extended Rights]
                InheritanceType : None
                InheritedObjectType : GuidNULL
                IsInherited : False
            #>

                $Splat = @{
                    Id                = $CurrentGroup
                    LDAPPath          = $CurrentContext
                    AdRight           = 'ExtendedRight'
                    AccessControlType = 'Allow'
                    ObjectType        = $Variables.ExtendedRightsMap['Replicating Directory Changes In Filtered Set']
                }
                # Check if RemoveRule switch is present.
                If ($PSBoundParameters['RemoveRule']) {

                    # Add the parameter to remove the rule
                    $Splat.Add('RemoveRule', $true)

                } #end If

                If ($Force -or
                    $PSCmdlet.ShouldProcess($PSBoundParameters['Group'],
                        'Delegate the permissions to Replicating Directory Changes In Filtered Set?')) {

                    Set-AclConstructor4 @Splat

                } #end If

                ####################
                # Manage Replication Topology
                <#
                ACENumber :
                DistinguishedName : Current Naming Context
                IdentityReference : EguibarIT\XXX
                ActiveDirectoryRights : ExtendedRight
                AccessControlType : Allow
                ObjectType : Manage Replication Topology [Extended Rights]
                InheritanceType : None
                InheritedObjectType : GuidNULL
                IsInherited : False
            #>

                $Splat = @{
                    Id                = $CurrentGroup
                    LDAPPath          = $CurrentContext
                    AdRight           = 'ExtendedRight'
                    AccessControlType = 'Allow'
                    ObjectType        = $Variables.ExtendedRightsMap['Manage Replication Topology']
                }
                # Check if RemoveRule switch is present.
                If ($PSBoundParameters['RemoveRule']) {

                    # Add the parameter to remove the rule
                    $Splat.Add('RemoveRule', $true)

                } #end If

                If ($Force -or
                    $PSCmdlet.ShouldProcess($PSBoundParameters['Group'],
                        'Delegate the permissions to Manage Replication Topology?')) {

                    Set-AclConstructor4 @Splat

                } #end If

                ####################
                # Replication Synchronization
                <#
                ACENumber :
                DistinguishedName : Current Naming Context
                IdentityReference : EguibarIT\XXX
                ActiveDirectoryRights : ExtendedRight
                AccessControlType : Allow
                ObjectType : Replication Synchronization [Extended Rights]
                InheritanceType : All
                InheritedObjectType : GuidNULL
                IsInherited : False
            #>

                $Splat = @{
                    Id                = $CurrentGroup
                    LDAPPath          = $CurrentContext
                    AdRight           = 'ExtendedRight'
                    AccessControlType = 'Allow'
                    ObjectType        = $Variables.ExtendedRightsMap['Replication Synchronization']
                }
                # Check if RemoveRule switch is present.
                If ($PSBoundParameters['RemoveRule']) {

                    # Add the parameter to remove the rule
                    $Splat.Add('RemoveRule', $true)

                } #end If

                If ($Force -or
                    $PSCmdlet.ShouldProcess($PSBoundParameters['Group'],
                        'Delegate the permissions to Replication Synchronization?')) {

                    Set-AclConstructor4 @Splat

                } #end If

            } #end Foreach


            $Splat = @{
                Filter      = '*'
                SearchBase  = $Variables.PartitionsContainer
                SearchScope = 'OneLevel'
                Properties  = 'name', 'nCName', 'msDS-NC-Replica-Locations'
            }
            $partitions = Get-ADObject @Splat | Select-Object name, nCName, msDS-NC-Replica-Locations

            ####################
            # Configure partitions attribute "msDS-NC-Replica-Locations"
            foreach ($part in $partitions) {
                Write-Verbose -Message "Processing partition: $($part.name)"

                If ($part.'msDS-NC-Replica-Locations') {
                    $partitionPath = 'CN={0},CN=Partitions,CN=Configuration,{1}' -f $part.name, $Variables.defaultNamingContext
                    Write-Verbose -Message ('Processing msDS-NC-Replica-Locations for {0}' -f $partitionPath)

                    #
                    $Splat = @{
                        Id                = $CurrentGroup
                        LDAPPath          = $partitionPath
                        AdRight           = 'ReadProperty'
                        AccessControlType = 'Allow'
                        ObjectType        = $Variables.GuidMap['msDS-NC-Replica-Locations']
                    }
                    # Check if RemoveRule switch is present.
                    If ($PSBoundParameters['RemoveRule']) {

                        # Add the parameter to remove the rule
                        $Splat.Add('RemoveRule', $true)

                    } #end If

                    If ($Force -or
                        $PSCmdlet.ShouldProcess($PSBoundParameters['Group'],
                            "Delegate read permissions to msDS-NC-Replica-Locations on $($part.name)?")) {

                        Set-AclConstructor4 @Splat

                    } #end If

                    $Splat = @{
                        Id                = $CurrentGroup
                        LDAPPath          = $partitionPath
                        AdRight           = 'WriteProperty'
                        AccessControlType = 'Allow'
                        ObjectType        = $Variables.GuidMap['msDS-NC-Replica-Locations']
                    }
                    # Check if RemoveRule switch is present.
                    If ($PSBoundParameters['RemoveRule']) {

                        # Add the parameter to remove the rule
                        $Splat.Add('RemoveRule', $true)

                    } #end If

                    If ($Force -or
                        $PSCmdlet.ShouldProcess($PSBoundParameters['Group'],
                            "Delegate write permissions to msDS-NC-Replica-Locations on $($part.name)?")) {

                        Set-AclConstructor4 @Splat

                    } #end If

                } #end If
            } #end Foreach
        } Catch {
            $ErrorMsg = ('Error: {0}' -f $_.Exception.Message)
            Write-Error -Message $ErrorMsg
            throw $ErrorMsg
        } Finally {
            # Cleanup code if needed
            Write-Verbose -Message 'Cleanup code executed.'
        }
    }#end Process

    End {

        if ($RemoveRule) {
            Write-Verbose ('Permissions removal process completed for group: {0}' -f $PSBoundParameters['Group'])
        } else {
            Write-Verbose ('Permissions delegation process completed for group: {0}' -f $PSBoundParameters['Group'])
        } #end If-Else

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

            $txt = ($Variables.FooterDelegation -f $MyInvocation.InvocationName,
                'delegating replication.'
            )
            Write-Verbose -Message $txt
        } #end If

    } #end End
} #end function Set-AdDirectoryReplication