Reorder acl after migrating with ms web deploy

Hi Folks,

I am very new to powershell but I am trying to get my head round it. I am currently migrating websites from windows 2003 iis 6 to 2012 iis 8.5 using MS WebDeploy with include acl.

After migrating I have come across many folders whos files have permissions which are not inherited from the parent and are coming up as ‘not in cranonical order’. Obviously going to the security tab and then clicking reorder sorts out that particular file but it would be impossible to do that with each file manually so I have been looking for a PS solution. I have come across a reference which says that if you get-acl and then set-acl it will reorder the permissions on that object.

$path = C:\Path\To\Broke\ACL
$acl = Get-Acl $path
Set-Acl $path $acl

This seems to only work with one file or one folder. Can anyone help me make this work for all files in a folder or all objects in a tree?

thanks in advance.


Will this help you?

 PS C:\>$NewAcl = Get-Acl File0.txt
 PS C:\>Get-ChildItem c:\temp -Recurse -Include *.txt -Force | Set-Acl -AclObject $NewAcl

Thanks Ondrej.

Just to clarify, this will get all files and folders including text files in c:\temp and apply the same permissions to each file and folder to match the permissions on File0.txt?

This would effectively be changing the permissions on each file rather then just reordering the permissions. I could do this for files in one single folder but as permissions on the folders are different I was hoping to be able to just reorder the permissions on each file in a tree.

If anyone else has any suggestions please let me know.



Try this:

