Infrastructure-as-code framework for Active Directory objects and Group Policy. Sanitized from production deployment for public sharing.
232 lines
8.8 KiB
PowerShell
232 lines
8.8 KiB
PowerShell
# Apply-ADBaseline.ps1
|
|
# Orchestration script for managing AD objects (OUs, groups, users).
|
|
#
|
|
# Reads declarative definitions from ous.ps1, groups.ps1, users.ps1,
|
|
# delegations.ps1, and password-policies.ps1, then compares against
|
|
# live AD and applies changes. Works from any machine with RSAT.
|
|
#
|
|
# Usage:
|
|
# .\Apply-ADBaseline.ps1 # Apply all AD objects
|
|
# .\Apply-ADBaseline.ps1 -TestOnly # Compare desired vs. current (no changes)
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[switch]$TestOnly
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$ScriptRoot = $PSScriptRoot
|
|
|
|
# -------------------------------------------------------------------
|
|
# Load helper functions
|
|
# -------------------------------------------------------------------
|
|
. (Join-Path $ScriptRoot 'lib\ADHelper.ps1')
|
|
|
|
# -------------------------------------------------------------------
|
|
# Load definitions
|
|
# -------------------------------------------------------------------
|
|
$ous = & (Join-Path $ScriptRoot 'ous.ps1')
|
|
$groups = & (Join-Path $ScriptRoot 'groups.ps1')
|
|
$users = & (Join-Path $ScriptRoot 'users.ps1')
|
|
$delegations = & (Join-Path $ScriptRoot 'delegations.ps1')
|
|
$passwordPolicies = & (Join-Path $ScriptRoot 'password-policies.ps1')
|
|
|
|
$totalDiffs = 0
|
|
$createdUsers = @()
|
|
|
|
# -------------------------------------------------------------------
|
|
# Pre-flight: Warn about stale credential files (older than 24 hours)
|
|
# -------------------------------------------------------------------
|
|
$credDir = Join-Path $ScriptRoot '.credentials'
|
|
if (Test-Path $credDir) {
|
|
$staleFiles = Get-ChildItem -Path $credDir -Filter '*.txt' |
|
|
Where-Object { $_.LastWriteTime -lt (Get-Date).AddHours(-24) }
|
|
if ($staleFiles.Count -gt 0) {
|
|
Write-Host '========================================================' -ForegroundColor Red
|
|
Write-Host ' WARNING: Stale credential files detected (>24 hours)' -ForegroundColor Red
|
|
Write-Host '========================================================' -ForegroundColor Red
|
|
foreach ($f in $staleFiles) {
|
|
$age = [math]::Round(((Get-Date) - $f.LastWriteTime).TotalHours, 1)
|
|
Write-Host " - $($f.Name) (${age}h old)" -ForegroundColor Red
|
|
}
|
|
Write-Host ' These should have been handed off and deleted.' -ForegroundColor Red
|
|
Write-Host '========================================================' -ForegroundColor Red
|
|
Write-Host ''
|
|
}
|
|
}
|
|
|
|
# -------------------------------------------------------------------
|
|
# Step 1: Organizational Units (must exist before groups/users)
|
|
# -------------------------------------------------------------------
|
|
Write-Host "=== Organizational Units ===" -ForegroundColor White
|
|
|
|
foreach ($ou in $ous) {
|
|
if ($TestOnly) {
|
|
$diff = Compare-ADOU -Name $ou.Name -Path $ou.Path
|
|
if ($diff) { $totalDiffs++ }
|
|
} else {
|
|
Ensure-ADOU -Name $ou.Name -Path $ou.Path -Description $ou.Description | Out-Null
|
|
}
|
|
}
|
|
|
|
Write-Host ''
|
|
|
|
# -------------------------------------------------------------------
|
|
# Step 2: Security Groups (must exist before users can join them)
|
|
# -------------------------------------------------------------------
|
|
Write-Host "=== Security Groups ===" -ForegroundColor White
|
|
|
|
foreach ($group in $groups) {
|
|
if ($TestOnly) {
|
|
$diffs = Compare-ADSecurityGroup -Name $group.Name -Members $group.Members
|
|
$totalDiffs += @($diffs).Count
|
|
} else {
|
|
$params = @{
|
|
Name = $group.Name
|
|
Path = $group.Path
|
|
Description = $group.Description
|
|
Scope = $group.Scope
|
|
}
|
|
# Don't sync members yet - users may not exist
|
|
Ensure-ADSecurityGroup @params | Out-Null
|
|
}
|
|
}
|
|
|
|
Write-Host ''
|
|
|
|
# -------------------------------------------------------------------
|
|
# Step 3: User Accounts
|
|
# -------------------------------------------------------------------
|
|
Write-Host "=== User Accounts ===" -ForegroundColor White
|
|
|
|
foreach ($user in $users) {
|
|
# Extract optional properties (anything not in the core schema)
|
|
$coreKeys = @('SamAccountName','Name','GivenName','Surname','Path','Enabled','MemberOf')
|
|
$optionalProps = @{}
|
|
foreach ($key in $user.Keys) {
|
|
if ($key -notin $coreKeys) {
|
|
$optionalProps[$key] = $user[$key]
|
|
}
|
|
}
|
|
|
|
if ($TestOnly) {
|
|
$diffs = Compare-ADUserAccount `
|
|
-SamAccountName $user.SamAccountName `
|
|
-Path $user.Path `
|
|
-Enabled $user.Enabled `
|
|
-MemberOf $user.MemberOf `
|
|
-Properties $optionalProps
|
|
$totalDiffs += @($diffs).Count
|
|
} else {
|
|
$wasCreated = Ensure-ADUserAccount `
|
|
-SamAccountName $user.SamAccountName `
|
|
-Name $user.Name `
|
|
-GivenName $user.GivenName `
|
|
-Surname $user.Surname `
|
|
-Path $user.Path `
|
|
-Enabled $user.Enabled `
|
|
-MemberOf $user.MemberOf `
|
|
-Properties $optionalProps
|
|
if ($wasCreated) {
|
|
$createdUsers += $user.SamAccountName
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-Host ''
|
|
|
|
# -------------------------------------------------------------------
|
|
# Step 4: Sync group membership (now that users exist)
|
|
# -------------------------------------------------------------------
|
|
if (-not $TestOnly) {
|
|
Write-Host "=== Group Membership Sync ===" -ForegroundColor White
|
|
|
|
foreach ($group in $groups) {
|
|
if ($group.Members.Count -gt 0) {
|
|
Ensure-ADSecurityGroup -Name $group.Name -Path $group.Path -Scope $group.Scope -Members $group.Members | Out-Null
|
|
}
|
|
}
|
|
|
|
Write-Host ''
|
|
}
|
|
|
|
# -------------------------------------------------------------------
|
|
# Step 5: OU Delegation (ACLs)
|
|
# -------------------------------------------------------------------
|
|
Write-Host "=== OU Delegation ===" -ForegroundColor White
|
|
|
|
foreach ($delegation in $delegations) {
|
|
foreach ($ou in $delegation.TargetOUs) {
|
|
if ($TestOnly) {
|
|
$diffs = Compare-OUDelegation `
|
|
-GroupName $delegation.GroupName `
|
|
-TargetOU $ou `
|
|
-Rights $delegation.Rights
|
|
$totalDiffs += @($diffs).Count
|
|
} else {
|
|
Ensure-OUDelegation `
|
|
-GroupName $delegation.GroupName `
|
|
-TargetOU $ou `
|
|
-Rights $delegation.Rights | Out-Null
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-Host ''
|
|
|
|
# -------------------------------------------------------------------
|
|
# Step 6: Fine-Grained Password Policies (PSOs)
|
|
# -------------------------------------------------------------------
|
|
Write-Host "=== Password Policies ===" -ForegroundColor White
|
|
|
|
foreach ($pso in $passwordPolicies) {
|
|
if ($TestOnly) {
|
|
$diffs = Compare-ADPasswordPolicy -Definition $pso
|
|
$totalDiffs += @($diffs).Count
|
|
} else {
|
|
Ensure-ADPasswordPolicy -Definition $pso
|
|
}
|
|
}
|
|
|
|
Write-Host ''
|
|
|
|
# -------------------------------------------------------------------
|
|
# Summary
|
|
# -------------------------------------------------------------------
|
|
if ($TestOnly) {
|
|
if ($totalDiffs -eq 0) {
|
|
Write-Host 'All AD objects match desired state.' -ForegroundColor Green
|
|
} else {
|
|
Write-Host "$totalDiffs difference(s) found." -ForegroundColor Red
|
|
}
|
|
Write-Host 'Compliance test complete. No changes were made.' -ForegroundColor Yellow
|
|
} else {
|
|
Write-Host 'All AD objects applied.' -ForegroundColor Green
|
|
|
|
# --- Credential handoff warning ---
|
|
if ($createdUsers.Count -gt 0) {
|
|
$credDir = Join-Path $ScriptRoot '.credentials'
|
|
Write-Host ''
|
|
Write-Host '========================================================' -ForegroundColor Magenta
|
|
Write-Host ' ACTION REQUIRED: New user credentials pending handoff' -ForegroundColor Magenta
|
|
Write-Host '========================================================' -ForegroundColor Magenta
|
|
Write-Host ''
|
|
Write-Host " $($createdUsers.Count) user(s) were created with temporary passwords." -ForegroundColor Yellow
|
|
Write-Host ' Passwords are stored in:' -ForegroundColor Yellow
|
|
Write-Host " $credDir" -ForegroundColor White
|
|
Write-Host ''
|
|
foreach ($u in $createdUsers) {
|
|
Write-Host " - $u.txt" -ForegroundColor White
|
|
}
|
|
Write-Host ''
|
|
Write-Host ' You must:' -ForegroundColor Yellow
|
|
Write-Host ' 1. Read each credential file' -ForegroundColor White
|
|
Write-Host ' 2. Securely share the password with the end user' -ForegroundColor White
|
|
Write-Host ' 3. Delete the credential file after handoff' -ForegroundColor White
|
|
Write-Host ''
|
|
Write-Host ' These files are ACL-locked to your account only.' -ForegroundColor DarkGray
|
|
Write-Host ' Users must change their password on first login.' -ForegroundColor DarkGray
|
|
Write-Host '========================================================' -ForegroundColor Magenta
|
|
}
|
|
}
|