AD Bootstrap Scripts Reference

Automation scripts for managing the AD structure defined in AD Bootstrap.

1. Overview

Script Purpose Flags

ad-bootstrap-create.ps1

Create all OUs and groups (idempotent)

-WhatIf

ad-bootstrap-reset.ps1

Delete all OUs, groups, and user

-WhatIf, -Recreate

2. ad-bootstrap-create.ps1

Creates the enterprise AD structure. Idempotent - safe to run multiple times, skips existing objects.

2.1. What It Creates

  • 7 top-level OUs: Tier 0-2, User Accounts, Groups, Staging, Disabled

  • 13 nested OUs: Service Accounts, Linux/Windows per tier, Workstations, etc.

  • 8 security groups: GRP-Computers-, GRP-Servers-, GRP-Users-*

2.2. Source Code

# ad-bootstrap-create.ps1
# Creates all AD objects defined in ad-bootstrap.adoc

param(
    [switch]$WhatIf
)

$ErrorActionPreference = "Stop"
$Domain = "DC=inside,DC=domusdigitalis,DC=dev"

Write-Host "=== AD Bootstrap Create ===" -ForegroundColor Cyan

if ($WhatIf) {
    Write-Host "[WHATIF MODE] No changes will be made" -ForegroundColor Yellow
}

2.2.1. Phase 1: Top-Level OUs

# These are the root OUs following Microsoft's Enterprise Access Model
$topLevelOUs = @(
    "Tier 0 - Identity",    # DCs, PKI, identity infrastructure
    "Tier 1 - Servers",     # Application servers
    "Tier 2 - Endpoints",   # Workstations, mobile
    "User Accounts",        # All user objects (renamed from "Users" to avoid CN=Users conflict)
    "Groups",               # Security and distribution groups
    "Staging",              # New objects before production placement
    "Disabled"              # Decommissioned objects (retain for audit)
)

foreach ($ou in $topLevelOUs) {
    try {
        New-ADOrganizationalUnit -Name $ou -Path $Domain -ProtectedFromAccidentalDeletion $true
        Write-Host "  Created OU: $ou" -ForegroundColor Green
    } catch {
        Write-Host "  OU exists: $ou" -ForegroundColor Yellow
    }
}

2.2.2. Phase 2: Nested OUs

# Nested OUs for granular organization
# Order matters: parent OUs must exist before children
$nestedOUs = @(
    # Tier 0 - Identity infrastructure service accounts
    @{ Name = "Service Accounts"; Path = "OU=Tier 0 - Identity,$Domain" },

    # Tier 1 - Servers by OS
    @{ Name = "Linux"; Path = "OU=Tier 1 - Servers,$Domain" },
    @{ Name = "Windows"; Path = "OU=Tier 1 - Servers,$Domain" },

    # Tier 2 - Endpoints
    @{ Name = "Workstations"; Path = "OU=Tier 2 - Endpoints,$Domain" },
    @{ Name = "Mobile"; Path = "OU=Tier 2 - Endpoints,$Domain" },

    # Workstations by OS (nested under Workstations)
    @{ Name = "Linux"; Path = "OU=Workstations,OU=Tier 2 - Endpoints,$Domain" },
    @{ Name = "Windows"; Path = "OU=Workstations,OU=Tier 2 - Endpoints,$Domain" },
    @{ Name = "Mac"; Path = "OU=Workstations,OU=Tier 2 - Endpoints,$Domain" },

    # User Accounts by role
    @{ Name = "Admins"; Path = "OU=User Accounts,$Domain" },
    @{ Name = "Standard"; Path = "OU=User Accounts,$Domain" },
    @{ Name = "Service Accounts"; Path = "OU=User Accounts,$Domain" },

    # Groups by type
    @{ Name = "Security"; Path = "OU=Groups,$Domain" },
    @{ Name = "Distribution"; Path = "OU=Groups,$Domain" }
)

foreach ($ou in $nestedOUs) {
    New-ADOrganizationalUnit -Name $ou.Name -Path $ou.Path -ProtectedFromAccidentalDeletion $true
}

2.2.3. Phase 3: Security Groups

# Security groups for ISE authorization policies
# Naming: GRP-<ObjectType>-<OS/Role>
$groupPath = "OU=Security,OU=Groups,$Domain"

