[Resolved] Removing orphaned SIDs from directories

I am working with a customer to do some cleanup of their process for setting NTFS permissions on DFS shares. Currently, many of their shared folders have orphaned SIDs when looking at the security tab. I thought something quick like this would clean it up:

$Path = "P:"

    $aFolders = Get-ChildItem -Path $Path -Directory
        foreach ($folder in $aFolders) {
            $acl = Get-Acl -Path "$($Path)\$($folder.Name)"
                foreach($acc in $acl.access ) { 
                    $value = $acc.IdentityReference.Value 
                        if($value -match "S-1-5-*") { 
                            $ACL.RemoveAccessRule($acc) | Out-Null 
                            Set-Acl -Path "$($Path)\$($folder.Name)" -AclObject $acl -ErrorAction Stop 
                            Write-Host "Removed Orphans from $($Path)\$($folder.Name)" 
                        }
                }
        }

But it does not remove any of them. If I pipe the Get-Acl line out to a grid-view, I see all of the ACLs except the S-1-5* orphans. Just curious why that would be, as I thought get-acl would show me everything. And has anyone else resolved this kind of issue in another way?

P.S. I also tried the NTFSSecurity module and get the same behavior, none of the orphans are shown

I don’t have any orphan SIDs to test, but I think if you change your conditional operator -match to -like you might have better results. -Match is for regex and -like is for wildcards. I don’t think “S-1-5-*” would be the regular expression for what you are looking for. Also on line 5 and 11 of your script if you use the FullName property you wouldn’t have to do the “$($Path)$($folder.Name)”

The code you have should remove any explicitly defined ACEs that have principals that can’t be resolved. Note that just because the computer you’re currently on can’t resolve a principal’s SID to a user or group object doesn’t necessarily mean that it’s orphaned. True orhpaned ACEs do exhibit the same behavior, though, so I don’t have the perfect answer for detecting truly orphaned ACEs. I’d just recommend making a backup of any ACEs before you remove them just in case.

Back to your specific problem: is there any chance the ACEs you can’t remove are inherited? You should be able to tell that by looking at the $acc.IsInherited property. If it’s inherited, you can remove it (you have to go higher up and remove it from the parent that has it explicitly defined).

Next, you said Get-Acl doesn’t show it when you pipe it to Out-GridView? Do you mean you can see it from the terminal, but not in the OGV window? Or do you mean you can’t see it in the terminal, either, but only see it in the Explorer GUI?

Finally, while your code looks like it should work, a few suggestions:

  1. Set-Acl only needs to be called once for each folder. The whole security descriptor should be stored in the $acl variable, and you can call RemoveAccessRule() (and any other ACL modification method) multiple times, then call Set-Acl once when you’re done. So I’d move Set-Acl outside of the foreach() block (it shouldn’t hurt anything the way it’s done, but it would save some time if you try to do this on a lot of folders at once if you move it)
  2. Mike already mentioned this, but $folder is an object and should have access to the entire path of the folder. Try $folder.FullName instead of "$($Path)$($folder.Name)
  3. This code could remove non orphaned ACEs if you ever had a user whose name started with ‘S-1-5-’. That almost definitely wouldn’t happen, but it might be cleaner to test the $acc.IdentityReference type instead of the value of it’s string contents. Either of these might work (I say might because I haven’t tested them):
if ($acc.IdentityReference -is [System.Security.Principal.SecurityIdentifier]) {
    # It should have been turned into an [NtAccount] object if it could have been, so
    # this might be an orphaned ACE
}

## or ##

$CantTranslate = try {
    $NtAccount = $acc.IdentityReference.Translate([System.Security.Principal.NTAccount])
    $false
}
catch {
    $true
}
if ($CantTranslate) {
    # This computer couldn't translate the principal, so it might be orphaned
}

Thanks for the suggestions. I did some further testing on some of the folders, assigning groups and then deleting the groups from A.D. (working with a copy of prod), and my original code identified those orphans just fine. In talking to the on-site team for this customer, they had some issues in the past with the old script; techs would often realize they’d typed in the wrong group name and hit cancel, causing the script to barf after permissions had already been set on x number of folders, then delete the group. I think it may have led to some instability which contributed to what I was seeing (or not seeing, rather). I spent an hour going through the tedious task of removing these orphaned orphans manually, and have confirmed in subsequent tests that things are working as they should.

Rohn, I will definitely look at your code for how I can reduce and simplify. Thanks!

Glad you got it working. By the way, you can create orphaned ACEs without having to create temporary user/group accounts and delete them:

$SD = Get-Acl C:\path\to\folder\
$SD.AddAccessRule((
    New-Object System.Security.AccessControl.FileSystemAccessRule (
        [System.Security.Principal.SecurityIdentifier] 'S-1-5-100-1-2-3-4',  # Dummy SID
        'Read',
        'Deny'
    )
))
$SD | Set-Acl

That might make it a little easier to set up test cases to see how your code is working.