# 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 }