Infrastructure-as-code framework for Active Directory objects and Group Policy. Sanitized from production deployment for public sharing.
201 lines
7.5 KiB
PowerShell
201 lines
7.5 KiB
PowerShell
# ADUser.ps1
|
|
# User account management.
|
|
# Depends on: ADCore.ps1 (New-RandomPassword)
|
|
|
|
function Ensure-ADUserAccount {
|
|
<#
|
|
.SYNOPSIS
|
|
Idempotently creates a user account, ensures correct OU placement and group membership.
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$SamAccountName,
|
|
|
|
[Parameter(Mandatory)]
|
|
[string]$Name,
|
|
|
|
[string]$GivenName = '',
|
|
[string]$Surname = '',
|
|
|
|
[Parameter(Mandatory)]
|
|
[string]$Path,
|
|
|
|
[bool]$Enabled = $true,
|
|
|
|
[string[]]$MemberOf = @(),
|
|
|
|
[hashtable]$Properties = @{}
|
|
)
|
|
|
|
$created = $false
|
|
|
|
try {
|
|
$user = Get-ADUser -Identity $SamAccountName -Properties MemberOf -ErrorAction Stop
|
|
Write-Host " [OK] User exists: $SamAccountName" -ForegroundColor Green
|
|
|
|
# Check OU placement
|
|
$currentOU = ($user.DistinguishedName -split ',', 2)[1]
|
|
if ($currentOU -ne $Path) {
|
|
Move-ADObject -Identity $user.DistinguishedName -TargetPath $Path
|
|
Write-Host " [MOVED] $SamAccountName -> $Path" -ForegroundColor Yellow
|
|
}
|
|
|
|
# Check enabled state
|
|
if ($user.Enabled -ne $Enabled) {
|
|
Set-ADUser -Identity $SamAccountName -Enabled $Enabled
|
|
Write-Host " [UPDATED] $SamAccountName Enabled=$Enabled" -ForegroundColor Yellow
|
|
}
|
|
|
|
# Check optional properties
|
|
if ($Properties.Count -gt 0) {
|
|
$propNames = @($Properties.Keys)
|
|
$current = Get-ADUser -Identity $SamAccountName -Properties $propNames
|
|
foreach ($prop in $propNames) {
|
|
$desired = $Properties[$prop]
|
|
$actual = $current.$prop
|
|
if ("$actual" -ne "$desired") {
|
|
Set-ADUser -Identity $SamAccountName -Replace @{ $prop = $desired }
|
|
Write-Host " [UPDATED] $SamAccountName $prop='$desired'" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
}
|
|
|
|
} catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
|
|
$password = New-RandomPassword
|
|
$securePass = ConvertTo-SecureString $password -AsPlainText -Force
|
|
|
|
$params = @{
|
|
SamAccountName = $SamAccountName
|
|
Name = $Name
|
|
UserPrincipalName = "$SamAccountName@$((Get-ADDomain).DNSRoot)"
|
|
Path = $Path
|
|
AccountPassword = $securePass
|
|
Enabled = $Enabled
|
|
ChangePasswordAtLogon = $true
|
|
}
|
|
|
|
if ($GivenName) { $params.GivenName = $GivenName }
|
|
if ($Surname) { $params.Surname = $Surname }
|
|
|
|
# Merge optional properties into creation parameters
|
|
foreach ($key in $Properties.Keys) {
|
|
$params[$key] = $Properties[$key]
|
|
}
|
|
|
|
New-ADUser @params
|
|
|
|
# Write password to a secured file -- never to console (avoids transcript leaks)
|
|
$credDir = Join-Path (Split-Path $PSScriptRoot -Parent) '.credentials'
|
|
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
|
|
if (-not (Test-Path $credDir)) {
|
|
New-Item -ItemType Directory -Path $credDir -Force | Out-Null
|
|
|
|
# Lock directory permissions to current user only
|
|
$dirAcl = Get-Acl $credDir
|
|
$dirAcl.SetAccessRuleProtection($true, $false)
|
|
$dirRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
$currentUser, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')
|
|
$dirAcl.AddAccessRule($dirRule)
|
|
Set-Acl -Path $credDir -AclObject $dirAcl
|
|
}
|
|
|
|
$credFile = Join-Path $credDir "$SamAccountName.txt"
|
|
Set-Content -Path $credFile -Value "User: $SamAccountName`nPassword: $password`nCreated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')`nNote: Must change on first login`nDelete this file after handing off the password."
|
|
|
|
# Lock file permissions to current user only
|
|
$acl = Get-Acl $credFile
|
|
$acl.SetAccessRuleProtection($true, $false)
|
|
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
$currentUser, 'FullControl', 'Allow')
|
|
$acl.AddAccessRule($rule)
|
|
Set-Acl -Path $credFile -AclObject $acl
|
|
|
|
Write-Host " [CREATED] User: $SamAccountName (password saved to $credFile)" -ForegroundColor Yellow
|
|
$created = $true
|
|
}
|
|
|
|
# Sync group membership
|
|
foreach ($group in $MemberOf) {
|
|
$currentGroups = @(Get-ADPrincipalGroupMembership -Identity $SamAccountName | Select-Object -ExpandProperty Name)
|
|
if ($group -notin $currentGroups) {
|
|
Add-ADGroupMember -Identity $group -Members $SamAccountName
|
|
Write-Host " [ADDED] $SamAccountName -> $group" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
|
|
return $created
|
|
}
|
|
|
|
function Compare-ADUserAccount {
|
|
<#
|
|
.SYNOPSIS
|
|
Compares desired user state against AD. Returns diffs.
|
|
#>
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$SamAccountName,
|
|
|
|
[Parameter(Mandatory)]
|
|
[string]$Path,
|
|
|
|
[bool]$Enabled = $true,
|
|
|
|
[string[]]$MemberOf = @(),
|
|
|
|
[hashtable]$Properties = @{}
|
|
)
|
|
|
|
$diffs = @()
|
|
|
|
try {
|
|
$user = Get-ADUser -Identity $SamAccountName -Properties MemberOf -ErrorAction Stop
|
|
Write-Host " [OK] User exists: $SamAccountName" -ForegroundColor Green
|
|
|
|
# Check OU placement
|
|
$currentOU = ($user.DistinguishedName -split ',', 2)[1]
|
|
if ($currentOU -ne $Path) {
|
|
Write-Host " [DRIFT] $SamAccountName in $currentOU, expected $Path" -ForegroundColor Red
|
|
$diffs += [PSCustomObject]@{ Type = 'UserOU'; User = $SamAccountName; Current = $currentOU; Desired = $Path }
|
|
}
|
|
|
|
# Check enabled state
|
|
if ($user.Enabled -ne $Enabled) {
|
|
Write-Host " [DRIFT] $SamAccountName Enabled=$($user.Enabled), expected $Enabled" -ForegroundColor Red
|
|
$diffs += [PSCustomObject]@{ Type = 'UserEnabled'; User = $SamAccountName; Current = $user.Enabled; Desired = $Enabled }
|
|
}
|
|
|
|
# Check optional properties
|
|
if ($Properties.Count -gt 0) {
|
|
$propNames = @($Properties.Keys)
|
|
$current = Get-ADUser -Identity $SamAccountName -Properties $propNames
|
|
foreach ($prop in $propNames) {
|
|
$desired = $Properties[$prop]
|
|
$actual = $current.$prop
|
|
if ("$actual" -ne "$desired") {
|
|
Write-Host " [DRIFT] $SamAccountName $prop='$actual', expected '$desired'" -ForegroundColor Red
|
|
$diffs += [PSCustomObject]@{
|
|
Type = 'UserProperty'; User = $SamAccountName
|
|
Property = $prop; Current = $actual; Desired = $desired
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Check group membership
|
|
$currentGroups = @(Get-ADPrincipalGroupMembership -Identity $SamAccountName | Select-Object -ExpandProperty Name)
|
|
foreach ($group in $MemberOf) {
|
|
if ($group -notin $currentGroups) {
|
|
Write-Host " [DRIFT] $SamAccountName not in group $group" -ForegroundColor Red
|
|
$diffs += [PSCustomObject]@{ Type = 'UserGroup'; User = $SamAccountName; Group = $group; Status = 'Missing' }
|
|
}
|
|
}
|
|
|
|
} catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
|
|
Write-Host " [MISSING] User: $SamAccountName" -ForegroundColor Red
|
|
$diffs += [PSCustomObject]@{ Type = 'User'; Name = $SamAccountName; Status = 'Missing' }
|
|
}
|
|
|
|
return $diffs
|
|
}
|