$groups = @(
    # Computer groups - for 802.1X machine authentication
    # ISE checks computer account's group membership (e.g., modestus-razer$)
    @{ Name = "GRP-Computers-Linux-Admin"; Desc = "Linux admin workstations - Tier 2 privileged" },
    @{ Name = "GRP-Computers-Linux-Standard"; Desc = "Linux standard workstations" },
    @{ Name = "GRP-Computers-Windows"; Desc = "Windows workstations" },
    @{ Name = "GRP-Computers-Mac"; Desc = "Mac workstations" },

    # Server groups
    @{ Name = "GRP-Servers-Linux"; Desc = "Linux servers - network access" },
    @{ Name = "GRP-Servers-Windows"; Desc = "Windows servers - network access" },

    # User groups - for user-based authorization
    @{ Name = "GRP-Users-Admins"; Desc = "Admin users - privileged access" },
    @{ Name = "GRP-Users-Standard"; Desc = "Standard users" }
)

foreach ($group in $groups) {
    New-ADGroup -Name $group.Name `
        -GroupScope Global `
        -GroupCategory Security `
        -Path $groupPath `
        -Description $group.Desc
}

3. ad-bootstrap-reset.ps1

Removes all AD objects for testing. Destructive - use only in lab environments.

3.1. What It Removes

  1. User account (evanusmodestus)

  2. All 8 security groups (GRP-*)

  3. All 21 OUs (nested first, then parents)

3.2. Key Concepts

3.2.1. Deletion Order

OUs must be deleted children first, parents last. If you try to delete a parent OU with children, it fails.

# WRONG order - will fail
Remove-ADOrganizationalUnit "OU=Tier 2 - Endpoints,$Domain"  # Has children!

# CORRECT order - children first
Remove-ADOrganizationalUnit "OU=Linux,OU=Workstations,OU=Tier 2 - Endpoints,$Domain"
Remove-ADOrganizationalUnit "OU=Windows,OU=Workstations,OU=Tier 2 - Endpoints,$Domain"
Remove-ADOrganizationalUnit "OU=Mac,OU=Workstations,OU=Tier 2 - Endpoints,$Domain"
Remove-ADOrganizationalUnit "OU=Workstations,OU=Tier 2 - Endpoints,$Domain"
Remove-ADOrganizationalUnit "OU=Mobile,OU=Tier 2 - Endpoints,$Domain"
Remove-ADOrganizationalUnit "OU=Tier 2 - Endpoints,$Domain"  # Now safe

3.2.2. Protection Flag

OUs are created with -ProtectedFromAccidentalDeletion $true. Must disable before deletion:

# This will fail
Remove-ADOrganizationalUnit "OU=Staging,$Domain"
# Error: "Access denied" or "Object is protected"

# Must disable protection first
Set-ADOrganizationalUnit -Identity "OU=Staging,$Domain" -ProtectedFromAccidentalDeletion $false
Remove-ADOrganizationalUnit "OU=Staging,$Domain"  # Now works

3.3. Source Code

# ad-bootstrap-reset.ps1
# Removes all AD objects created by ad-bootstrap.adoc

param(
    [switch]$WhatIf,
    [switch]$Recreate  # If set, recreates structure after deletion
)

$ErrorActionPreference = "SilentlyContinue"
$Domain = "DC=inside,DC=domusdigitalis,DC=dev"

3.3.1. Phase 1: Remove User

# User must be removed first (exists in an OU we're deleting)
$user = Get-ADUser -Filter 'SamAccountName -eq "evanusmodestus"'
if ($user) {
    Remove-ADUser -Identity "evanusmodestus" -Confirm:$false
    Write-Host "Removed user: evanusmodestus"
}

3.3.2. Phase 2: Remove Groups

# Groups must be removed before their container OU
$groups = @(
    "GRP-Computers-Linux-Admin",
    "GRP-Computers-Linux-Standard",
    "GRP-Computers-Windows",
    "GRP-Computers-Mac",
    "GRP-Servers-Linux",
    "GRP-Servers-Windows",
    "GRP-Users-Admins",
    "GRP-Users-Standard"
)

foreach ($group in $groups) {
    $g = Get-ADGroup -Filter "Name -eq '$group'"
    if ($g) {
        Remove-ADGroup -Identity $group -Confirm:$false
        Write-Host "Removed group: $group"
    }
}

