functions/schemadefaultpermissions/Invoke-FMSchemaDefaultPermission.ps1

function Invoke-FMSchemaDefaultPermission
{
<#
    .SYNOPSIS
        Brings the target forest into compliance with the defined default permissions in its schema.
     
    .DESCRIPTION
        Brings the target forest into compliance with the defined default permissions in its schema.
     
        Use the module's configuration settings to govern schema admin credentials.
        The configuration can be read with Get-PSFConfig and updated with Set-PSFConfig.
     
    .PARAMETER InputObject
        Test results from Test-FMSchemaDefaultPermission to apply.
     
    .PARAMETER Server
        The server / domain to work with.
         
    .PARAMETER Credential
        The credentials to use for this operation.
 
    .PARAMETER EnableException
        This parameters disables user-friendly warnings and enables the throwing of exceptions.
        This is less user friendly, but allows catching exceptions in calling scripts.
     
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
     
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
     
    .EXAMPLE
        PS C:\> Invoke-FMSchemaDefaultPermission -Server contoso.com
     
        Brings the contoso.com forest into compliance with the defined default permissions in its schema.
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseUsingScopeModifierInNewRunspaces", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    Param (
        [Parameter(ValueFromPipeline = $true)]
        $InputObject,
        
        [PSFComputer]
        $Server,
        
        [PSCredential]
        $Credential,
        
        [switch]
        $EnableException
    )
    
    begin
    {
        #region Utility Functions
        function Add-AccessRule
        {
            [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseUsingScopeModifierInNewRunspaces", "")]
            [CmdletBinding()]
            param (
                $Change,
                
                $Session,
                
                [Hashtable]
                $Tracking
            )
            
            Invoke-Command -Session $Session -ArgumentList $Change -ScriptBlock {
                param ($Change)
                
                $rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
                    [System.Security.Principal.SecurityIdentifier]$Change.Configuration.Principal,
                    $Change.Configuration.ActiveDirectoryRights,
                    $Change.Configuration.AccessControlType,
                    $Change.Configuration.ObjectTypeGuid,
                    $Change.Configuration.InheritanceType,
                    $Change.Configuration.InheritedObjectTypeGuid
                )
                $null = $acl.AddAccessRule($rule)
            } -ErrorAction Stop
            $Tracking[$Change] = $Change
        }
        
        function Remove-AccessRule
        {
            [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseUsingScopeModifierInNewRunspaces", "")]
            [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
            [CmdletBinding()]
            param (
                $Change,
                
                $Session,
                
                [Hashtable]
                $Tracking
            )
            
            Invoke-Command -Session $Session -ArgumentList $Change -ScriptBlock {
                param ($Change)
                $rules = $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])
                foreach ($rule in $rules)
                {
                    if ($rule.ActiveDirectoryRights -ne $Change.ADObject.ActiveDirectoryRights) { continue }
                    if ($rule.InheritanceType -ne $Change.ADObject.InheritanceType) { continue }
                    if ($rule.ObjectType -ne $Change.ADObject.ObjectType) { continue }
                    if ($rule.InheritedObjectType -ne $Change.ADObject.InheritedObjectType) { continue }
                    if ($rule.AccessControlType -ne $Change.ADObject.AccessControlType) { continue }
                    if ("$($rule.IdentityReference)" -ne "$($Change.ADObject.IdentityReference)") { continue }
                    $null = $acl.RemoveAccessRule($rule)
                }
            } -ErrorAction Stop
            $Tracking[$Change] = $Change
        }
        
        function Write-SchemaDefaultPermission
        {
            [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseUsingScopeModifierInNewRunspaces", "")]
            [CmdletBinding()]
            param (
                $Session,
                
                [hashtable]
                $SchemaParameters
            )
            
            $newSddl, $schemaObjectDN = Invoke-Command -Session $Session -ScriptBlock { $acl.Sddl, $schemaObject.DistinguishedName }
            
            Set-ADObject @SchemaParameters -Identity $schemaObjectDN -Replace @{ defaultSecurityDescriptor = $newSddl } -ErrorAction Stop
        }
        #endregion Utility Functions
        
        $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential
        $parameters['Debug'] = $false
        Assert-ADConnection @parameters -Cmdlet $PSCmdlet
        Invoke-Callback @parameters -Cmdlet $PSCmdlet
        Assert-Configuration -Type SchemaDefaultPermissions -Cmdlet $PSCmdlet
        try { $rootDSE = Get-ADRootDSE @parameters -ErrorAction Stop }
        catch
        {
            Stop-PSFFunction -String 'Invoke-FMSchemaDefaultPermission.Connect.Failed' -StringValues $Server -ErrorRecord $_ -EnableException $EnableException -Exception $_.Exception.GetBaseException()
            return
        }
        $forest = Get-ADForest @parameters
        $parameters["Server"] = $forest.SchemaMaster
        #region WinRM
        Invoke-PSFProtectedCommand -ActionString 'Invoke-FMSchemaDefaultPermission.WinRM.Connect' -Target $forest.SchemaMaster -ScriptBlock {
            $psParameters = $parameters.Clone()
            $psParameters.Remove('Server')
            $psParameters.ComputerName = $forest.SchemaMaster
            $session = New-PSSession @psParameters -ErrorAction Stop
        } -EnableException $EnableException -PSCmdlet $PSCmdlet -WhatIf:$false -Confirm:$false
        #endregion WinRM
        
        #region Resolve Credentials
        $cred = $null
        $schemaParameters = $parameters.Clone()
        Invoke-PSFProtectedCommand -ActionString 'Invoke-FMSchemaDefaultPermission.Schema.Credentials' -Target $forest.SchemaMaster -ScriptBlock {
            [PSCredential]$cred = Get-SchemaAdminCredential @parameters | Write-Output | Select-Object -First 1
            if ($cred) { $schemaParameters['Credential'] = $cred }
        } -EnableException $EnableException -PSCmdlet $PSCmdlet
        if (Test-PSFFunctionInterrupt) { return }
        $null = Invoke-PSFProtectedCommand -ActionString 'Invoke-FMSchemaDefaultPermission.Credentials.Test' -Target $forest.SchemaMaster -ScriptBlock {
            $null = Get-ADDomain @schemaParameters -ErrorAction Stop
        } -EnableException $EnableException -PSCmdlet $PSCmdlet -RetryCount 5 -RetryWait 1
        if (Test-PSFFunctionInterrupt) { return }
        #endregion Resolve Credentials
    }
    process
    {
        if (Test-PSFFunctionInterrupt) { return }
        
        if (-not $InputObject)
        {
            $InputObject = Test-FMSchemaDefaultPermission @parameters -EnableException:$EnableException
        }
        
        foreach ($testItem in $InputObject)
        {
            # Catch invalid input - can only process test results
            if ($testItem.PSObject.TypeNames -notcontains 'ForestManagement.SchemaDefaultPermission.TestResult')
            {
                Stop-PSFFunction -String 'General.Invalid.Input' -StringValues 'Test-FMSchemaDefaultPermission', $testItem -Target $testItem -Continue -EnableException $EnableException
            }
            
            switch ($testItem.Type)
            {
                'Update'
                {
                    #region Load Acl from SDDL
                    Invoke-Command -Session $session -ArgumentList $rootDSE.schemaNamingContext, $testItem.Identity -ScriptBlock {
                        param ($SchemaNC, $ClassName)
                        $schemaObject = Get-ADObject -Server localhost -SearchBase $SchemaNC -LDAPFilter "(name=$ClassName)" -Properties defaultSecurityDescriptor
                        $acl = New-Object System.DirectoryServices.ActiveDirectorySecurity
                        $acl.SetSecurityDescriptorSddlForm($schemaObject.defaultSecurityDescriptor)
                    }
                    #endregion Load Acl from SDDL
                    
                    #region Apply individual changes to in-memory ACL
                    $tracking = @{ }
                    # Apply remove changes
                    foreach ($change in $testItem.Changed)
                    {
                        if ($change.Type -ne 'Remove') { continue }
                        
                        Invoke-PSFProtectedCommand -ActionString 'Invoke-FMSchemaDefaultPermission.AccessRule.Remove' -ActionStringValues $change.Identity, $change.Privilege, $change.Access -Target $testItem -ScriptBlock {
                            Remove-AccessRule -Change $change -Session $session -Tracking $tracking -ErrorAction Stop
                        } -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue -Tag invoke
                        
                    }
                    
                    # Apply add changes
                    foreach ($change in $testItem.Changed)
                    {
                        if ($change.Type -ne 'Add') { continue }
                        
                        Invoke-PSFProtectedCommand -ActionString 'Invoke-FMSchemaDefaultPermission.AccessRule.Add' -ActionStringValues $change.Identity, $change.Privilege, $change.Access -Target $testItem -ScriptBlock {
                            Add-AccessRule -Change $change -Session $session -Tracking $tracking -ErrorAction Stop
                        } -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue -Tag invoke
                    }
                    #endregion Apply individual changes to in-memory ACL
                    
                    # Write SDDL back to schema object
                    Invoke-PSFProtectedCommand -ActionString 'Invoke-FMSchemaDefaultPermission.Permissions.Update' -ActionStringValues $tracking.Count, $testItem.Changed.Count, $testItem.Identity -Target $testItem -ScriptBlock {
                        Write-SchemaDefaultPermission -Session $session -SchemaParameters $schemaParameters -ErrorAction Stop
                    } -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue -Tag invoke
                }
                'NotFound'
                {
                    Write-PSFMessage -Level Warning -String 'Invoke-FMSchemaDefaultPermission.NotFound' -StringValues $testItem.Identity -Target $testItem
                }
                'IdentityError'
                {
                    Write-PSFMessage -Level Warning -String 'Invoke-FMSchemaDefaultPermission.IdentityError' -StringValues $testItem.Identity -Target $testItem
                }
            }
        }
    }
    end
    {
        if ($session) { Remove-PSSession -Session $session -ErrorAction Ignore -WhatIf:$false -Confirm:$false }
        
        if (Test-PSFFunctionInterrupt) { return }
        
        Invoke-PSFProtectedCommand -ActionString 'Invoke-FMSchemaDefaultPermission.Schema.Credentials.Release' -Target $forest.SchemaMaster -ScriptBlock {
            $null = Remove-SchemaAdminCredential @parameters -ErrorAction Stop
        } -EnableException $EnableException.ToBool() -PSCmdlet $PSCmdlet
    }
}