Public/AdTopology/Set-AdAclFMSOtransfer.ps1

Function Set-AdAclFMSOtransfer {
    <#
        .SYNOPSIS
            Delegate the management rights of FSMO roles.

        .DESCRIPTION
            This function delegates permissions to transfer Flexible Single Master Operations (FSMO) roles
            to a specified group. It supports:

            - All five FSMO roles (Schema, Domain Naming, Infrastructure, RID, PDC)
            - Both forest-wide and domain-specific roles
            - Adding and removing delegations
            - Progress tracking and detailed logging
            - WhatIf/Confirm support for safe execution

            The function requires Enterprise Admin rights for forest-wide roles.

        .PARAMETER Group
            Security group that will receive FSMO transfer rights. Must be a valid AD group.
            Accepts pipeline input and name or Distinguished Name format.

        .PARAMETER FSMOroles
            Array of FSMO roles to delegate. Valid values:
            - Schema (forest-wide)
            - DomainNaming (forest-wide)
            - Infrastructure (domain-specific)
            - RID (domain-specific)
            - PDC (domain-specific)

        .PARAMETER RemoveRule
            If specified, removes the delegated permissions instead of adding them.
            Use with caution as this affects FSMO management capabilities.

        .EXAMPLE
            Set-AdAclFMSOtransfer -Group "SL_FSMOadmin" -FSMOroles "Schema", "Infrastructure"

            Delegates Schema and Infrastructure FSMO transfer rights to the specified group.

        .EXAMPLE
            Set-AdAclFMSOtransfer -Group "SL_FSMOadmin" -FSMOroles "PDC" -RemoveRule

            Removes PDC FSMO transfer rights from the specified group.

        .EXAMPLE
            "SG_FSMOAdmins" | Set-AdAclFMSOtransfer -FSMOroles "RID", "PDC" -WhatIf

            Shows what changes would be made for RID and PDC role delegation.

        .OUTPUTS
            [void]

        .NOTES
            Used Functions:
                Name ║ Module
                ═════════════════════════════════════╬══════════════════════════════
                Set-AclConstructor4 ║ EguibarIT.DelegationPS
                Get-AttributeSchemaHashTable ║ EguibarIT.DelegationPS
                Get-ExtendedRightHashTable ║ EguibarIT.DelegationPS
                Get-AdObjectType ║ EguibarIT.DelegationPS
                Write-Verbose ║ Microsoft.PowerShell.Utility
                Write-Error ║ Microsoft.PowerShell.Utility
        .NOTES
            Version: 1.1
            DateModified: 24/Mar/2025
            LasModifiedBy: Vicente Rodriguez Eguibar
                vicente@eguibar.com
                Eguibar IT
                http://www.eguibarit.com

        .LINK
            https://github.com/vreguibar/EguibarIT.DelegationPS/blob/main/Public/AdTopology/Set-AdAclFMSOtransfer.ps1

        .LINK
            https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understanding-operations-masters

    #>


    [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,

        [Parameter(Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'Flexible Single Master Operations (FSMO) Roles to delegate.',
            Position = 1)]
        [ValidateSet('Schema', 'Infrastructure', 'DomainNaming', 'RID', 'PDC')]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $FSMOroles,

        # PARAM3 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 = 2)]
        [Switch]
        $RemoveRule,

        [Parameter(Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false,
            HelpMessage = 'If present, the function will not ask for confirmation when performing actions.',
            Position = 3)]
        [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)

        # $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 {
        # Process each of the FMSO roles
        foreach ($Role in $FSMOroles) {
            switch ($role) {
                # Forest wide roles
                'Schema' {
                    <#
                        Get-AclAccessRule -LDAPpath 'CN=Schema,CN=Configuration,DC=EguibarIT,DC=local' -SearchBy xxxx

                        ACENumber : 1
                        DistinguishedName : CN=Schema,CN=Configuration,DC=EguibarIT,DC=local
                        IdentityReference : EguibarIT\XXXX
                        ActiveDirectoryRights : ExtendedRight
                        AccessControlType : Allow
                        ObjectType : Change Schema Master [Extended Rights]
                        InheritanceType : None
                        InheritedObjectType : GuidNULL
                        IsInherited : False

                        ACENumber : 2
                        DistinguishedName : CN=Schema,CN=Configuration,DC=EguibarIT,DC=local
                        IdentityReference : EguibarIT\XXXX
                        ActiveDirectoryRights : WriteProperty
                        AccessControlType : Allow
                        ObjectType : fSMORoleOwner [AttributeSchema]
                        InheritanceType : None
                        InheritedObjectType : GuidNULL
                        IsInherited : False
                    #>


                    $Splat = @{
                        Id                = $CurrentGroup
                        LDAPPath          = 'CN=Schema,CN=Configuration,{0}' -f $Variables.defaultNamingContext
                        AdRight           = 'ExtendedRight'
                        AccessControlType = 'Allow'
                        ObjectType        = $Variables.ExtendedRightsMap['Change Schema Master']
                        Verbose           = $false
                    }
                    # Check if RemoveRule switch is present.
                    If ($PSBoundParameters['RemoveRule']) {

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

                    }

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

                        Set-AclConstructor4 @Splat

                    } #end If

                    $Splat = @{
                        Id                = $CurrentGroup
                        LDAPPath          = 'CN=Schema,CN=Configuration,{0}' -f $Variables.defaultNamingContext
                        AdRight           = 'WriteProperty'
                        AccessControlType = 'Allow'
                        ObjectType        = $Variables.GuidMap['fSMORoleOwner']
                        Verbose           = $false
                    }
                    # 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 transfer Schema Master?')) {

                        Set-AclConstructor4 @Splat

                    } #end If
                } #end Schema

                'DomainNaming' {
                    <#
                        Get-AclAccessRule -LDAPpath 'CN=Partitions,CN=Configuration,DC=EguibarIT,DC=local' -SearchBy xxxx

                        ACENumber : 1
                        DistinguishedName : CN=Partitions,CN=Configuration,DC=EguibarIT,DC=local
                        IdentityReference : EguibarIT\XXXX
                        ActiveDirectoryRights : WriteProperty
                        AccessControlType : Allow
                        ObjectType : fSMORoleOwner [AttributeSchema]
                        InheritanceType : None
                        InheritedObjectType : GuidNULL
                        IsInherited : False

                        ACENumber : 2
                        DistinguishedName : CN=Partitions,CN=Configuration,DC=EguibarIT,DC=local
                        IdentityReference : EguibarIT\XXXX
                        ActiveDirectoryRights : ExtendedRight
                        AccessControlType : Allow
                        ObjectType : Change Domain Master [Extended Rights]
                        InheritanceType : None
                        InheritedObjectType : GuidNULL
                        IsInherited : False
                    #>


                    $Splat = @{
                        Id                = $CurrentGroup
                        LDAPPath          = 'CN=Partitions,CN=Configuration,{0}' -f $Variables.defaultNamingContext
                        AdRight           = 'WriteProperty'
                        AccessControlType = 'Allow'
                        ObjectType        = $Variables.GuidMap['fSMORoleOwner']
                        Verbose           = $false
                    }
                    # 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 transfer Domain Naming Master?')) {

                        Set-AclConstructor4 @Splat

                    } #end If

                    $Splat = @{
                        Id                = $CurrentGroup
                        LDAPPath          = 'CN=Partitions,CN=Configuration,{0}' -f $Variables.defaultNamingContext
                        AdRight           = 'ExtendedRight'
                        AccessControlType = 'Allow'
                        ObjectType        = $Variables.ExtendedRightsMap['Change Domain Master']
                        Verbose           = $false
                    }
                    # Check if RemoveRule switch is present.
                    If ($PSBoundParameters['RemoveRule']) {


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

                    }

                    If ($Force -or
                        $PSCmdlet.ShouldProcess($PSBoundParameters['Group'], 'Delegate the permissions to transfer Domain Naming Master?')) {

                        Set-AclConstructor4 @Splat

                    } #end If
                } #end DomainNaming

                # Domain specific roles
                'Infrastructure' {
                    <#
                        Get-AclAccessRule -LDAPpath 'CN=Infrastructure,DC=EguibarIT,DC=local' -SearchBy xxxx

                        ACENumber : 1
                        DistinguishedName : CN=Infrastructure,DC=EguibarIT,DC=local
                        IdentityReference : EguibarIT\XXXX
                        ActiveDirectoryRights : WriteProperty
                        AccessControlType : Allow
                        ObjectType : fSMORoleOwner [AttributeSchema]
                        InheritanceType : None
                        InheritedObjectType : GuidNULL
                        IsInherited : False

                        ACENumber : 2
                        DistinguishedName : CN=Infrastructure,DC=EguibarIT,DC=local
                        IdentityReference : EguibarIT\XXXX
                        ActiveDirectoryRights : ExtendedRight
                        AccessControlType : Allow
                        ObjectType : Change Infrastructure Master [Extended Rights]
                        InheritanceType : None
                        InheritedObjectType : GuidNULL
                        IsInherited : False
                    #>


                    $Splat = @{
                        Id                = $CurrentGroup
                        LDAPPath          = 'CN=Infrastructure,{0}' -f $Variables.defaultNamingContext
                        AdRight           = 'WriteProperty'
                        AccessControlType = 'Allow'
                        ObjectType        = $Variables.GuidMap['fSMORoleOwner']
                        Verbose           = $false
                    }
                    # 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 transfer Infrastructure Master?')) {

                        Set-AclConstructor4 @Splat

                    } #end If

                    $Splat = @{
                        Id                = $CurrentGroup
                        LDAPPath          = 'CN=Infrastructure,{0}' -f $Variables.defaultNamingContext
                        AdRight           = 'ExtendedRight'
                        AccessControlType = 'Allow'
                        ObjectType        = $Variables.ExtendedRightsMap['Change Infrastructure Master']
                        Verbose           = $false
                    }
                    # 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 transfer Infrastructure Master?')) {

                        Set-AclConstructor4 @Splat

                    } #end If
                } #end Infrastructure

                'RID' {
                    <#
                        Get-AclAccessRule -LDAPpath 'CN=RID Manager$,CN=System,DC=EguibarIT,DC=local' -SearchBy xxxx

                        ACENumber : 1
                        DistinguishedName : CN=RID Manager$,CN=System,DC=EguibarIT,DC=local
                        IdentityReference : EguibarIT\XXXX
                        ActiveDirectoryRights : WriteProperty
                        AccessControlType : Allow
                        ObjectType : fSMORoleOwner [AttributeSchema]
                        InheritanceType : None
                        InheritedObjectType : GuidNULL
                        IsInherited : False

                        ACENumber : 2
                        DistinguishedName : CN=RID Manager$,CN=System,DC=EguibarIT,DC=local
                        IdentityReference : EguibarIT\XXXX
                        ActiveDirectoryRights : ExtendedRight
                        AccessControlType : Allow
                        ObjectType : Change Rid Master [Extended Rights]
                        InheritanceType : None
                        InheritedObjectType : GuidNULL
                        IsInherited : False
                    #>


                    $Splat = @{
                        Id                = $CurrentGroup
                        LDAPPath          = 'CN=RID Manager$,CN=System,{0}' -f $Variables.defaultNamingContext
                        AdRight           = 'WriteProperty'
                        AccessControlType = 'Allow'
                        ObjectType        = $Variables.GuidMap['fSMORoleOwner']
                        Verbose           = $false
                    }
                    # 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 transfer RID Master?')) {

                        Set-AclConstructor4 @Splat

                    } #end If

                    $Splat = @{
                        Id                = $CurrentGroup
                        LDAPPath          = 'CN=RID Manager$,CN=System,{0}' -f $Variables.defaultNamingContext
                        AdRight           = 'ExtendedRight'
                        AccessControlType = 'Allow'
                        ObjectType        = $Variables.ExtendedRightsMap['Change Rid Master']
                        Verbose           = $false
                    }
                    # 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 transfer RID Master?')) {

                        Set-AclConstructor4 @Splat

                    } #end If
                } #end RID

                'PDC' {
                    <#
                        Get-AclAccessRule -LDAPpath 'DC=EguibarIT,DC=local' -SearchBy xxxx

                        ACENumber : 1
                        DistinguishedName : DC=EguibarIT,DC=local
                        IdentityReference : EguibarIT\XXXX
                        ActiveDirectoryRights : WriteProperty
                        AccessControlType : Allow
                        ObjectType : fSMORoleOwner [AttributeSchema]
                        InheritanceType : None
                        InheritedObjectType : GuidNULL
                        IsInherited : False

                        ACENumber : 2
                        DistinguishedName : DC=EguibarIT,DC=local
                        IdentityReference : EguibarIT\XXXX
                        ActiveDirectoryRights : ExtendedRight
                        AccessControlType : Allow
                        ObjectType : Change PDC [Extended Rights]
                        InheritanceType : None
                        InheritedObjectType : GuidNULL
                        IsInherited : False
                    #>


                    $Splat = @{
                        Id                = $CurrentGroup
                        LDAPPath          = $Variables.defaultNamingContext
                        AdRight           = 'WriteProperty'
                        AccessControlType = 'Allow'
                        ObjectType        = $Variables.GuidMap['fSMORoleOwner']
                        Verbose           = $false
                    }
                    # 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 transfer PDCemulator Master?')) {

                        Set-AclConstructor4 @Splat

                    } #end If

                    $Splat = @{
                        Id                = $CurrentGroup
                        LDAPPath          = $Variables.defaultNamingContext
                        AdRight           = 'ExtendedRight'
                        AccessControlType = 'Allow'
                        ObjectType        = $Variables.ExtendedRightsMap['Change PDC']
                        Verbose           = $false
                    }
                    # 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 transfer PDCemulator Master?')) {

                        Set-AclConstructor4 @Splat

                    } #end If
                } #end PDC
            } #end Switch

            If ($PSBoundParameters['RemoveRule']) {

                Write-Verbose -Message ('
                    The right to transfer {1} role was revoked from {0}.'
 -f $PSBoundParameters['Group'], $role
                )

            } else {

                Write-Verbose -Message ('
                    {0} now has the right to transfer {1} role.'
 -f $PSBoundParameters['Group'], $role
                )

            } #end If-Else
        } #End Foreach
    } #end Process

    End {

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

            $txt = ($Variables.FooterDelegation -f $MyInvocation.InvocationName,
                'delegating FSMO role transfer.'
            )
            Write-Verbose -Message $txt
        } #end If
    } #end End
} # End Set-AdAclFMSOtransfer function