3.3.3. Phase 3: Remove OUs

# OUs in deletion order: deepest nested first, then parents
$ous = @(
    # Level 3 (deepest) - Workstation OS types
    "OU=Linux,OU=Workstations,OU=Tier 2 - Endpoints,$Domain",
    "OU=Windows,OU=Workstations,OU=Tier 2 - Endpoints,$Domain",
    "OU=Mac,OU=Workstations,OU=Tier 2 - Endpoints,$Domain",

    # Level 2 - Nested OUs
    "OU=Service Accounts,OU=Tier 0 - Identity,$Domain",
    "OU=Linux,OU=Tier 1 - Servers,$Domain",
    "OU=Windows,OU=Tier 1 - Servers,$Domain",
    "OU=Workstations,OU=Tier 2 - Endpoints,$Domain",
    "OU=Mobile,OU=Tier 2 - Endpoints,$Domain",
    "OU=Admins,OU=User Accounts,$Domain",
    "OU=Standard,OU=User Accounts,$Domain",
    "OU=Service Accounts,OU=User Accounts,$Domain",
    "OU=Security,OU=Groups,$Domain",
    "OU=Distribution,OU=Groups,$Domain",

    # Level 1 - Top-level OUs (deleted last)
    "OU=Tier 0 - Identity,$Domain",
    "OU=Tier 1 - Servers,$Domain",
    "OU=Tier 2 - Endpoints,$Domain",
    "OU=User Accounts,$Domain",
    "OU=Groups,$Domain",
    "OU=Staging,$Domain",
    "OU=Disabled,$Domain"
)

foreach ($ouDN in $ous) {
    $ou = Get-ADOrganizationalUnit -Filter "DistinguishedName -eq '$ouDN'"
    if ($ou) {
        # Disable protection, then delete
        Set-ADOrganizationalUnit -Identity $ouDN -ProtectedFromAccidentalDeletion $false
        Remove-ADOrganizationalUnit -Identity $ouDN -Confirm:$false -Recursive
        Write-Host "Removed OU: $($ou.Name)"
    }
}

4. Usage Examples

4.1. Fresh Deployment

# Preview what will be created
.\ad-bootstrap-create.ps1 -WhatIf

# Create everything
.\ad-bootstrap-create.ps1

# Manually create user (requires password)
New-ADUser -Name "Evan Rosado" -SamAccountName "evanusmodestus" `
    -UserPrincipalName "evanusmodestus@inside.domusdigitalis.dev" `
    -Path "OU=Admins,OU=User Accounts,DC=inside,DC=domusdigitalis,DC=dev" `
    -AccountPassword (Read-Host -AsSecureString "Password") `
    -Enabled $true -PasswordNeverExpires $true

Add-ADGroupMember -Identity "Domain Admins" -Members "evanusmodestus"
Add-ADGroupMember -Identity "GRP-Users-Admins" -Members "evanusmodestus"

4.2. Test Cycle

# Nuke everything
.\ad-bootstrap-reset.ps1

# Verify clean state
Get-ADOrganizationalUnit -Filter * | Select-Object Name
# Should only show "Domain Controllers"

# Rebuild
.\ad-bootstrap-create.ps1

# Verify
Get-ADOrganizationalUnit -Filter * | Select-Object Name
# Should show all 21 OUs

4.3. Full Reset and Recreate

# One command to delete and recreate (except user)
.\ad-bootstrap-reset.ps1 -Recreate

5. ISE Group Mapping

After creation, these groups map to ISE authorization policies:

AD Group Members ISE Authorization Profile

GRP-Computers-Linux-Admin

modestus-razer$, modestus-p50$, modestus-aw$

Linux-Admin-VLAN10

GRP-Computers-Linux-Standard

Standard Linux workstations

Linux-Standard-VLAN10

GRP-Computers-Windows

Windows computer objects

Windows-Workstation-VLAN10

GRP-Computers-Mac

Mac computer objects

Mac-Workstation-VLAN10

GRP-Servers-Linux

vault-01$, kvm-01$, nas-01$

Server-VLAN20

GRP-Servers-Windows

home-dc01$, future Windows servers

Server-VLAN20