Copy OU Permissions and Groups

This topic contains 8 replies, has 4 voices, and was last updated by  Nicolaj Rasmussen 3 years, 1 month ago.

  • Author
    Posts
  • #10077

    Trevor March
    Participant

    I have existing OUs but want to copy permissions and groups to a newly named OU and then have the names of the groups match the names of the newly created OU.

    Example:
    Existing OU named: GR8
    Group names: 'GR8' Admin Users, 'GR8' Regular Users, etc

    I would love a .PS1 that would prompt for the OU Name and OU Groups and then copy the exact permissions of an existing OU.
    Create new OU – ABC, copy same permissions/groups as GR8 but have ABC instead of GR8 in the names for all the associated groups, permissions, etc.

    Sorry I know this sounds kind of crazy but hopefully someone understands what I am trying to do!

    Thanks,
    Trevor

  • #10080

    Dave Wyatt
    Moderator

    I think it can be done, but the code will be a bit complex (working with ACLs always is; no one-liners here). Part of the code would probably look something like this:

    $sourceOU = Get-ADOrganizationalUnit -Identity 'OU=Source,DC=domain,DC=com' -Properties nTSecurityDescriptor -ErrorAction Stop
    $destOU = Get-ADOrganizationalUnit -Identity 'OU=Destination,DC=domain,DC=com' -Properties nTSecurityDescriptor -ErrorAction Stop
    
    # You might want to clear the destination ACL of non-inherited ACEs before starting this loop
    
    foreach ($sourceAce in $sourceOU.nTSecurityDescriptor.Access)
    {
        if ($sourceAce.IsInherited) { continue }
    
        $destAce = $destOU.nTSecurityDescriptor.AccessRuleFactory($sourceAce.IdentityReference,
                                                                  $sourceAce.ActiveDirectoryRights,
                                                                  $sourceAce.IsInherited,
                                                                  $sourceAce.InheritanceFlags,
                                                                  $sourceAce.PropagationFlags,
                                                                  $sourceAce.AccessControlType,
                                                                  $sourceAce.ObjectType,
                                                                  $sourceAce.InheritedObjectType)
        
        try
        {
            $sourceAccount = $sourceAce.IdentityReference.Translate([System.Security.Principal.NTAccount])
            
            $newName = $sourceAccount.Value -replace "\\$([regex]::Escape($sourceOU.Name))", "\$($destOU.Name)"
    
            $destAce.IdentityReference = [System.Security.Principal.NTAccount]$newName
        }
        catch
        {
            # You may want to log an error if you can't translate the SID to User\Group form
        }
        
        # This statement will throw an error if the destination identity doesn't exist.
        $destOU.nTSecurityDescriptor.AddAccessRule($destAce)
    }
    
    # call Set-ADOrganizationalUnit to commit the new ACL updates
    
  • #10085

    Richard Siddaway
    Moderator

    Do the groups administer the contents of the OU?

    if so you can't just copy the permissions on the old groups as they will refer to the old OU.

  • #10086

    Trevor March
    Participant

    Yes that's what I was thinking. Do you have an idea of what I am trying to do and how to accomplish it then?

    # Get distinguished name of the AD Object to get ACL from and distinguished name of the AD Object to apply the ACL to
    $DNPathOU1 = Read-Host "Enter the DN path of the AD object to copy security descriptors from"
    $DNPathOU2 = Read-Host "Enter the DN path of the AD object to apply security descriptors to"

    # Use ADSI connections to AD for the objects defined above. These are needed to get ACL and set ACL objects
    $DE1 = [ADSI]"LDAP://$DNPathOU1"
    $DE2 = [ADSI]"LDAP://$DNPathOU2"

    # Retrieve security descriptors of the first AD object
    $OU1acl = $DE1.psbase.ObjectSecurity
    $OU1sddl = $OU1acl.GetSecurityDescriptorSddlForm([System.Security.AccessControl.AccessControlSections]::All)

    # Add and apply security descriptor changes to the second AD object
    $DE2.psbase.ObjectSecurity.SetSecurityDescriptorSddlForm($OU1sddl)
    $DE2.psbase.CommitChanges()

  • #10087

    Dave Wyatt
    Moderator

    My understanding of the original post was that the old OU named GR8 might have a group named (for instance) GR8_Admins with Full Control permission defined. The new OU named ABC should grant Full Control to ABC_Admins instead. That's what the sample code I posted should accomplish (though the code isn't complete and I haven't tested it yet; it was just meant to demonstrate how to work with ACLs in Active Directory.)

  • #10093

    Trevor March
    Participant

    You are correct in your statements above ^^.

  • #19237

    Nicolaj Rasmussen
    Participant

    Sorry to revive an old thread, but this is exactly what I was looking for and I'm unsure whether the thread starter got his problem solved using the code, since I get the error:

    'IdentityReference' is a ReadOnly property.

    in the line:

    $destAce.IdentityReference = [System.Security.Principal.NTAccount]$newName

    Does anyone have an idea how to set the permissions on the destination ou?
    Should I somehow build a new nTSecurityDescriptor object and populate with the values from the source instead?

    A bit blank here I'm afraid, so any help is higly appreciated!

  • #19249

    Dave Wyatt
    Moderator

    Ah, that was just some sample code that I typed up; it wasn't tested. That bug should be easy enough to fix, though; just do the translation before constructing the ACE. Here's a revision of the original sample (still untested):

    $sourceOU = Get-ADOrganizationalUnit -Identity 'OU=Source,DC=domain,DC=com' -Properties nTSecurityDescriptor -ErrorAction Stop
    $destOU = Get-ADOrganizationalUnit -Identity 'OU=Destination,DC=domain,DC=com' -Properties nTSecurityDescriptor -ErrorAction Stop
    
    # You might want to clear the destination ACL of non-inherited ACEs before starting this loop
    
    foreach ($sourceAce in $sourceOU.nTSecurityDescriptor.Access)
    {
        if ($sourceAce.IsInherited) { continue }
    
        $identityReference = $null
        
        try
        {
            $sourceAccount = $sourceAce.IdentityReference.Translate([System.Security.Principal.NTAccount])
            $newName = $sourceAccount.Value -replace "\\$([regex]::Escape($sourceOU.Name))", "\$($destOU.Name)"
            $identityReference = [System.Security.Principal.NTAccount]$newName
        }
        catch
        {
            # You may want to log an error if you can't translate the SID to User\Group form
        }
    
        $destAce = $destOU.nTSecurityDescriptor.AccessRuleFactory($identityReference,
                                                                  $sourceAce.ActiveDirectoryRights,
                                                                  $sourceAce.IsInherited,
                                                                  $sourceAce.InheritanceFlags,
                                                                  $sourceAce.PropagationFlags,
                                                                  $sourceAce.AccessControlType,
                                                                  $sourceAce.ObjectType,
                                                                  $sourceAce.InheritedObjectType)
    
        # This statement will throw an error if the destination identity doesn't exist.
        $destOU.nTSecurityDescriptor.AddAccessRule($destAce)
    }
    
    # call Set-ADOrganizationalUnit to commit the new ACL updates
    
  • #19274

    Nicolaj Rasmussen
    Participant

    Hi Dave, thanks a lot, I didn't think of that.

    To get the replace to work, I had to omit the regexp portionm like this:

    $newName = $sourceAccount.Value -replace $sourceOUName, $destOUName
    

    Echoing out "$sourceAccount.Value" in quotes gave me the DN of the OU instead of just the name, echoing $sourceAccount.Value without quotes gave me the name instead, but not entirely sure if that was the problem with the regexp too.

    Anyway, If I print out the destAce after the replace it looks good, the portion of the groupname has been replaced:

    ---------- SOURCE ACL v ----------
    ActiveDirectoryRights : WriteProperty
    InheritanceType       : None
    ObjectType            : 00000000-0000-0000-0000-000000000000
    InheritedObjectType   : 00000000-0000-0000-0000-000000000000
    ObjectFlags           : None
    AccessControlType     : Allow
    IdentityReference     : MYDOMAIN\Admins@4077.dk
    IsInherited           : False
    InheritanceFlags      : None
    PropagationFlags      : None
    
    ---------- DESTINATION ACL v ----------
    ActiveDirectoryRights : WriteProperty
    InheritanceType       : None
    ObjectType            : 00000000-0000-0000-0000-000000000000
    InheritedObjectType   : 00000000-0000-0000-0000-000000000000
    ObjectFlags           : None
    AccessControlType     : Allow
    IdentityReference     : MYDOMAIN\Admins@4204.dk
    IsInherited           : False
    InheritanceFlags      : None
    PropagationFlags      : None
    

    But calling Set-ADOrganizationalUnit on the $destOU in the end, either inside or after the foreach loop, doesn't write the ACL to the destination OU when checking in AD Users and Computers, security properties of the OU (and no errors).

    Set-ADOrganizationalUnit -instance $destOU
    

    Do you have any more pointers on how to apply the new ACLs?
    And do you have an idea for how to remove the already non-inherited ACLs on the destOU easily?

    The slightly modified script so far:

    cls
    
    $sourceOU = Get-ADOrganizationalUnit -Identity 'OU=4077.dk,OU=Hosting,DC=mycorp,DC=dk' -Properties nTSecurityDescriptor -ErrorAction Stop
    $destOU = Get-ADOrganizationalUnit -Identity 'OU=4204.dk,OU=Hosting,DC=mycorp,DC=dk'-Properties nTSecurityDescriptor -ErrorAction Stop
    
    # You might want to clear the destination ACL of non-inherited ACEs before starting this loop
    
    foreach ($sourceAce in $sourceOU.nTSecurityDescriptor.Access)
    {
        if ($sourceAce.IsInherited) { continue }
        "---------- SOURCE ACL v ----------"
        $sourceAce
        $identityReference = $null
    
    #    try
    #    {
            $sourceAccount = $sourceAce.IdentityReference.Translate([System.Security.Principal.NTAccount])
            $newName = $sourceAccount.Value -replace $sourceOU.Name, $destOU.Name
            $identityReference = [System.Security.Principal.NTAccount]$newName
    #    }
    #    catch
    #    {
            # You may want to log an error if you can't translate the SID to User\Group form
    #    }
    
        $destAce = $destOU.nTSecurityDescriptor.AccessRuleFactory($identityReference,
                                                                  $sourceAce.ActiveDirectoryRights,
                                                                  $sourceAce.IsInherited,
                                                                  $sourceAce.InheritanceFlags,
                                                                  $sourceAce.PropagationFlags,
                                                                  $sourceAce.AccessControlType,
                                                                  $sourceAce.ObjectType,
                                                                  $sourceAce.InheritedObjectType)
    
        "---------- DESTINATION ACL v ----------"
        $destAce
    
        # This statement will throw an error if the destination identity doesn't exist.
        $destOU.nTSecurityDescriptor.AddAccessRule($destAce)
        Set-ADOrganizationalUnit -instance $destOU
    }
    

    Thanks a lot – I really appreciate it!
    Nicolaj

You must be logged in to reply to this topic.