Author Posts

September 12, 2013 at 7:42 am

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

September 12, 2013 at 8:35 am

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

September 12, 2013 at 10:12 am

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.

September 12, 2013 at 10:17 am

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()

September 12, 2013 at 10:19 am

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.)

September 12, 2013 at 12:05 pm

You are correct in your statements above ^^.

October 1, 2014 at 4:10 am

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!

October 1, 2014 at 5:44 am

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

October 2, 2014 at 12:25 am

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