Resetting permissions on share folders (i.e. Roaming Profiles)

I’ve researched, poked and prodded this script for weeks and just can’t get it figured out yet.

Problem: User shares are all messed up due to restores gone wrong, rogue ‘admins’ changing permissions manually, etc.

Goal: Import list of usernames, looping through the list one user at a time (and folder) that will wipe out all of the explicit permissions, reapply “SYSTEM”/“Administrators”/“username” with full permissions on the parent folder then have all subfolders and files reinherit all the way down.

I have worked with icacls and takeown along with setacl, etc. I want to scrap that completely within powershell and move to the set-acl command or whatever other native powershell command will work. This will be a future plug 'n play script to be utilized on multiple customers as well so I’d like Powershell 2 to run this (unless there is some super new cool stuff that would win me over to use a newer one.

Here is what I have so far to set just the “Administrators” group with full permissions on the ‘usershare’ directory.


$username = "administrators"
$usershareParent = "\\server\shareroot\usershare\"
$Ar = new-object System.Security.AccessControl.FileSystemAccessRule ($username, "FullControl", "ContainerInherit", "ObjectInherit", "None", "Allow")
$acl = Get-ACL $usershareParent 
$acl.SetAccessRule($Ar)
set-acl -path $usershareParent -AclObject $acl

This appears to work just fine with whatever I set.

My problem then is how to set all the child items to inherit from this folder. I may need to change owner to Administrators first then set back to the ‘username’ when done.

Thank you!

I think you’re making the right choice by moving to native PowerShell to manage your permissions. There is a learning curve, but it looks like you’re already most of the way there. Once you understand how .NET/PowerShell treats this stuff, you can create your own tools around it, or at least be able to use other third party tools better.

If I could only give you one suggestion, though, it would be to not use Set-Acl (at least with the file system). There are some very bad bugs over on the Connect site associated with it. Your problems can range from it simply not working sometimes when it should from an un-elevated session, to wiping out your SACLs (the ACL that handles auditing) if you are elevated. Thankfully there’s an easy workaround to using Set-Acl, and that’s the SetAccessControl() .NET method:

$UserName = "UserName"
$UserShareParent = "\\server\shareroot\usershare"

$Acl = Get-Acl $UserShareParent

# If you don't want the parent to inherit permissions, do this:
$Acl.SetAccessRuleProtection($true, $false)
foreach ($CurrentUser in echo Administrators, SYSTEM, $UserName) {
    $Acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
        $CurrentUser,
        "FullControl",
        "ContainerInherit, ObjectInherit",
        "None",
        "Allow"
    )))
}
(Get-Item $UserShareParent).SetAccessControl($Acl)

You already had that part taken care of, but this does show how to use SetAccessControl() instead of Set-Acl.

If you want to set all of the children to inherit their permissions, that means you want to call Get-Acl on each one and check the ‘AreAccessRulesProtected’ property. If it is true, then the DACL is considered protected and inheritance is disabled. In that case, you’ll need to call the SetAccessRuleProtection() to unprotect them/enable inheritance. You’ll probably also want to make sure that any explicit entries that were added are also removed. Something like this, maybe?

$VerbosePreference = "Continue"
dir $UserShareParent -Recurse | ForEach-Object {
    Write-Verbose ("Current FSO: {0}" -f $_.FullName)
    
    $FSO = $_
    try {
        Write-Verbose "    Getting security descriptor"
        $Acl = $FSO | Get-Acl
    }
    catch {
        Write-Error ("Error getting security descriptor for '{0}': {1}" -f $FSO.FullName, $_.Exception.Message)
    }

    if ($Acl.AreAccessRulesProtected) {
        Write-Verbose "    DACL inheritance is disabled; enabling it now"
        $Acl.SetAccessRuleProtection($false, $false)
    }

    foreach ($Ace in $Acl.GetAccessRules($true, $false, [System.Security.Principal.NTAccount])) {
        Write-Verbose ("    Removing explicit ACE: {0} {1} {2} (IF: {3}, PF: {4})" -f $Ace.AccessControlType, $Ace.IdentityReference, $Ace.FileSystemRights, $Ace.InheritanceFlags, $Ace.PropagationFlags)
        [void] $Acl.RemoveAccessRule($Ace)
    }

    try {
        Write-Verbose "    Writing security descriptor"
        $FSO.SetAccessControl($Acl)
    }
    catch {
        Write-Error ("Error setting security descriptor for '{0}': {1}" -f $FSO.FullName, $_.Exception.Message)
    }
}

