Infrastructure-as-code framework for Active Directory objects and Group Policy. Sanitized from production deployment for public sharing.
114 lines
3.8 KiB
PowerShell
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
|
|
}
|