[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
param(
    [Parameter(Mandatory = $true, Position = 0)]
    [ValidateNotNullOrEmpty()]
    [string]$Path
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

function Test-OrphanedSid {
    param(
        [Parameter(Mandatory = $true)]
        [System.Security.Principal.SecurityIdentifier]$Sid
    )

    try {
        [void]$Sid.Translate([System.Security.Principal.NTAccount])
        return $false
    }
    catch [System.Security.Principal.IdentityNotMappedException] {
        return $true
    }
}

function Get-TargetDirectories {
    param(
        [Parameter(Mandatory = $true)]
        [string]$RootPath
    )

    $rootItem = Get-Item -LiteralPath $RootPath
    if (-not $rootItem.PSIsContainer) {
        throw "Der Pfad '$RootPath' ist kein Ordner."
    }

    $directories = New-Object System.Collections.Generic.List[System.IO.DirectoryInfo]
    $directories.Add([System.IO.DirectoryInfo]$rootItem)

    foreach ($directory in Get-ChildItem -LiteralPath $RootPath -Directory -Recurse -Force) {
        if (($directory.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
            Write-Warning "Ueberspringe Reparse-Point: $($directory.FullName)"
            continue
        }

        $directories.Add([System.IO.DirectoryInfo]$directory)
    }

    return $directories
}

function Remove-OrphanedAclEntriesFromDirectory {
    param(
        [Parameter(Mandatory = $true)]
        [System.IO.DirectoryInfo]$Directory
    )

    $acl = Get-Acl -LiteralPath $Directory.FullName
    $rules = $acl.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier])

    $orphanedRules = @()
    foreach ($rule in $rules) {
        $sid = [System.Security.Principal.SecurityIdentifier]$rule.IdentityReference
        if (Test-OrphanedSid -Sid $sid) {
            $orphanedRules += $rule
        }
    }

    if ($orphanedRules.Count -eq 0) {
        return [pscustomobject]@{
            Path              = $Directory.FullName
            CheckedRuleCount  = $rules.Count
            RemovedRuleCount  = 0
            RemovedIdentities = @()
            Changed           = $false
        }
    }

    $removedIdentities = New-Object System.Collections.Generic.List[string]

    if ($PSCmdlet.ShouldProcess($Directory.FullName, "Remove $($orphanedRules.Count) orphaned ACL entries")) {
        foreach ($rule in $orphanedRules) {
            [void]$acl.RemoveAccessRuleSpecific($rule)
            $removedIdentities.Add($rule.IdentityReference.Value)
        }

        Set-Acl -LiteralPath $Directory.FullName -AclObject $acl
    }

    return [pscustomobject]@{
        Path              = $Directory.FullName
        CheckedRuleCount  = $rules.Count
        RemovedRuleCount  = $orphanedRules.Count
        RemovedIdentities = $removedIdentities.ToArray()
        Changed           = $orphanedRules.Count -gt 0
    }
}

$resolvedPath = (Resolve-Path -LiteralPath $Path).Path
$results = New-Object System.Collections.Generic.List[object]
$errorCount = 0

foreach ($directory in Get-TargetDirectories -RootPath $resolvedPath) {
    try {
        $result = Remove-OrphanedAclEntriesFromDirectory -Directory $directory
        $results.Add($result)

        if ($result.RemovedRuleCount -gt 0) {
            Write-Host ("[{0}] {1} verwaiste ACL-Eintraege in {2}" -f ($(if ($WhatIfPreference) { 'WHATIF' } else { 'OK' })), $result.RemovedRuleCount, $result.Path)
            foreach ($identity in $result.RemovedIdentities) {
                Write-Host ("  - {0}" -f $identity)
            }
        }
    }
    catch {
        $errorCount++
        Write-Warning ("Fehler bei {0}: {1}" -f $directory.FullName, $_.Exception.Message)
    }
}

$checkedDirectories = $results.Count
$changedDirectories = @($results | Where-Object { $_.Changed }).Count
$removedRules = ($results | Measure-Object -Property RemovedRuleCount -Sum).Sum
if ($null -eq $removedRules) {
    $removedRules = 0
}

Write-Host ''
Write-Host 'Zusammenfassung'
Write-Host ('Gepruefte Ordner : {0}' -f $checkedDirectories)
Write-Host ('Geaenderte Ordner: {0}' -f $changedDirectories)
Write-Host ('Entfernte ACEs   : {0}' -f $removedRules)
Write-Host ('Fehler           : {0}' -f $errorCount)
