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?
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)"
}
}
}