<# .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)" }