Now, that’s looks like a ton of stuff to do for this simple task (because it is). Most of the extra junk is from error handling. Without it, the loop is going to stop at the first error, and you should expect errors when checking an unknown number of files and folders. If you find yourself doing this a lot, you can create your own advanced functions to do this, or you can use other modules (shameless plug: I’ve got one that’s under development available on the TechNet Script repository and GitHub; there’s a demo of it here)

If you can, give the code above a look and let me know if you have any questions.

Wow, beautiful! Seems to work like a charm! So I’m guessing that setting owner to administrators first is irreverent? I do need to set owner to the $userShareParent and below to the $username user.

I was able to add $acl.SetOwner([system.security.principal.ntaccount] $userName) in the foreach loop for the parent but can’t get it to work for the subdirectories and files.

Is there something a little simpler (probably not) to set the owner to the $username user within the foreach-object ?

Thank you,
Dan

Addition:

Looks like this is effective…

$UserName = “username”
$UserShareParent = “\server\shareroot\usershare”

$acct1 = New-Object System.Security.Principal.NTAccount($username)
$profilefolder = Get-Item -path $usershareparent
$acl1 = $profilefolder.GetAccessControl()
$acl1.SetOwner($acct1)
dir -r $usershareParent | set-acl -aclobject $acl1

Any problems with that?

Be careful with that final call to Set-Acl. That type of command is useful when you’re trying to reset permissions and the reference security descriptor only inherits ACEs. If the reference SD has explicit ACEs, though, every child item is going to have an explicit ACE, too. If that’s what you’re looking to do, and if you’re using PowerShell version 5, then you should be fine (just remember that Set-Acl can wipe out SACLs without warning)

Changing the owner in PowerShell can be a little bit tricky. There are two scenarios:

  1. You want to “take ownership” by assigning yourself as the owner
  2. You want to assign a different principal other than yourself as the owner

Both require their own privileges to be granted and enabled. #1 requires a privilege called ‘SeTakeOwnership’, and #2 needs the ‘SeRestorePrivilege’. 99% of the time, you need to be an administrator to have those privileges granted. Having them granted isn’t enough to use them, though. For that, they have to be enabled. I mention that because .NET and PowerShell don’t always enable the privileges necessary to change the owner.

I’m pretty sure that in previous versions of PowerShell, Set-Acl wouldn’t let you set the owner to other principals. I just tried it in PSv5, though, and I was able to take ownership and set the owner to a different user. I’m not sure if this is new to version 5 or if it was fixed in a version before that…

The .NET Framework will let you take ownership with SetAccessControl(), but it will complain if you try to set the owner to something else (at least in .NET 4 with PS 5). If you want to stick with native .NET/PowerShell methods to do this, there is one way to workaround .NET not enabling the privileges for you (PS remoting must be enabled): you can use Invoke-Command with a -ComputerName parameter (even if you’re running against the local computer). That creates a session where all granted privileges are enabled. You can try something like this:

Invoke-Command -ComputerName $env:COMPUTERNAME -ScriptBlock {

    $UserName = "username"
    $UserShareParent = "\\server\shareroot\usershare"

    $(
        Get-Item -Path $UserShareParent
        Get-ChildItem -Path $UserShareParent -Recurse
    ) | ForEach-Object {

        $acl1 = $_.GetAccessControl()
        $acl1.SetOwner([System.Security.Principal.NTAccount] $UserName)
        $_.SetAccessControl($acl1)
    }
}