powershellpermissionsntfsicacls

Powershell add administrator access to all files and folders under specific folder


There are a bunch of similar posts to this, but none of them seem to have the same issue that I have:

I'm trying to automate a script in Powershell that utilises both icacls and takeown to add an administrator group with Full Control to everything under that folder. This is an environment with about 150 TB of data, and this needs to run on thousands of folders so using the GUI is not an option.

Goal: Add Domain Admins (or any other domain group) with Full Control for all objects/containers under a specific folder.

Here's the code so far:

$Permission = "(OI)(CI)F"
$Username = "Domain Admins"
$GrantString = $Username + ":" + $Permission

foreach ($Folder in $FoldersToFix) {
    try {
        icacls ""$Folder"" /C /Q /grant ""$GrantString"" /T
        Write-ToMyLog "Success: $Folder"
    }
    catch {
        Write-ToMyLog "Warning: failed to grant permission on folder $Folder (error was: $(($_.Exception -split ":")[-1].Trim())). Trying to take ownership first..."
        try {
            takeown /F ""$Folder""
        }
        catch {
            Write-ToMyLog "Failed to take ownership for folder $Folder, error message was: $_"
            continue
        }
        try {
            icacls ""$Folder"" /C /Q /grant ""$GrantString"" /T
        }
        catch {
            Write-ToMyLog "Failed to update permissions for folder $Folder, error message was: $_"
            continue
        }
    }
}

Problem I have is that the first icacls command only works on the topmost folder, it doesn't propagate to the subfolders unless I already have permission there. Both /T that's supposed to make it recursive, or just using /reset instead of /grant fail with Access Denied. If I take ownership first using takeown it seems to work better, but even that doesn't fully propagate. For example, let's say that I have the following structure without any permissions or ownership anywhere:

\\Server\Share\Folder
\\Server\Share\Folder\Subfolder
\\Server\Share\Folder\Subfolder\Leaf

The only way that I've found to make this work is if I run takeown /F "\\Server\Share\Folder" /A /R /D Y as anything else will either fail with an access denied, or just stop after the first level. While this temporarily works, I don't necessarily want to change the owner unless I have to. There are folders that need fixing nested with folders that don't need fixing, I preferably want to keep the current ownership information and permissions as intact as possible.

Next problem comes from the icacls command where the permissions are set on each object. Running it on the example above gives me a duplicate permissions entry on Subfolder, one inherited Full Control from Folder and one explicit Full Control directly on Subfolder. While it still achieves what I want, I assume this makes it very much not optimised. I'd prefer to only have one permission on the topmost folder and have that propagate through all subfolders and files. I tried to use (I)F as permission but that gives me 'Invalid parameter' error.

I don't want to take ownership where I don't need to, but I also don't want to rerun the commands on the same toplevel folder after each ownership update. Is there a way to add Full Control permissions for an admin group on a top-level folder and have that permission propagate down through the folder tree without having duplicated explicit/inherited permissions, without having to rerun the code several times, and without taking ownership unless I absolutely have to?


Solution

  • So, I found that all my issue were coming from the fact that the process itself was not properly elevated even though Powershell was running as an admin. I found this blog post that led me to this Lee Holmes blog post from 2010 about elevating privileges using Set-TokenPrivilege. From there, it became clear that using Set-Acl was the preferred option since it's native to Powershell. Here's an example of the final code that works. Before running this I elevate the prompt to have SeBackupPrivilege, SeTakeOwnershipPrivilege, and SeRestorePrivilege:

    function Set-MyPermissions {
        param(
            [System.Security.AccessControl.FileSystemSecurity]$Acl,
            [string]$Path
        )
    
        $PermissionObject = "$env:USERDOMAIN\Domain Admins",
        [System.Security.AccessControl.FileSystemRights[]]"FullControl",
        [System.Security.AccessControl.InheritanceFlags]"ContainerInherit, ObjectInherit",
        [System.Security.AccessControl.PropagationFlags]"None",
        [System.Security.AccessControl.AccessControlType]"Allow"
    
        $AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($PermissionObject)
        $Acl.SetAccessRule($AccessRule)
        Set-Acl -Path $Folder -AclObject $Acl
        return $Acl
    }
    
    function Set-MyOwner {
        param(
            [System.Security.AccessControl.FileSystemSecurity]$Acl,
            [string]$Path,
            [string]$Owner
        )
        
        $OwnerObject = New-Object System.Security.Principal.NTAccount($Owner)
        $Acl.SetOwner($OwnerObject)
        Set-Acl -Path $Path -AclObject $Acl
        return $Acl
    }
    
    foreach ( $Folder in $FoldersToFix ) {
        try {
            $Acl = Get-Acl -Path $Folder
        }
        catch {
            Write-Error "Failed to read Acl on path $Folder"
            continue
        }
        try {
            $NewAcl = Set-MyPermissions -Path $Folder -Acl $Acl
        }
        catch {
            $OriginalOwner = $Acl.Owner
            try {
                $NewAcl = Set-MyOwner -Path $Folder -Acl $Acl -Owner "Builtin\Administrators"
            }
            catch {
                throw "Failed to update owner on target path $Folder"
            }
            try {
                $NewAcl = Set-MyPermissions -Path $Folder -Acl $NewAcl
            }
            catch {
                throw "Failed to set admin permissions even when owner is Builtin\Administrators"
            }
            try {
                $RestoredAcl = Set-MyOwner -Path $Folder -Acl $NewAcl -Owner $OriginalOwner
            }
            catch {
                Write-Error "Failed to restore original owner ($OriginalOwner)"
            }
        }
    }