Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Script Library

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.

AT A GLANCENew-Tier2OUStructure.ps1
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.

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.

New-Tier2OUStructure.ps1Download
<#
.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

ParameterTypeDefaultWhat it's for
-Pathstring—Where the Tier 2 OU goes, as a DN. The domain root or an OU like OU=Corp,DC=contoso,DC=com. Required.
-NamestringTier 2Name of the Tier 2 OU.
-SubOUstring[]Devices, Users, Groups, DisabledSub-OUs to create under it. Pass your own list if your naming is different.
-GroupNamestringTier2-AdminsThe management group. Reused if it already exists.
-GroupPathstringsame as -PathWhere 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.
-DelegationstringScopedScoped gives rights over computer, user and group objects only. FullControl gives the group the whole Tier 2 subtree.
-Serverstring—Domain name or domain controller to use.
-WhatIfswitch—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' -WhatIf

Build 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 FullControl

What you'll see

Example outputvalues are illustrative
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

  1. 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.
  2. 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.
  3. 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.
  4. 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-ADObject so -Server is honored.
  5. 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 -SubOU list 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.