Reorder acl after migrating with ms web deploy

This topic contains 18 replies, has 5 voices, and was last updated by  Gaucho Doyle 2 years, 9 months ago.

  • Author
  • #20525

    Gaucho Doyle

    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.


  • #20526

    Ondrej Zilinec

    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
  • #20527

    Gaucho Doyle

    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.



  • #20528

    Dave Wyatt

    Try this:

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

    Gaucho Doyle

    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 = "";...

  • #20552

    Dave Wyatt

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

  • #20555

    Gaucho Doyle

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

  • #20556

    Dave Wyatt

    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.

  • #20558

    Charles Downing

    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.
  • #20559

    Gaucho Doyle

    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:

  • #20560

    Dave Wyatt

    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.

  • #20562

    Gaucho Doyle

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

  • #20564

    Gaucho Doyle

    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

  • #20585

    Rohn Edwards

    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
  • #20670

    Gaucho Doyle

    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'



  • #20673

    Rohn Edwards

    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.

  • #20703

    Gaucho Doyle

    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!!!!



  • #20704

    Rohn Edwards

    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.

  • #20754

    Gaucho Doyle

    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 🙂

You must be logged in to reply to this topic.