Fixing NTFS Folder ACLs with Powershell

Background

I encountered a problem on a file server when trying to add a new permission to a group that needed to be applied to all our user home directory folders. The parent folder held the “default” permissions that should apply to all the child home directories, but for some reason, the majority of child directories were set to “not inherit” permissions. Simply applying the new ACL to the parent folder would not propagate down to each child.

At some point, the permissions had been changed to explicit ones on each home directory. Sure, I could script an ACL to apply to each directory, again, but I actually wanted to fix it so that inheritance worked for the default permissions we wanted to apply to each home dir, while ensuring the ACL that allowed the user to access their directory (the Modify ACL shown at the top in the image below) remained untouched.

As you can see in the image, all permissions are “not inherited”, although the bottom three ACLs actually apply in the parent directory. The Include inheritable permissions from this object’s parent setting is not ticked.
faulty perms

However, simply ticking the “Include inheritable permissions..” does not actually help entirely. See below! Sure, the parent ACLs (including the new one I just added) are now inherited from the parent directory. But three of them are duplicates of the explicit permissions that had been previously set! (You can’t see the user Modify permission, since it’s scrolled off the top of the window).
fixed inheritance

This is actually what we want to achieve, below. Other than the user’s Modify ACL (not inherited), all the other perms should be inherited from the parent, with no duplicates.
fully fixed perms

Syntax

This script (see final section for the full script) does the following things:

  1. Enumerates all the folders beneath the parent directory. [Lines 1-4]. These have the same names as the actual user accounts. (This is important! )
  2. Goes through each folder and checks to see if the folder’s permissions are already set to inherit – about 10% or so on my file server were correct – the more recent folders. If the Inherit flag is already set, process the next folder.
  3. If the folder is not set to inherit ACLs, set the Inherit flag at the folder level only (all subfolders and files are correctly set to inherit from their parent already – I checked).
  4. Once the flag is set, clean up the duplicate explicit ACLs, leaving only the Modify permission for the actual user account that the folder belongs to in place.

Lines 1-4 do the folder enumeration – I’m simply selecting the folders without any recursive behaviour.

In line 5, I simply set the user name to the same as folder name. If you need some other logic to find the user name, that’s where it should go.

Line 7 gets the folder ACLs and stores them in $acl. The “bad” ACLs appear as below. areaccessrulesprotected for the folder is set to True (no inheritance). All of the user ACLs have isInherited set to False.

PS> $acl = get-acl "F:\Users\SMITH"

PS> ($acl.areaccessrulesprotected)
True

PS> $acl.access

FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : BUILTIN\Administrators
IsInherited       : False
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : Modify, Synchronize
AccessControlType : Allow
IdentityReference : DOMAIN\usershare_rw
IsInherited       : False
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : Modify, Synchronize
AccessControlType : Allow
IdentityReference : DOMAIN\SMITH
IsInherited       : False
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : ReadAndExecute, Synchronize
AccessControlType : Allow
IdentityReference : DOMAIN\special_reads
IsInherited       : False
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

So in line 9, we check to see if the areaaccessrulesproperty is set to True.

If the property is True, then we construct the replacement ACL for False (you need both $isProtected set to False and $preserveInheritance set to True for it to work). Then we actually set the replacement ACL in Line 18. This has the effect of “ticking” the Allow inheritance box in the second picture above.

To clean up the pesky duplicate ACLs, I’ve nested that process inside another loop. As you can see above, each ACL has its own set of properties, so we need to loop through the whole set to make sure we capture each one to remove it if it’s not wanted, or keep the one that belongs to the actual user account DOMAIN\SMITH. Here I’m doing a simple match of the bare user account name (remember, same as the folder name) because I can’t be bothered constructing “Domain+username” – this kind of match is good enough here.

If the ACL doesn’t match the username, then I remove it from the $acl container.

Foreach($value in $access.identityReference.Value)  {
    #skip the user permission and remove non-inherited ACLs
    if ($value -notmatch $user) {
        #construct the new ACL
        $acl.RemoveAccessRule($access) |out-null
}

In Line 37, we set the actual NTFS ACL to the “cleaned” version of $acl.

At that point, the folder ACLs are set correctly and will show up as below. All the ACLs except the user’s have “IsInherited” set to True. Also, the new ACL – the one that I had set on the parent folder that hadn’t propagated – is now visible on the folder.

PS> $acl = get-acl "F:\Users\JONES"
 
PS> $acl.access
 
FileSystemRights  : Modify, Synchronize
AccessControlType : Allow
IdentityReference : DOMAIN\JONES
IsInherited       : False
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None
 
FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : DOMAIN\usershare_full
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None
 
FileSystemRights  : ReadAndExecute, Synchronize
AccessControlType : Allow
IdentityReference : DOMAIN\special_reads
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None
 
FileSystemRights  : Modify, Synchronize
AccessControlType : Allow
IdentityReference : DOMAIN\usershare_rw
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None
 
FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : BUILTIN\Administrators
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

Results

Because this was a quick and dirty job, I didn’t make any functions, nor did I output to a log file or anything “nice”.

The “write-hosts” simply output whether the “not inherited” property is set to True/False. When False, it writes the output and goes to the next directory. When True, it writes the output, the property is set to False and the result output, and then the ACLs is pruned with that result also output. These changes are shown in green.

I can’t show any error statuses, because I had none over several thousand folders, and I couldn’t be bothered simulating one.

F:\Users\JONES 'not inherited' is False
F:\Users\BLOGGS 'not inherited' is False
F:\Users\SMITH 'not inherited' is True
F:\Users\SMITH 'not inherited' set to False
F:\Users\SMITH ACL cleanup successful

Script

$DirRoot = "F:\Users"
$Folders = Get-ChildItem $DirRoot | ?{ $_.PSIsContainer } | select -expandproperty Name
 
ForEach ($F in $Folders) {
    $user = $F
    $folder = "$DirRoot\$F"
    $acl = Get-Acl $folder
    write-host $folder "'not inherited' is" ($acl.areaccessrulesprotected)
    if ($acl.areaccessrulesprotected) {
        # folder is not set to inherit perms - set it to inherit
        $isProtected = $false
        $preserveInheritance = $true
        #construct the new ACL
        $acl.SetAccessRuleProtection($isProtected, $preserveInheritance) 
        try {
            #set the ACL
            Set-Acl -Path $folder -AclObject $acl -ea Stop
            write-host $folder "'not inherited' set to" ($acl.areaccessrulesprotected) -foregroundcolor Green
            #now clean up the extra "not inherited" permissions
            Foreach($access in $acl.access)   {
                if ($access.IsInherited) {
                    #skip inherited ACLs
                    Continue
                }
                else {
                    Foreach($value in $access.identityReference.Value)  {
                        #skip the user permission and remove non-inherited ACLs
                        if ($value -notmatch $user) {
                            #construct the new ACL
                            $acl.RemoveAccessRule($access) |out-null
                        }
                    } #end foreach value
                }
            } # end foreach access
            try {
                #set the "inheritance clean-up" ACL
                Set-Acl -path $folder -aclObject $acl -ea Stop
                write-host $folder "ACL cleanup successful" -foregroundcolor Green
            }
            catch {
                write-warning $folder $_
            }
        }
        catch {
            write-warning $folder $_
        }
    }
    else {
        #folder is set to inherit perms - hopefully they're correct!
        Continue
    }
}
Advertisements
This entry was posted in WindowsServer and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s