diff --git a/Sonstiges/Remove-OrphanedFolderAclEntries.ps1 b/Sonstiges/Remove-OrphanedFolderAclEntries.ps1 new file mode 100644 index 0000000..8209e5f --- /dev/null +++ b/Sonstiges/Remove-OrphanedFolderAclEntries.ps1 @@ -0,0 +1,133 @@ +[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)