Damien Coles f172d00514 Initial release: Declarative AD Framework v2.1.0
Infrastructure-as-code framework for Active Directory objects and Group Policy.
Sanitized from production deployment for public sharing.
2026-02-19 17:02:42 +00:00

114 lines
3.8 KiB
PowerShell

# ADGroup.ps1
# Security group management.
# No dependencies on other AD modules.
function Ensure-ADSecurityGroup {
<#
.SYNOPSIS
Idempotently creates a security group and syncs membership.
#>
param(
[Parameter(Mandatory)]
[string]$Name,
[Parameter(Mandatory)]
[string]$Path,
[string]$Description = '',
[ValidateSet('Global', 'DomainLocal', 'Universal')]
[string]$Scope = 'Global',
[string[]]$Members = @()
)
$created = $false
try {
$group = Get-ADGroup -Identity $Name -ErrorAction Stop
Write-Host " [OK] Group exists: $Name" -ForegroundColor Green
} catch {
New-ADGroup -Name $Name -Path $Path -GroupScope $Scope -GroupCategory Security `
-Description $Description -ProtectedFromAccidentalDeletion $true
Write-Host " [CREATED] Group: $Name" -ForegroundColor Yellow
$created = $true
}
# Ensure protected from accidental deletion
$group = Get-ADGroup -Identity $Name -Properties ProtectedFromAccidentalDeletion
if (-not $group.ProtectedFromAccidentalDeletion) {
Set-ADObject -Identity $group.DistinguishedName -ProtectedFromAccidentalDeletion $true
Write-Host " [UPDATED] $Name ProtectedFromAccidentalDeletion=True" -ForegroundColor Yellow
}
# Sync membership
if ($Members.Count -gt 0) {
$currentMembers = @(Get-ADGroupMember -Identity $Name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty SamAccountName)
foreach ($member in $Members) {
if ($member -notin $currentMembers) {
Add-ADGroupMember -Identity $Name -Members $member
Write-Host " [ADDED] $member -> $Name" -ForegroundColor Yellow
}
}
# Remove members not in desired list
foreach ($current in $currentMembers) {
if ($current -notin $Members) {
Remove-ADGroupMember -Identity $Name -Members $current -Confirm:$false
Write-Host " [REMOVED] $current from $Name" -ForegroundColor Red
}
}
}
return $created
}
function Compare-ADSecurityGroup {
<#
.SYNOPSIS
Compares desired group state against AD. Returns diffs.
#>
param(
[Parameter(Mandatory)]
[string]$Name,
[string[]]$Members = @()
)
$diffs = @()
try {
$group = Get-ADGroup -Identity $Name -Properties ProtectedFromAccidentalDeletion -ErrorAction Stop
Write-Host " [OK] Group exists: $Name" -ForegroundColor Green
# Check protection
if (-not $group.ProtectedFromAccidentalDeletion) {
Write-Host " [DRIFT] $Name not protected from accidental deletion" -ForegroundColor Red
$diffs += [PSCustomObject]@{ Type = 'GroupProtection'; Group = $Name; Status = 'Unprotected' }
}
# Check membership
$currentMembers = @(Get-ADGroupMember -Identity $Name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty SamAccountName)
foreach ($member in $Members) {
if ($member -notin $currentMembers) {
Write-Host " [DRIFT] $member not in group $Name" -ForegroundColor Red
$diffs += [PSCustomObject]@{ Type = 'GroupMember'; Group = $Name; Member = $member; Status = 'Missing' }
}
}
foreach ($current in $currentMembers) {
if ($current -notin $Members) {
Write-Host " [DRIFT] $current in group $Name but not in desired state" -ForegroundColor Red
$diffs += [PSCustomObject]@{ Type = 'GroupMember'; Group = $Name; Member = $current; Status = 'Extra' }
}
}
} catch {
Write-Host " [MISSING] Group: $Name" -ForegroundColor Red
$diffs += [PSCustomObject]@{ Type = 'Group'; Name = $Name; Status = 'Missing' }
}
return $diffs
}