Part 1 stood up the first DC. This part puts useful structure into the empty forest — OUs, users, security groups, the first GPO. Part 3 covers hardening GPOs.
Spent more time than I'd like to admit in environments where everything was dumped under Users and Computers. Don't do that. Five minutes of structure now saves five hours of GPO debugging later.
TL;DR — minimum sane OU structure:
lab.example.com/
├── Corp/ ← top-level container for everything you manage
│ ├── Users/
│ │ ├── Standard/
│ │ ├── Admins/
│ │ └── Service-Accounts/
│ ├── Groups/
│ ├── Computers/
│ │ ├── Workstations/
│ │ └── Servers/
│ └── Disabled/ ← park leavers here
└── (default Builtin / Users / Computers — leave alone)
Build this with New-ADOrganizationalUnit. Then provision users into the right OUs, GPOs link to OUs, life is simpler.
Why not use the default Users and Computers containers?
Because GPOs can't be linked to default CN containers. They're not OUs. The default Users container is CN=Users — a flat container, no OU semantics. Try to link a GPO to it, GPMC refuses.
Build OUs. Move accounts there. The default containers stay around for system accounts, but your stuff goes in your OUs.
Build the OU tree
$base = "DC=lab,DC=example,DC=com"
# Top
New-ADOrganizationalUnit -Name "Corp" -Path $base -ProtectedFromAccidentalDeletion $true
# Users
New-ADOrganizationalUnit -Name "Users" -Path "OU=Corp,$base" -ProtectedFromAccidentalDeletion $true
New-ADOrganizationalUnit -Name "Standard" -Path "OU=Users,OU=Corp,$base"
New-ADOrganizationalUnit -Name "Admins" -Path "OU=Users,OU=Corp,$base"
New-ADOrganizationalUnit -Name "Service-Accounts" -Path "OU=Users,OU=Corp,$base"
# Groups
New-ADOrganizationalUnit -Name "Groups" -Path "OU=Corp,$base" -ProtectedFromAccidentalDeletion $true
# Computers
New-ADOrganizationalUnit -Name "Computers" -Path "OU=Corp,$base" -ProtectedFromAccidentalDeletion $true
New-ADOrganizationalUnit -Name "Workstations" -Path "OU=Computers,OU=Corp,$base"
New-ADOrganizationalUnit -Name "Servers" -Path "OU=Computers,OU=Corp,$base"
# Disabled
New-ADOrganizationalUnit -Name "Disabled" -Path "OU=Corp,$base" -ProtectedFromAccidentalDeletion $true
-ProtectedFromAccidentalDeletion ($true by default in newer Server versions) blocks "right-click → delete" foot-guns. To remove an OU you have to unprotect first. Annoying once, prevents disasters.
Redirect default new-account location
By default, new computer joins land in CN=Computers. Move them automatically to your OU:
redircmp "OU=Workstations,OU=Computers,OU=Corp,$base"
redirusr "OU=Standard,OU=Users,OU=Corp,$base"
Now djoin and similar default-create their objects into the right place. Servers still need to be moved manually after join — that's intentional, you should categorize them.
For domain-joining a server with PowerShell, you can specify -OUPath to land directly in the right OU.
Create users
$path = "OU=Standard,OU=Users,OU=Corp,DC=lab,DC=example,DC=com"
$pass = ConvertTo-SecureString "TempPassw0rd!ChangeMe" -AsPlainText -Force
New-ADUser -Name "Lukas Goebel" `
-GivenName "Lukas" -Surname "Goebel" `
-SamAccountName "lgoebel" `
-UserPrincipalName "lgoebel@lab.example.com" `
-EmailAddress "lukas@lab.example.com" `
-Path $path `
-AccountPassword $pass `
-ChangePasswordAtLogon $true `
-Enabled $true
Bulk-create from CSV:
Import-Csv users.csv | ForEach-Object {
$pass = ConvertTo-SecureString $_.TempPassword -AsPlainText -Force
New-ADUser -Name "$($_.First) $($_.Last)" `
-GivenName $_.First -Surname $_.Last `
-SamAccountName $_.Sam `
-UserPrincipalName "$($_.Sam)@lab.example.com" `
-Path "OU=Standard,OU=Users,OU=Corp,DC=lab,DC=example,DC=com" `
-AccountPassword $pass `
-ChangePasswordAtLogon $true `
-Enabled $true
}
The CSV: First,Last,Sam,TempPassword. Generate temp passwords randomly — [System.Web.Security.Membership]::GeneratePassword(16,4) works if you load System.Web. Or generate via (1..16 | ForEach-Object { [char](Get-Random -Min 33 -Max 126) }) -join ''.
For creating local Windows users via cmd, net user is faster for one-offs but doesn't touch AD.
Security groups
Domain Local for resource access (e.g. "FileShare-Marketing-RW"). Global for role membership (e.g. "Marketing-Department"). Universal across domains in a forest. Most lab/small setups can use Global for everything until they grow.
$gpath = "OU=Groups,OU=Corp,DC=lab,DC=example,DC=com"
New-ADGroup -Name "Marketing-Dept" -GroupCategory Security `
-GroupScope Global -Path $gpath
New-ADGroup -Name "IT-Admins" -GroupCategory Security `
-GroupScope Global -Path $gpath
# Add members
Add-ADGroupMember -Identity "Marketing-Dept" -Members lgoebel, jdoe
Add-ADGroupMember -Identity "IT-Admins" -Members lgoebel
The pattern (AGDLP — Account → Global → Domain Local → Permission):
- Put users in global groups that represent roles ("Marketing-Dept", "IT-Admins").
- Put global groups in domain-local groups that represent permissions ("FileShare-Marketing-RW").
- Grant the domain-local group the resource permission.
This indirection feels excessive in a 50-user lab; it's load-bearing in a 5,000-user enterprise. Doing it consistently from the start means you don't refactor later.
Don't put users in Domain Admins casually
Domain Admins is the most powerful group in the domain. Anyone in it can do anything. Lots of orgs sprawl this group to 30+ users — that's a 30-account footprint for a single compromise.
Use tier-0 (DA) sparingly. For 99% of admin work, a member of a "Server Admins" group with delegated rights to specific OUs is enough.
# Delegate "manage user accounts in OU=Standard" to IT-Admins
# Easier via dsacls / GPMC delegation wizard than PowerShell.
Tier-0 / tier-1 / tier-2 separation is a whole other post. Start by keeping Domain Admins to ≤3 accounts.
First GPO — the demo
The classic demo: lock the desktop wallpaper for all standard users.
Import-Module GroupPolicy
# Create
New-GPO -Name "Standard-User-Settings" -Comment "Baseline for standard users"
# Set a registry-based setting — disable password reveal button (cosmetic example)
Set-GPRegistryValue -Name "Standard-User-Settings" `
-Key "HKCU\Software\Policies\Microsoft\Windows\CredUI" `
-ValueName "DisablePasswordReveal" -Type DWord -Value 1
# Link to OU
New-GPLink -Name "Standard-User-Settings" `
-Target "OU=Standard,OU=Users,OU=Corp,DC=lab,DC=example,DC=com" `
-Enforced No
Run gpupdate /force on a test workstation, log in as a user in that OU, verify.
For meaningful GPOs (password policy, audit, RDP restrictions, NTLMv1 disable), see Part 3 — GPO hardening.
Verify the structure
# OU tree
Get-ADOrganizationalUnit -Filter * -SearchBase "OU=Corp,DC=lab,DC=example,DC=com" |
Select Name, DistinguishedName
# User counts per OU
Get-ADOrganizationalUnit -Filter * -SearchBase "OU=Corp,DC=lab,DC=example,DC=com" |
ForEach-Object {
$count = (Get-ADUser -Filter * -SearchBase $_.DistinguishedName).Count
[PSCustomObject]@{OU=$_.Name; Users=$count}
}
# GPO links
Get-GPO -All | ForEach-Object {
$links = (Get-GPInheritance -Target $_.Path).GpoLinks
[PSCustomObject]@{Name=$_.DisplayName; LinkCount=$links.Count}
}
Disabling vs deleting users
When someone leaves: disable, don't delete. Move to the Disabled OU, disable the account, blank the password.
Disable-ADAccount -Identity lgoebel
Move-ADObject -Identity (Get-ADUser lgoebel).DistinguishedName `
-TargetPath "OU=Disabled,OU=Corp,DC=lab,DC=example,DC=com"
Disabled accounts retain group memberships (useful for forensics) and can be re-enabled in seconds. Deleting is irreversible — and breaks file ownership history.
After 90 days in Disabled, run a cleanup script to actually remove. Belt-and-braces.
Gotchas
- OU moves break GPO inheritance. A user in
OU=Agetting GPOFoolinked toOU=Awill loseFooafter moving toOU=B. IfOU=Bdoesn't haveFoolinked, behavior changes silently. Audit GPO links after every restructure. - Block Inheritance is dangerous. It cuts off ALL inherited GPOs to the OU below. Use sparingly. Prefer "Enforced" on the parent or security-group filtering on the GPO.
Authenticated Usersis the default GPO target. A GPO applies to everyone in scope unless you change the Security Filtering. To target a specific group, removeAuthenticated Usersand add the group — then ALSO grantAuthenticated UserstheRead(notApply group policy) permission, or processing breaks.- Don't move accounts manually with drag-and-drop in ADUC. Use PowerShell or the Move dialog. Drag-and-drop sometimes fails silently.
Where this leads
Part 3 — GPO hardening builds on this structure with the actually-useful policies: password length, lockout, audit, RDP scope, SMBv1 / LLMNR / NTLM disable at the GPO level. With the OU tree from this post, those policies have something to attach to.