Get-ChildItem -Path C:\Path\To\Broke\ACL -Recurse -Force |
ForEach-Object {
    $_ | Get-Acl | Set-Acl

Thanks for that Dave but I don’t think I can pipe get-childitem to get-acl and then to set-acl.

when I pipe get-childitem to get-acl and then to get-member, I get the following -

TypeName: System.Security.AccessControl.FileSecurity

Name MemberType Definition

Access CodeProperty System.Security.AccessControl.AuthorizationRuleCollection Access{get=…
CentralAccessPolicyId CodeProperty System.Security.Principal.SecurityIdentifier CentralAccessPolicyId{ge…
CentralAccessPolicyName CodeProperty System.String CentralAccessPolicyName{get=GetCentralAccessPolicyName;}
Group CodeProperty System.String Group{get=GetGroup;}
Owner CodeProperty System.String Owner{get=GetOwner;}
Path CodeProperty System.String Path{get=GetPath;}
Sddl CodeProperty System.String Sddl{get=GetSddl;}
AccessRuleFactory Method System.Security.AccessControl.AccessRule AccessRuleFactory(System.Sec…
AddAccessRule Method void AddAccessRule(System.Security.AccessControl.FileSystemAccessRule…
AddAuditRule Method void AddAuditRule(System.Security.AccessControl.FileSystemAuditRule r…
AuditRuleFactory Method System.Security.AccessControl.AuditRule AuditRuleFactory(System.Secur…
Equals Method bool Equals(System.Object obj)
GetAccessRules Method System.Security.AccessControl.AuthorizationRuleCollection GetAccessRu…
GetAuditRules Method System.Security.AccessControl.AuthorizationRuleCollection GetAuditRul…
GetGroup Method System.Security.Principal.IdentityReference GetGroup(type targetType)
GetHashCode Method int GetHashCode()
GetOwner Method System.Security.Principal.IdentityReference GetOwner(type targetType)
GetSecurityDescriptorBinaryForm Method byte GetSecurityDescriptorBinaryForm()
GetSecurityDescriptorSddlForm Method string GetSecurityDescriptorSddlForm(System.Security.AccessControl.Ac…
GetType Method type GetType()
ModifyAccessRule Method bool ModifyAccessRule(System.Security.AccessControl.AccessControlModi…
ModifyAuditRule Method bool ModifyAuditRule(System.Security.AccessControl.AccessControlModif…
PurgeAccessRules Method void PurgeAccessRules(System.Security.Principal.IdentityReference ide…
PurgeAuditRules Method void PurgeAuditRules(System.Security.Principal.IdentityReference iden…
RemoveAccessRule Method bool RemoveAccessRule(System.Security.AccessControl.FileSystemAccessR…
RemoveAccessRuleAll Method void RemoveAccessRuleAll(System.Security.AccessControl.FileSystemAcce…
RemoveAccessRuleSpecific Method void RemoveAccessRuleSpecific(System.Security.AccessControl.FileSyste…
RemoveAuditRule Method bool RemoveAuditRule(System.Security.AccessControl.FileSystemAuditRul…
RemoveAuditRuleAll Method void RemoveAuditRuleAll(System.Security.AccessControl.FileSystemAudit…
RemoveAuditRuleSpecific Method void RemoveAuditRuleSpecific(System.Security.AccessControl.FileSystem…
ResetAccessRule Method void ResetAccessRule(System.Security.AccessControl.FileSystemAccessRu…
SetAccessRule Method void SetAccessRule(System.Security.AccessControl.FileSystemAccessRule…
SetAccessRuleProtection Method void SetAccessRuleProtection(bool isProtected, bool preserveInheritance)
SetAuditRule Method void SetAuditRule(System.Security.AccessControl.FileSystemAuditRule r…
SetAuditRuleProtection Method void SetAuditRuleProtection(bool isProtected, bool preserveInheritance)
SetGroup Method void SetGroup(System.Security.Principal.IdentityReference identity)
SetOwner Method void SetOwner(System.Security.Principal.IdentityReference identity)
SetSecurityDescriptorBinaryForm Method void SetSecurityDescriptorBinaryForm(byte binaryForm), void SetSecu…
SetSecurityDescriptorSddlForm Method void SetSecurityDescriptorSddlForm(string sddlForm), void SetSecurity…
ToString Method string ToString()
PSChildName NoteProperty System.String PSChildName=ESB_40s_06.jpg
PSDrive NoteProperty System.Management.Automation.PSDriveInfo PSDrive=D
PSParentPath NoteProperty System.String PSParentPath=Microsoft.PowerShell.Core\FileSystem::D:\i…
PSPath NoteProperty System.String PSPath=Microsoft.PowerShell.Core\FileSystem::D:\inetpub…
PSProvider NoteProperty System.Management.Automation.ProviderInfo PSProvider=Microsoft.PowerS…
AccessRightType Property type AccessRightType {get;}
AccessRuleType Property type AccessRuleType {get;}
AreAccessRulesCanonical Property bool AreAccessRulesCanonical {get;}
AreAccessRulesProtected Property bool AreAccessRulesProtected {get;}
AreAuditRulesCanonical Property bool AreAuditRulesCanonical {get;}
AreAuditRulesProtected Property bool AreAuditRulesProtected {get;}
AuditRuleType P roperty type AuditRuleType {get;}
AccessToString ScriptProperty System.Object AccessToString {get=$toString = “”;…
AuditToString ScriptProperty System.Object AuditToString {get=$toString = “”;…

Did you actually try it? Unless it runs into an error related to permissions, it will work.

When I run it, it returns to the prompt very quickly, no error and no desired effect.

Sounds like calling Get-Acl and Set-Acl isn’t enough to reorder the ACEs, then. I don’t have any broken permissions that I could test that on.

If you’re running Get-Member to see what cmdlets you can pass the output of Get-Acl to, it doesn’t work the way you think it will, Gaucho. Get-Member is showing you the type of .NET object that is returned by Get-Acl and all of the properties and functions associated with that .NET object. If you looked up the TypeName on MSDN, you’d see these same things listed there: the System.Security.AccessControl.FileSecurity class in this case.

In order to figure out what could be piped to Set-Acl, you’d need to run “Get-Help Set-Acl -detailed” at the PowerShell prompt and look at the input parameters available. In that output, you’ll notice an AclObject parameter. If you look a little deeper at that parameter, you’ll see that it can take pipeline input:

PS l>  get-help Set-Acl -Parameter aclobject

    Specifies an ACL with the desired property values. Set-Acl changes the ACL of item specified by the Path or
    InputObject parameter to match the values in the specified security object.

    You can save the output of a Get-Acl command in a variable and then use the AclObject parameter to pass the
    variable, or type a Get-Acl command.

    Required?                    true
    Position?                    2
    Default value
    Accept pipeline input?       true (ByValue)
    Accept wildcard characters?  false

And even better than that, take a look at the examples provided in the Set-Acl help. Examples 1 and 2 show pretty much exactly what you’re wanting to do:

PS >  get-help Set-Acl -Examples


    Changes the security descriptor of a specified item, such as a file or a registry key.

    -------------------------- EXAMPLE 1 --------------------------

    PS C:\>$DogACL = Get-Acl C:\Dog.txt
    PS C:\>Set-Acl -Path C:\Cat.txt -AclObject $DogACL

    These commands copy the values from the security descriptor of the Dog.txt file to the security descriptor of the
    Cat.txt file. When the commands complete, the security descriptors of the Dog.txt and Cat.txt files are identical.

    The first command uses the Get-Acl cmdlet to get the security descriptor of the Dog.txt file. The assignment
    operator (=) stores the security descriptor in the value of the $DogACL variable.

    The second command uses Set-Acl to change the values in the ACL of Cat.txt to the values in $DogACL.

    The value of the Path parameter is the path to the Cat.txt file. The value of the AclObject parameter is the model
    ACL, in this case, the ACL of Dog.txt as saved in the $DogACL variable.

    -------------------------- EXAMPLE 2 --------------------------

    PS C:\>Get-Acl C:\Dog.txt | Set-Acl -Path C:\Cat.txt

    This command is almost the same as the command in the previous example, except that it uses a pipeline operator to
    send the security descriptor from a Get-Aclcommand to a Set-Acl command.

    The first command uses the Get-Acl cmdlet to get the security descriptor of the Dog.txt file. The pipeline
    operator (|) passes an object that represents the Dog.txt security descriptor to the Set-Acl cmdlet.

    The second command uses Set-Acl to apply the security descriptor of  Dog.txt to Cat.txt. When the command
    completes, the ACLs of the Dog.txt and Cat.txt files are identical.

    -------------------------- EXAMPLE 3 --------------------------

    PS C:\>$NewAcl = Get-Acl File0.txt
    PS C:\>Get-ChildItem c:\temp -Recurse -Include *.txt -Force | Set-Acl -AclObject $NewAcl

    These commands apply the security descriptors in the File0.txt file to all text files in the C:\Temp directory and
    all of its subdirectories.

    The first command gets the security descriptor of the File0.txt file in the current directory and uses the
    assignment operator (=) to store it in the $NewACL variable.

    The first command in the pipeline uses the Get-ChildItem cmdlet to get all of the text files in the C:\Temp
    directory. The Recurse parameter extends the command to all subdirectories of C:\temp. The Include parameter
    limits the files retrieved to those with the ".txt" file name extension. The Force parameter gets hidden files,
    which would otherwise be excluded. (You cannot use "c:\temp\*.txt", because the Recurse parameter works on
    directories, not on files.)

    The pipeline operator (|) sends the objects representing the retrieved files to the Set-Acl cmdlet, which applies
    the security descriptor in the AclObject parameter to all of the files in the pipeline.

    In practice, it is best to use the Whatif parameter with all Set-Acl commands that can affect more than one item.
    In this case, the second command in the pipeline would be "Set-Acl -AclObject $NewAcl -WhatIf". This command lists
    the files that would be affected by the command. After reviewing the result, you can run the command again without
    the Whatif parameter.

OK thanks Dave

When I use:
$acl = get-childitem -Path C:\Path\To\Broke\ACL -Recurse -Force | Get-Acl
set-acl $acl

I get:
> set-acl $acl

cmdlet Set-Acl at command pipeline position 1
Supply values for the following parameters:

Changing how you call Set-Acl isn’t going to make a difference; if it doesn’t reorder the ACEs, then it just doesn’t do that. You’ll need to solve that problem first.

OK I will take your point Dave and will test with just one file to confirm. This was my source for the idea

thanks Dave, Charles and Ondrej for your input.

We will be having a microsoft consultant on site in the next couple of weeks so I will put my issue of multiple files and folders permissions out of order after migrating with web deploy and see if he has any solutions as reordering them manually will be impossible.

I’m on a steep learning curve with Powershell but finding it very valuable.

If I find a solution I will post it back here.

Thanks again

This can be done in PowerShell. I have a module here that has a function named ‘Repair-AclCanonicalOrder’. It’s slow, but it should work until version 4.0 is ready (just don’t try it on AD objects).

If you don’t want to use the module, you can still use some native PowerShell to try to fix this:

function RepairAclCanonicalOrder {
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
        [System.Security.AccessControl.FileSystemSecurity] $Acl

    process {
        if ($Acl.AreAccessRulesCanonical) {
            Write-Verbose "Rules are already in canonical order for '$(Convert-Path $Acl.Path)'"

        Write-Verbose "Working on '$(Convert-Path $Acl.Path)'"
        # Convert ACL to a raw security descriptor:
        $RawSD = New-Object System.Security.AccessControl.RawSecurityDescriptor($Acl.Sddl)

        # Create a new, empty DACL
        $NewDacl = New-Object System.Security.AccessControl.RawAcl(
            $RawSD.DiscretionaryAcl.Count  # Capacity of ACL

        # Put in reverse canonical order and insert each ACE (I originally had a different method that
        # preserved the order as much as it could, but that order isn't preserved later when we put this
        # back into a DirectorySecurity object, so I went with this shorter command)
        $RawSD.DiscretionaryAcl | Sort-Object @{E={$_.IsInherited}; Descending=$true}, AceQualifier | ForEach-Object {
            $NewDacl.InsertAce(0, $_)

        # Replace the DACL with the re-ordered one
        $RawSD.DiscretionaryAcl = $NewDacl

        # Commit those changes back to the original SD object (but not to disk yet):

        # Commit changes to disk
        if ($PSCmdlet.ShouldProcess(
            "Saved the ACL for '$(Convert-Path $Acl.Path)'",
            "Save the ACL for '$(Convert-Path $Acl.Path)'?",
            "Saving ACLs"
        )) {
            $Acl | Set-Acl

# Use the function like this:
Get-Acl $PathToBadFileOrFolder | RepairAclCanonicalOrder -Verbose

Give that a shot and let me know if it works.

By the way, if anyone wants to test this out themselves, here’s some code that will give you a DACL that is not in canonical order:

# First, create two folders:
$Parent = New-Item -Path "$env:temp\break_canonical_order" -ItemType directory -Force
$Child = New-Item -Path "$Parent\child_folder" -ItemType directory -Force
# Add a deny ACE on the parent:
$ParentAcl = Get-Acl $Parent
$ParentAcl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
    "ContainerInherit, ObjectInherit",
$ParentAcl | Set-Acl

# Add an allow ACE on the child:
$ChildAcl = Get-Acl $Child
$ChildAcl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
    "ContainerInherit, ObjectInherit",

# Disable inheritance on the child:
$ChildAcl.SetAccessRuleProtection($true, $true)
$ChildAcl | Set-Acl

# Confirm DACL is not in canonical order:
Get-Acl $Child | Format-List Path, Owner, AccessToString, AreAccessRulesCanonical

Hi Rohn,

thanks for your input here and on the module, sorry but I only just saw your post today.

I have installed the module as I have taken a snapshot of the effected server prior to testing the module.

I have tried running this on a tree, should I phraze it differently as I get the following result?

dir C:\Path\To\Broke\ACL\ | Get-SecurityDescriptor | Repair-AclCanonicalOrder

WARNING: The access rules for “C:\Path\To\Broke\ACL\foreachfile” are not in canonical order. To fix this, please run the ‘Repair-AclCanonicalOrder’



This is a confusing part of the module that I’ve got to document a little bit better. As written, the command is doing this:

dir C:\Path\To\Broke\ACL\ | Get-SecurityDescriptor | Repair-AclCanonicalOrder

[Get a list of files and folders in C:\Path\To\Broke\ACL] ->
    [Get the security descriptor for each one (this is like Get-Acl); if an ACL that isn't in canonical order is found, write a warning] ->
        [Put the ACL in canonical order for the in-memory security descriptor]

It’s not saving the modified security descriptor, though. Most of the SD modification functions in the module are made to work with SD objects from Get-Acl or Get-SecurityDescriptor, so they work on the security descriptor and expect it to be saved out later using Set-Acl (if you used Get-Acl), Set-SecurityDescriptor, or the -Apply switch. There is one exception to this where it will save the SD on it’s own (it will still prompt, though), and that’s when you pass it something other than a security descriptor. For example, if you pass output from Get-Item, Get-ChildItem, Get-Service, Get-WmiObject/Get-CimInstance (for some classes), etc, the functions will automatically call Get-SecurityDescriptor for you, make whatever change the function is responsible for, and prompt you to save the changes immediately.

That being said, any of the following commands should work:

Get-Item C:\Path\To\Broke\ACL | Get-SecurityDescriptor | Repair-AclCanonicalOrder -PassThru | Set-SecurityDescriptor #-Force

Get-Item C:\Path\To\Broke\ACL | Get-SecurityDescriptor | Repair-AclCanonicalOrder -Apply #-Force

# -Apply isn't necessary here, but it wouldn't hurt anything, either
Get-Item C:\Path\To\Broke\ACL | Repair-AclCanonicalOrder #-Force

You’ll always see the warning, but those commands should actually save the changes. Try it out and let me know how it goes. If you have any questions, please let me know.

Thanks Rohn,

I ran it on the content of a single folder and it worked well so I will go ahead and run it on the full affected tree.

I really appreaciate your work on this module!!!



I’m glad it worked, and thanks for the kind words. Keep a look out for the next version over the next few weeks–it is a lot faster, and there are some pretty cool features that have been added.

Thanks Rohn,

The process ran on the 40GB tree and completed without errors or issues. You have certainly saved the last remaining hairs on my head for the moment anyway. Thanks alot :slight_smile: