I have been scouring the internet and several StackOverflow questions trying to puzzle this out. I may not have the best solution, but I think it satisfies the question. According to my research, Powershell's Set-Acl
just doesn't handle inheritance properly. The key to the code below is two things: the System.Security.AccessControl.DirectorySecurity
object and using the alternative method for setting the ACL $dir.SetAccessControl()
The children of the target folder (both folders and files) will successfully inherit the permissions attached to your target folder.
Calling example:
$newACL=@()
$newACL+=New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList @("MyLocalGroup1","ReadAndExecute,Synchronize","ContainerInherit,ObjectInherit","None","Allow")
$newACL+=New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList @("MyLocalGroup2","FullControl","ContainerInherit,ObjectInherit","None","Allow")
Set-FolderPermissions -Path $Path -KeepDefault -ResetOwner -AccessRuleList $newACL
Function:
function Set-FolderPermissions {
# The whole point of this script is because Set-Acl bungles inheritance
[CmdletBinding(SupportsShouldProcess=$false)]
Param ([Parameter(Mandatory=$true, ValueFromPipeline=$false)] [ValidateNotNullOrEmpty()] [string]$Path,
[Parameter(Mandatory=$false, ValueFromPipeline=$false)] [switch]$KeepExisting,
[Parameter(Mandatory=$false, ValueFromPipeline=$false)] [switch]$KeepDefault,
[Parameter(Mandatory=$false, ValueFromPipeline=$false)] [switch]$ResetOwner,
[Parameter(Mandatory=$true, ValueFromPipeline=$false)] [System.Security.AccessControl.FileSystemAccessRule[]]$AccessRuleList)
Process {
$aryDefaultACL="NT AUTHORITY\SYSTEM","CREATOR OWNER","BUILTIN\Administrators"
$tempACL=@()
$owner=New-Object System.Security.Principal.NTAccount("BUILTIN","Administrators")
$acl=Get-Acl -Path $Path
# Save only needed individual rules.
if ($KeepExisting.IsPresent) {
if ($KeepDefault.IsPresent) {
# Keep everything
$acl.Access | ForEach-Object { $tempACL+=$_ }
}
else {
# Remove the defaults, keep everything else
for ($i=0; $i -lt $acl.Access.Count; $i++) {
if (!$aryDefaultACL.Contains($acl.Access[$i].IdentityReference.Value)) { $tempACL+=$acl.Access[$i] }
}
}
}
else {
if ($KeepDefault.IsPresent) {
# Keep only the default, drop everything else
for ($i=0; $i -lt $acl.Access.Count; $i++) {
if ($aryDefaultACL.Contains($acl.Access[$i].IdentityReference.Value)) { $tempACL+=$acl.Access[$i] }
}
}
#else { # Do nothing, because $TempACL is already empty. }
}
# Add the new rules
# I could have been modifying $acl this whole time, but it turns out $tempACL=$acl doesn't work so well.
# As the rules are removed from $acl, they are also removed from $tempACL
for ($i=0; $i -lt $AccessRuleList.Count; $i++) { $tempACL+=$AccessRuleList[$i] }
# This is the object that you're looking for...
$aclDS=New-Object System.Security.AccessControl.DirectorySecurity -ArgumentList @($Path,[System.Security.AccessControl.AccessControlSections]::None)
# The object, apparently, comes with a bonus rule...
$aclDS.RemoveAccessRuleSpecific($aclDS.Access[0])
# Add the rules to our new object
for ($i=0; $i -lt $tempACL.Count; $i++) {
# I tried adding the rules directly but they didn't work. I have to re-create them.
$tempRule=New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList @($tempACL[$i].IdentityReference,$tempACL[$i].FileSystemRights,$tempACL[$i].InheritanceFlags,$tempACL[$i].PropagationFlags,$tempACL[$i].AccessControlType)
$aclDS.AddAccessRule($tempRule)
}
# This has to be done after all the rules are added, otherwise it doesn't work
$aclDS.SetAccessRuleProtection($true,$false)
if ($ResetOwner.IsPresent) {
# Often, the default owner is SYSTEM. This ownership will prevent you from making any changes.
# So, we change owner to the local Administrator
$acl.SetOwner($owner)
# We have to apply it now because we are applying our ACLs in two stages. We won't be using Set-Acl again.
Set-Acl -Path $Path -AclObject $acl
}
# Lastly, apply our ACls
$dir=Get-Item -Path $Path
$dir.SetAccessControl($aclDS)
}
}