functions/acl/Remove-AdsOrphanAce.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
function Remove-AdsOrphanAce
{
<#
    .SYNOPSIS
        Removes all access rules that have an unresolveable identity.
     
    .DESCRIPTION
        Removes all access rules that have an unresolveable identity.
        This is aimed at identifying and remediating orphaned SIDs in active directory.
     
    .PARAMETER Path
        The full distinguished name to the object to clean.
     
    .PARAMETER ExcludeDomainSID
        SIDs from the specified domain SIDs will be ignored.
        Use this to safely handle one-way trust where ID resolution is impossible for some IDs.
     
    .PARAMETER IncludeDomainSID
        If specified, only unresolved identities from the specified SIDs will be listed.
        Use this to safely target only rules from your owned domains in the targeted domain.
     
    .PARAMETER Server
        The server / domain to connect to.
         
    .PARAMETER Credential
        The credentials to use for AD operations.
     
    .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:\> Get-ADObject -LDAPFillter '(objectCategory=*)' | Remove-AdsOrphanAce
     
        Purges all objects in the current domain from orphaned access rules.
#>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [string[]]
        $Path,
        
        [string[]]
        $ExcludeDomainSID,
        
        [string[]]
        $IncludeDomainSID,
        
        [string]
        $Server,
        
        [System.Management.Automation.PSCredential]
        $Credential,
        
        [switch]
        $EnableException
    )
    
    begin
    {
        $adParameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential
        Assert-ADConnection @adParameters -Cmdlet $PSCmdlet
        
        function Write-Result
        {
            [CmdletBinding()]
            param (
                [string]
                $Path,
                
                [System.DirectoryServices.ActiveDirectoryAccessRule]
                $AccessRule,
                
                [ValidateSet('Deleted', 'Failed')]
                [string]
                $Action,
                
                [System.Management.Automation.ErrorRecord]
                $ErrorRecord
            )
            
            [PSCustomObject]@{
                PSTypeName = 'ADSec.AccessRule'
                Path       = $Path
                Identity   = $AccessRule.IdentityReference
                Action       = $Action
                ADRights   = $AccessRule.ActiveDirectoryRights
                Type       = $AccessRule.AccessControlType
                ObjectType = $AccessRule.ObjectType
                InheritedOpectType = $AccessRule.InheritedObjectType
                Rule       = $AccessRule
                Error       = $ErrorRecord
            }
        }
        
        # Wrap as nested pipeline to avoid asserting connection each time
        $scriptCmd = { Get-AdsAcl @adParameters -EnableException:$EnableException }
        $getAdsAcl = $scriptCmd.GetSteppablePipeline()
        $getAdsAcl.Begin($true)
    }
    process
    {
        foreach ($pathItem in $Path)
        {
            Write-PSFMessage -Level Verbose -String 'Remove-AdsOrphanAce.Searching' -StringValues $pathItem
            try { $acl = $getAdsAcl.Process($pathItem) | Write-Output }
            catch { Stop-PSFFunction -String 'Remove-AdsOrphanAce.Read.Failed' -StringValues $pathItem -EnableException $EnableException -ErrorRecord $_ -Cmdlet $PSCmdlet -Continue }
            if (-not $acl) { Stop-PSFFunction -String 'Remove-AdsOrphanAce.Read.Failed' -StringValues $pathItem -EnableException $EnableException -Cmdlet $PSCmdlet -Continue }
            
            $rulesToPurge = foreach ($rule in $acl.Access)
            {
                if ($rule.IsInherited) { continue }
                if ($rule.IdentityReference -is [System.Security.Principal.NTAccount]) { continue }
                if ($rule.IdentityReference.AccountDomainSID.Value -in $ExcludeDomainSID) { continue }
                if ($IncludeDomainSID -and ($rule.IdentityReference.AccountDomainSID.Value -notin $IncludeDomainSID)) { continue }
                
                try { $null = $rule.IdentityReference.Translate([System.Security.Principal.NTAccount]) }
                catch
                {
                    $null = $acl.RemoveAccessRule($rule)
                    $rule
                }
            }
            if (-not $rulesToPurge)
            {
                Write-PSFMessage -Level Verbose -String 'Remove-AdsOrphanAce.NoOrphans' -StringValues $pathItem
                continue
            }
            
            Invoke-PSFProtectedCommand -ActionString 'Remove-AdsOrphanAce.Removing' -ActionStringValues ($rulesToPurge | Measure-Object).Count -Target $pathItem -ScriptBlock {
                try
                {
                    Set-ADObject @adParameters -Identity $pathItem -Replace @{ ntSecurityDescriptor = $acl } -ErrorAction Stop
                    foreach ($rule in $rulesToPurge) { Write-Result -Path $pathItem -AccessRule $rule -Action Deleted }
                }
                catch
                {
                    foreach ($rule in $rulesToPurge) { Write-Result -Path $pathItem -AccessRule $rule -Action Failed -ErrorRecord $_ }
                    throw
                }
            } -EnableException $EnableException.ToBool() -PSCmdlet $PSCmdlet -Continue
        }
    }
    end
    {
        $getAdsAcl.End()
    }
}