SCRIPT LIBRARY · POWERSHELL
Building a Tier 2 OU Structure and Admin Group with PowerShell
Create the Tier 2 OU, its sub-OUs and the group that manages them, and delegate just enough rights for workstation and user admin work.
- What it does
- Creates a Tier 2 OU with sub-OUs, creates the Tier 2 admin group, and delegates create, delete and full control of computers, users and groups under Tier 2 to it. Re-runnable, and -WhatIf previews everything.
- Requires
- Windows PowerShell 5.1 or PowerShell 7 on Windows
- ActiveDirectory module (RSAT)
- Permissions
- Rights to create OUs under the parent, create the group, and change permissions on the new OU. Usually a Domain Admin.
- Runs on
- Windows 10/11 with RSAT, Windows Server 2016+
- Tested
- Parse-checked and dry-run with -WhatIf against mocked AD cmdlets in PowerShell 7.4. The ACL step needs a real domain to exercise.
Part 2 of the thread Keeping Active Directory tidy
Tier 2 is where most of the day-to-day work happens in a tiered admin model: workstations, regular user accounts, and the groups they belong to. It's also where most of your admins spend their time, so it's the tier where least privilege pays off the most.
This script lays the groundwork. It creates a Tier 2 OU with sub-OUs for devices, users, groups and disabled objects, creates the group that manages them, and delegates permissions on the Tier 2 OU. By default that delegation is scoped: the group can create, delete and fully manage computer, user and group objects anywhere under Tier 2, and that's it. No rights on the OUs themselves, no linking GPOs, nothing outside the tree. If you really do want the whole subtree handed over, -Delegation FullControl does that.
The original version of this post used raw ADSI calls with blank placeholders and didn't run as written. This one uses the ActiveDirectory module, takes every name and path as a parameter, and supports -WhatIf.
<#
.SYNOPSIS
Builds a Tier 2 OU structure and delegates it to a Tier 2 admin group.
.DESCRIPTION
Creates the Tier 2 OU and its sub-OUs, creates the group that will manage them, and
delegates permissions on the Tier 2 OU. The default "Scoped" delegation lets the group
create, delete and fully manage computer, user and group objects under Tier 2 and nothing
else. "FullControl" hands over the whole subtree instead. Safe to re-run: anything that
already exists is left alone. Supports -WhatIf.
.PARAMETER Path
Distinguished name of the parent the Tier 2 OU goes under, e.g. DC=contoso,DC=com.
.PARAMETER Name
Name of the Tier 2 OU. Default: Tier 2.
.PARAMETER SubOU
Sub-OUs to create inside it. Default: Devices, Users, Groups, Disabled.
.PARAMETER GroupName
Name of the management group. Default: Tier2-Admins.
.PARAMETER GroupPath
Where the management group lives. Defaults to -Path. Keep it outside the Tier 2 OU.
.PARAMETER Delegation
Scoped (computers, users, groups) or FullControl. Default: Scoped.
.PARAMETER Server
Domain or domain controller to talk to. Defaults to the current domain.
.EXAMPLE
.\New-Tier2OUStructure.ps1 -Path 'DC=contoso,DC=com' -WhatIf
.EXAMPLE
.\New-Tier2OUStructure.ps1 -Path 'OU=Corp,DC=contoso,DC=com' -GroupPath 'OU=Groups,OU=Tier 1,OU=Admin,DC=contoso,DC=com'
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)][ValidatePattern('DC=')][string]$Path,
[ValidateNotNullOrEmpty()][string]$Name = 'Tier 2',
[string[]]$SubOU = @('Devices', 'Users', 'Groups', 'Disabled'),
[ValidateNotNullOrEmpty()][string]$GroupName = 'Tier2-Admins',
[string]$GroupPath,
[ValidateSet('Scoped', 'FullControl')][string]$Delegation = 'Scoped',
[string]$Server
)
$ErrorActionPreference = 'Stop'
Import-Module ActiveDirectory
$ad = @{}
if ($Server) { $ad.Server = $Server }
if (-not $GroupPath) { $GroupPath = $Path }
$tier2DN = "OU=$Name,$Path"
function Write-Result([string]$Action, [string]$Target, [string]$Result, [string]$Detail = '') {
[pscustomobject]@{ Action = $Action; Target = $Target; Result = $Result; Detail = $Detail }
}
function Confirm-OU([string]$OUName, [string]$Parent) {
$dn = "OU=$OUName,$Parent"
if (Get-ADOrganizationalUnit -Filter * -SearchBase $Parent -SearchScope OneLevel @ad | Where-Object DistinguishedName -eq $dn) {
Write-Result 'Create OU' $dn 'Skipped' 'Already exists'
}
elseif ($PSCmdlet.ShouldProcess($dn, 'Create OU')) {
New-ADOrganizationalUnit -Name $OUName -Path $Parent -ProtectedFromAccidentalDeletion $true @ad
Write-Result 'Create OU' $dn 'Done'
}
else { Write-Result 'Create OU' $dn 'WhatIf' }
}
if ($GroupPath -like "*$tier2DN") {
Write-Warning 'The management group is inside the OU it manages, so its members can edit its membership. Consider -GroupPath in a higher tier.'
}
# --- 1. OUs
$null = Get-ADObject -Identity $Path @ad
Confirm-OU $Name $Path
$tier2Exists = [bool](Get-ADOrganizationalUnit -Filter * -SearchBase $Path -SearchScope OneLevel @ad | Where-Object DistinguishedName -eq $tier2DN)
foreach ($child in $SubOU) {
if ($tier2Exists) { Confirm-OU $child $tier2DN } else { Write-Result 'Create OU' "OU=$child,$tier2DN" 'WhatIf' }
}
# --- 2. Management group
$group = Get-ADGroup -Filter "Name -eq '$($GroupName -replace "'", "''")'" @ad
if ($group) {
Write-Result 'Create group' $GroupName 'Skipped' "Already exists at $($group.DistinguishedName)"
}
elseif ($PSCmdlet.ShouldProcess("$GroupName in $GroupPath", 'Create security group')) {
$group = New-ADGroup -Name $GroupName -SamAccountName $GroupName -GroupCategory Security -GroupScope Global -Path $GroupPath -Description "Manages $tier2DN (tiered admin model)" -PassThru @ad
Write-Result 'Create group' $GroupName 'Done'
}
else { Write-Result 'Create group' $GroupName 'WhatIf' }
if (-not ($group -and $tier2Exists)) {
Write-Result 'Delegate permissions' $tier2DN 'WhatIf' "$Delegation delegation, once the OU and group exist"
return
}
# --- 3. Delegation
$sid = [System.Security.Principal.SecurityIdentifier]$group.SID.Value
$all = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All
$descendents = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::Descendents
$rules = [System.Collections.Generic.List[System.DirectoryServices.ActiveDirectoryAccessRule]]::new()
if ($Delegation -eq 'FullControl') {
$rules.Add([System.DirectoryServices.ActiveDirectoryAccessRule]::new($sid, 'GenericAll', 'Allow', $all))
}
else {
# Schema class GUIDs are the same in every AD forest.
$classes = @{
computer = [guid]'bf967a86-0de6-11d0-a285-00aa003049e2'
user = [guid]'bf967aba-0de6-11d0-a285-00aa003049e2'
group = [guid]'bf967a9c-0de6-11d0-a285-00aa003049e2'
}
foreach ($class in $classes.Values) {
# Create and delete this kind of object anywhere under Tier 2...
$rules.Add([System.DirectoryServices.ActiveDirectoryAccessRule]::new($sid, 'CreateChild, DeleteChild', 'Allow', $class, $all, [guid]::Empty))
# ...and full control over existing objects of this kind.
$rules.Add([System.DirectoryServices.ActiveDirectoryAccessRule]::new($sid, 'GenericAll', 'Allow', [guid]::Empty, $descendents, $class))
}
}
$ou = Get-ADObject -Identity $tier2DN -Properties nTSecurityDescriptor @ad
$acl = $ou.nTSecurityDescriptor
$existing = @($acl.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier]) | Where-Object IdentityReference -eq $sid)
$toAdd = @($rules | Where-Object {
$r = $_
-not ($existing | Where-Object { $_.ActiveDirectoryRights -eq $r.ActiveDirectoryRights -and $_.ObjectType -eq $r.ObjectType -and $_.InheritedObjectType -eq $r.InheritedObjectType -and $_.InheritanceType -eq $r.InheritanceType })
})
if ($toAdd.Count -eq 0) {
Write-Result 'Delegate permissions' $tier2DN 'Skipped' 'All permissions already present'
}
elseif ($PSCmdlet.ShouldProcess($tier2DN, "Grant $GroupName $($toAdd.Count) permission entries ($Delegation)")) {
foreach ($rule in $toAdd) { $acl.AddAccessRule($rule) }
Set-ADObject -Identity $tier2DN -Replace @{ nTSecurityDescriptor = $acl } @ad
Write-Result 'Delegate permissions' $tier2DN 'Done' "$($toAdd.Count) entries ($Delegation)"
}
else { Write-Result 'Delegate permissions' $tier2DN 'WhatIf' "$($toAdd.Count) entries ($Delegation)" }
Parameters
| Parameter | Type | Default | What it's for |
|---|---|---|---|
-Path | string | — | Where the Tier 2 OU goes, as a DN. The domain root or an OU like OU=Corp,DC=contoso,DC=com. Required. |
-Name | string | Tier 2 | Name of the Tier 2 OU. |
-SubOU | string[] | Devices, Users, Groups, Disabled | Sub-OUs to create under it. Pass your own list if your naming is different. |
-GroupName | string | Tier2-Admins | The management group. Reused if it already exists. |
-GroupPath | string | same as -Path | Where the management group is created. Put it in a higher tier's Groups OU if you have one. You'll get a warning if it lands inside Tier 2. |
-Delegation | string | Scoped | Scoped gives rights over computer, user and group objects only. FullControl gives the group the whole Tier 2 subtree. |
-Server | string | — | Domain name or domain controller to use. |
-WhatIf | switch | — | Preview every OU, group and permission without creating anything. |
Run it
Dry run against the domain root.
.\New-Tier2OUStructure.ps1 -Path 'DC=contoso,DC=com' -WhatIfBuild it under a top-level OU, with the admin group kept in Tier 1.
.\New-Tier2OUStructure.ps1 -Path 'OU=Corp,DC=contoso,DC=com' -GroupPath 'OU=Groups,OU=Tier 1,OU=Admin,DC=contoso,DC=com'Your own sub-OUs and group name.
.\New-Tier2OUStructure.ps1 -Path 'DC=contoso,DC=com' -SubOU Workstations, Laptops, Users, Groups, Disabled -GroupName 'GG-T2-ServiceDesk'A branch office where the local team gets the whole subtree.
.\New-Tier2OUStructure.ps1 -Path 'OU=Branch-East,DC=contoso,DC=com' -GroupName 'Tier2-East-Admins' -Delegation FullControlWhat you'll see
Action Target Result Detail
------ ------ ------ ------
Create OU OU=Tier 2,OU=Corp,DC=contoso,DC=com Done
Create OU OU=Devices,OU=Tier 2,OU=Corp,DC=contoso... Done
Create OU OU=Users,OU=Tier 2,OU=Corp,DC=contoso,D... Done
Create OU OU=Groups,OU=Tier 2,OU=Corp,DC=contoso,... Done
Create OU OU=Disabled,OU=Tier 2,OU=Corp,DC=contos... Done
Create group Tier2-Admins Done
Delegate permissions OU=Tier 2,OU=Corp,DC=contoso,DC=com Done 6 entries (Scoped)
How it works
- Create the OUs. Tier 2 goes under
-Path, and each sub-OU goes under Tier 2. Anything that already exists is skipped, so a second run just fills in the gaps. - Create the management group. A global security group at
-GroupPath. If a group with that name exists anywhere in the domain, the script uses it rather than making a duplicate. - Build the permission entries. For
Scoped, that's two entries for each of computer, user and group: one for creating and deleting that kind of object anywhere under Tier 2, and one for full control over existing objects of that kind. They're keyed by the schema class GUIDs, which are the same in every AD forest, so there's nothing environment-specific to look up. - Add only what's missing. It compares the new entries against what the group already has on the OU and writes back only the difference, using
Set-ADObjectso-Serveris honored. - Report. You get one object per step, with Done, Skipped, Failed or WhatIf.
Take it further
- Do the same for Tier 1. Point it at a server OU with a Tier 1 group and a
-SubOUlist that suits servers. The scoped delegation works the same way. - Add the Tier 0 piece. The Tier 0 delegation script handles the top of the model, where the rules get stricter.
- Put the group to work on the endpoints. Delegating the OU lets Tier 2 admins manage the AD objects. Making them local admins on the workstations themselves is a separate job for Intune or Group Policy.
Things that'll trip you up
- Keep the group out of the OU it manages. If Tier2-Admins lives in Tier 2's own Groups OU, its members have full control over it and can add whoever they like. Park it in a higher tier. The script warns you when it spots this.
- Scoped still means full control of users. That includes resetting passwords. Which is fine, as long as only regular user accounts live under Tier 2. Admin accounts for Tier 0 and Tier 1 belong in their own tiers.
- Protected accounts ignore delegation. If a member of Domain Admins or another protected group ends up under Tier 2, AdminSDHolder resets its permissions every hour and your delegation won't apply to it. Another good reason to keep admin accounts out of here.
- Link GPOs before you move computers in. Moving a machine into a new OU changes which Group Policy it gets on the next refresh. Link your workstation policies to the new OUs first, then move a test machine or two before the rest.
- Cleaning up a test run takes an extra step. The OUs are created with "Protect from accidental deletion" turned on. To delete them, clear that flag first with Set-ADOrganizationalUnit -ProtectedFromAccidentalDeletion $false.