Infrastructure-as-code framework for Active Directory objects and Group Policy. Sanitized from production deployment for public sharing.
377 lines
16 KiB
PowerShell
377 lines
16 KiB
PowerShell
# Apply-GPOBaseline.ps1
|
|
# Orchestration script for applying GPO settings to Active Directory.
|
|
#
|
|
# Reads settings.ps1 from each GPO directory, compares against the live
|
|
# GPO in AD, and applies changes. Works from any machine with RSAT.
|
|
#
|
|
# Usage:
|
|
# .\Apply-GPOBaseline.ps1 # Apply all GPO settings
|
|
# .\Apply-GPOBaseline.ps1 -TestOnly # Compare desired vs. current (no changes)
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[switch]$TestOnly,
|
|
[switch]$GpUpdate,
|
|
[switch]$NoBackup,
|
|
[switch]$NoCleanup
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$ScriptRoot = $PSScriptRoot
|
|
|
|
# -------------------------------------------------------------------
|
|
# Load helper functions
|
|
# -------------------------------------------------------------------
|
|
. (Join-Path $ScriptRoot 'lib\GPOHelper.ps1')
|
|
|
|
# -------------------------------------------------------------------
|
|
# Groups that should have edit rights on ALL managed GPOs.
|
|
# This ensures Tier 0 operational admins can manage GPOs without
|
|
# Domain Admins membership (DA = break-glass only).
|
|
# -------------------------------------------------------------------
|
|
$ManagementGroups = @('MasterAdmins')
|
|
|
|
# -------------------------------------------------------------------
|
|
# Discover GPO directories (each must contain a settings.ps1)
|
|
# -------------------------------------------------------------------
|
|
$gpoDirs = Get-ChildItem -Path $ScriptRoot -Directory |
|
|
Where-Object { Test-Path (Join-Path $_.FullName 'settings.ps1') }
|
|
|
|
if ($gpoDirs.Count -eq 0) {
|
|
Write-Host 'No GPO directories with settings.ps1 found.' -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
Write-Host "Found $($gpoDirs.Count) GPO configuration(s):`n" -ForegroundColor Cyan
|
|
|
|
$totalDiffs = 0
|
|
|
|
# -------------------------------------------------------------------
|
|
# Process each GPO
|
|
# -------------------------------------------------------------------
|
|
foreach ($dir in $gpoDirs) {
|
|
$settingsPath = Join-Path $dir.FullName 'settings.ps1'
|
|
$settings = & $settingsPath
|
|
|
|
$gpoName = $settings.GPOName
|
|
Write-Host "=== $gpoName ===" -ForegroundColor White
|
|
|
|
# Verify GPO exists in AD — create if missing
|
|
try {
|
|
$gpo = Get-GPO -Name $gpoName -ErrorAction Stop
|
|
Write-Host " GPO found: {$($gpo.Id)}" -ForegroundColor Green
|
|
} catch {
|
|
if ($TestOnly) {
|
|
Write-Host " [DRIFT] GPO '$gpoName' does not exist yet (will be created on apply)" -ForegroundColor Yellow
|
|
$totalDiffs++
|
|
Write-Host ''
|
|
continue
|
|
} else {
|
|
Write-Host " GPO '$gpoName' not found - creating..." -ForegroundColor Yellow
|
|
$newGpoParams = @{ Name = $gpoName }
|
|
if ($settings.Description) { $newGpoParams.Comment = $settings.Description }
|
|
$gpo = New-GPO @newGpoParams
|
|
Write-Host " [CREATED] GPO: $gpoName {$($gpo.Id)}" -ForegroundColor Green
|
|
}
|
|
}
|
|
|
|
# --- GPO Description ---
|
|
if ($settings.Description) {
|
|
if ($TestOnly) {
|
|
if ($gpo.Description -ne $settings.Description) {
|
|
Write-Host " [DRIFT] Description: '$($gpo.Description)' -> '$($settings.Description)'" -ForegroundColor Red
|
|
$totalDiffs++
|
|
}
|
|
} else {
|
|
if ($gpo.Description -ne $settings.Description) {
|
|
$gpo.Description = $settings.Description
|
|
Write-Host " [UPDATED] Description set" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- GPO Status (enabled/disabled sections) ---
|
|
$hasStatusConfig = $settings.ContainsKey('DisableUserConfiguration') -or
|
|
$settings.ContainsKey('DisableComputerConfiguration')
|
|
if ($hasStatusConfig) {
|
|
$disableUser = if ($settings.ContainsKey('DisableUserConfiguration')) {
|
|
$settings.DisableUserConfiguration
|
|
} else { $false }
|
|
$disableComputer = if ($settings.ContainsKey('DisableComputerConfiguration')) {
|
|
$settings.DisableComputerConfiguration
|
|
} else { $false }
|
|
|
|
if ($TestOnly) {
|
|
$statusDiff = Compare-GPOStatus -GPOName $gpoName `
|
|
-DisableUserConfiguration $disableUser `
|
|
-DisableComputerConfiguration $disableComputer
|
|
if ($statusDiff) { $totalDiffs++ }
|
|
} else {
|
|
Ensure-GPOStatus -GPOName $gpoName `
|
|
-DisableUserConfiguration $disableUser `
|
|
-DisableComputerConfiguration $disableComputer
|
|
}
|
|
}
|
|
|
|
# --- Pre-Apply Backup ---
|
|
if (-not $TestOnly -and -not $NoBackup) {
|
|
try {
|
|
Backup-GPOState -GPOName $gpoName | Out-Null
|
|
} catch {
|
|
Write-Host " [WARN] Backup failed: $($_.Exception.Message)" -ForegroundColor Yellow
|
|
Write-Host " Continuing with apply..." -ForegroundColor Yellow
|
|
}
|
|
}
|
|
|
|
# --- Management Permissions ---
|
|
foreach ($mgmtGroup in $ManagementGroups) {
|
|
if ($TestOnly) {
|
|
$permDiff = Compare-GPOManagementPermission -GPOName $gpoName -GroupName $mgmtGroup
|
|
if ($permDiff) { $totalDiffs++ }
|
|
} else {
|
|
Ensure-GPOManagementPermission -GPOName $gpoName -GroupName $mgmtGroup
|
|
}
|
|
}
|
|
|
|
# --- Version scope tracking (for deferred bump at end of GPO) ---
|
|
$bumpMachine = $false
|
|
$bumpUser = $false
|
|
|
|
# --- Restricted Groups -> merge into SecurityPolicy ---
|
|
if ($settings.RestrictedGroups -and $settings.RestrictedGroups.Count -gt 0) {
|
|
if ($settings.SecurityPolicy -and $settings.SecurityPolicy.ContainsKey('Group Membership')) {
|
|
throw "GPO '$gpoName': Use RestrictedGroups OR SecurityPolicy['Group Membership'], not both."
|
|
}
|
|
$gmEntries = ConvertTo-RestrictedGroupEntries -RestrictedGroups $settings.RestrictedGroups
|
|
if (-not $settings.SecurityPolicy) { $settings.SecurityPolicy = @{} }
|
|
$settings.SecurityPolicy['Group Membership'] = $gmEntries
|
|
}
|
|
|
|
# --- Security Policy (GptTmpl.inf) ---
|
|
if ($settings.SecurityPolicy -and $settings.SecurityPolicy.Count -gt 0) {
|
|
if ($TestOnly) {
|
|
Write-Host " Comparing security policy..." -ForegroundColor Yellow
|
|
$diffs = Compare-GPOSecurityPolicy -GPOName $gpoName -SecurityPolicy $settings.SecurityPolicy
|
|
|
|
if ($diffs.Count -eq 0) {
|
|
Write-Host " [OK] Security policy matches desired state" -ForegroundColor Green
|
|
} else {
|
|
Write-Host " [DRIFT] $($diffs.Count) difference(s) found:" -ForegroundColor Red
|
|
foreach ($diff in $diffs) {
|
|
Write-Host " [$($diff.Section)] $($diff.Setting): '$($diff.Current)' -> '$($diff.Desired)'" -ForegroundColor Red
|
|
}
|
|
$totalDiffs += $diffs.Count
|
|
}
|
|
} else {
|
|
Write-Host " Applying security policy..." -ForegroundColor Yellow
|
|
Set-GPOSecurityPolicy -GPOName $gpoName -SecurityPolicy $settings.SecurityPolicy
|
|
$bumpMachine = $true
|
|
}
|
|
} else {
|
|
Write-Host " No security policy settings defined." -ForegroundColor DarkGray
|
|
}
|
|
|
|
# --- Registry Settings (Administrative Templates) ---
|
|
# Note: Set-GPRegistryValue handles version bumping internally — no manual bump needed
|
|
if ($settings.RegistrySettings -and $settings.RegistrySettings.Count -gt 0) {
|
|
if ($TestOnly) {
|
|
Write-Host " Comparing registry settings..." -ForegroundColor Yellow
|
|
$regDiffs = Compare-GPORegistrySettings -GPOName $gpoName -RegistrySettings $settings.RegistrySettings
|
|
|
|
if ($regDiffs.Count -eq 0) {
|
|
Write-Host " [OK] Registry settings match desired state" -ForegroundColor Green
|
|
} else {
|
|
Write-Host " [DRIFT] $($regDiffs.Count) registry difference(s) found:" -ForegroundColor Red
|
|
foreach ($diff in $regDiffs) {
|
|
Write-Host " $($diff.Key)\$($diff.ValueName): '$($diff.Current)' -> '$($diff.Desired)'" -ForegroundColor Red
|
|
}
|
|
$totalDiffs += $regDiffs.Count
|
|
}
|
|
} else {
|
|
Write-Host " Applying registry settings..." -ForegroundColor Yellow
|
|
Set-GPORegistrySettings -GPOName $gpoName -RegistrySettings $settings.RegistrySettings `
|
|
-Cleanup:(-not $NoCleanup)
|
|
}
|
|
}
|
|
|
|
# --- GPO Link(s) ---
|
|
if ($settings.LinkTo) {
|
|
$links = Normalize-GPOLinkTo -LinkTo $settings.LinkTo
|
|
foreach ($link in $links) {
|
|
if ($TestOnly) {
|
|
$linkDiffs = Compare-GPOLink -GPOName $gpoName -TargetOU $link.Target `
|
|
-Order $link.Order -Enforced $link.Enforced
|
|
$totalDiffs += @($linkDiffs).Count
|
|
} else {
|
|
Ensure-GPOLink -GPOName $gpoName -TargetOU $link.Target `
|
|
-Order $link.Order -Enforced $link.Enforced
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- Security Filtering (Deny Apply) ---
|
|
if ($settings.SecurityFiltering -and $settings.SecurityFiltering.DenyApply) {
|
|
if ($TestOnly) {
|
|
Write-Host " Checking security filtering..." -ForegroundColor Yellow
|
|
$filterDiffs = Compare-GPOSecurityFiltering -GPOName $gpoName -DenyApply $settings.SecurityFiltering.DenyApply
|
|
$totalDiffs += @($filterDiffs).Count
|
|
} else {
|
|
Ensure-GPOSecurityFiltering -GPOName $gpoName -DenyApply $settings.SecurityFiltering.DenyApply
|
|
}
|
|
}
|
|
|
|
# --- WMI Filter ---
|
|
if ($settings.WMIFilter) {
|
|
$wmiDesc = if ($settings.WMIFilter.Description) { $settings.WMIFilter.Description } else { '' }
|
|
if ($TestOnly) {
|
|
$wmiDiffs = Compare-GPOWmiFilter -GPOName $gpoName -FilterName $settings.WMIFilter.Name `
|
|
-Description $wmiDesc -Query $settings.WMIFilter.Query
|
|
$totalDiffs += @($wmiDiffs).Count
|
|
} else {
|
|
Ensure-GPOWmiFilter -GPOName $gpoName -FilterName $settings.WMIFilter.Name `
|
|
-Description $wmiDesc -Query $settings.WMIFilter.Query
|
|
}
|
|
}
|
|
|
|
# --- Scripts (Startup / Shutdown / Logon / Logoff) ---
|
|
if ($settings.Scripts) {
|
|
if ($TestOnly) {
|
|
$scriptDiffs = Compare-GPOScripts -GPOName $gpoName -Scripts $settings.Scripts -SourceDir $dir.FullName
|
|
$totalDiffs += @($scriptDiffs).Count
|
|
} else {
|
|
Set-GPOScripts -GPOName $gpoName -Scripts $settings.Scripts -SourceDir $dir.FullName
|
|
foreach ($type in $settings.Scripts.Keys) {
|
|
if ($type -like 'Machine*') { $bumpMachine = $true }
|
|
if ($type -like 'User*') { $bumpUser = $true }
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- Advanced Audit Policy (audit.csv) ---
|
|
if ($settings.AdvancedAuditPolicy -and $settings.AdvancedAuditPolicy.Count -gt 0) {
|
|
if ($TestOnly) {
|
|
$auditDiffs = Compare-GPOAdvancedAuditPolicy -GPOName $gpoName -AuditPolicy $settings.AdvancedAuditPolicy
|
|
$totalDiffs += @($auditDiffs).Count
|
|
} else {
|
|
Set-GPOAdvancedAuditPolicy -GPOName $gpoName -AuditPolicy $settings.AdvancedAuditPolicy
|
|
$bumpMachine = $true
|
|
}
|
|
}
|
|
|
|
# --- Group Policy Preferences ---
|
|
if ($settings.Preferences) {
|
|
if ($TestOnly) {
|
|
$prefDiffs = Compare-GPOPreferences -GPOName $gpoName -Preferences $settings.Preferences
|
|
$totalDiffs += @($prefDiffs).Count
|
|
} else {
|
|
Set-GPOPreferences -GPOName $gpoName -Preferences $settings.Preferences
|
|
foreach ($typeName in $settings.Preferences.Keys) {
|
|
switch ($typeName) {
|
|
'DriveMaps' { $bumpUser = $true }
|
|
'Printers' { $bumpUser = $true }
|
|
'Services' { $bumpMachine = $true }
|
|
'NetworkShares' { $bumpMachine = $true }
|
|
'LocalUsersAndGroups' { $bumpMachine = $true }
|
|
default {
|
|
foreach ($item in $settings.Preferences[$typeName]) {
|
|
if ($item.Scope -eq 'User') { $bumpUser = $true } else { $bumpMachine = $true }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- Firewall Profiles ---
|
|
if ($settings.FirewallProfiles) {
|
|
if ($TestOnly) {
|
|
$fwProfileDiffs = Compare-GPOFirewallProfiles -GPOName $gpoName -FirewallProfiles $settings.FirewallProfiles
|
|
$totalDiffs += @($fwProfileDiffs).Count
|
|
} else {
|
|
Set-GPOFirewallProfiles -GPOName $gpoName -FirewallProfiles $settings.FirewallProfiles
|
|
}
|
|
}
|
|
|
|
# --- Firewall Rules ---
|
|
if ($settings.FirewallRules -and $settings.FirewallRules.Count -gt 0) {
|
|
if ($TestOnly) {
|
|
$fwDiffs = Compare-GPOFirewall -GPOName $gpoName -FirewallRules $settings.FirewallRules
|
|
$totalDiffs += @($fwDiffs).Count
|
|
} else {
|
|
Set-GPOFirewall -GPOName $gpoName -FirewallRules $settings.FirewallRules
|
|
}
|
|
}
|
|
|
|
# --- AppLocker Policy ---
|
|
if ($settings.AppLockerPolicy) {
|
|
if ($TestOnly) {
|
|
$alDiffs = Compare-GPOAppLockerPolicy -GPOName $gpoName -AppLockerPolicy $settings.AppLockerPolicy
|
|
$totalDiffs += @($alDiffs).Count
|
|
} else {
|
|
Set-GPOAppLockerPolicy -GPOName $gpoName -AppLockerPolicy $settings.AppLockerPolicy
|
|
}
|
|
}
|
|
|
|
# --- WDAC Policy ---
|
|
if ($settings.WDACPolicy) {
|
|
if ($TestOnly) {
|
|
$wdacDiffs = Compare-GPOWdacPolicy -GPOName $gpoName -WDACPolicy $settings.WDACPolicy -SourceDir $dir.FullName
|
|
$totalDiffs += @($wdacDiffs).Count
|
|
} else {
|
|
Set-GPOWdacPolicy -GPOName $gpoName -WDACPolicy $settings.WDACPolicy -SourceDir $dir.FullName
|
|
$bumpMachine = $true
|
|
}
|
|
}
|
|
|
|
# --- Folder Redirection ---
|
|
if ($settings.FolderRedirection) {
|
|
if ($TestOnly) {
|
|
$frDiffs = Compare-GPOFolderRedirection -GPOName $gpoName -FolderRedirection $settings.FolderRedirection
|
|
$totalDiffs += @($frDiffs).Count
|
|
} else {
|
|
Set-GPOFolderRedirection -GPOName $gpoName -FolderRedirection $settings.FolderRedirection
|
|
$bumpUser = $true
|
|
}
|
|
}
|
|
|
|
# --- Deferred Version Bump (single bump per GPO, scope-aware) ---
|
|
if (-not $TestOnly -and ($bumpMachine -or $bumpUser)) {
|
|
$scope = if ($bumpMachine -and $bumpUser) { 'Both' }
|
|
elseif ($bumpMachine) { 'Machine' }
|
|
else { 'User' }
|
|
Update-GPOVersion -GPOName $gpoName -Scope $scope
|
|
Write-Host " GPO version bumped ($scope)." -ForegroundColor Green
|
|
}
|
|
|
|
Write-Host ''
|
|
}
|
|
|
|
# -------------------------------------------------------------------
|
|
# Summary
|
|
# -------------------------------------------------------------------
|
|
if ($TestOnly) {
|
|
if ($totalDiffs -eq 0) {
|
|
Write-Host 'All GPO settings 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 GPO settings applied.' -ForegroundColor Green
|
|
}
|
|
|
|
# -------------------------------------------------------------------
|
|
# Optional: Trigger group policy refresh
|
|
# -------------------------------------------------------------------
|
|
if ($GpUpdate -and -not $TestOnly) {
|
|
Write-Host ''
|
|
Write-Host 'Running gpupdate /force to process updated policies...' -ForegroundColor Cyan
|
|
$gpResult = gpupdate /force 2>&1
|
|
Write-Host ($gpResult | Out-String) -ForegroundColor Gray
|
|
Write-Host 'Group Policy refresh complete.' -ForegroundColor Green
|
|
} elseif (-not $TestOnly) {
|
|
Write-Host ''
|
|
Write-Host 'TIP: Run with -GpUpdate to trigger gpupdate /force after applying.' -ForegroundColor DarkGray
|
|
Write-Host ' Or manually: gpupdate /force' -ForegroundColor DarkGray
|
|
}
|