# ADCore.ps1 # Shared utility functions used by multiple AD modules. # Must be loaded before other AD modules (ADUser.ps1 depends on New-RandomPassword). function Get-CryptoRandomInt { <# .SYNOPSIS Returns a cryptographically secure random integer in [0, $Max). #> param([int]$Max) $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() $bytes = [byte[]]::new(4) $rng.GetBytes($bytes) $rng.Dispose() return [Math]::Abs([BitConverter]::ToInt32($bytes, 0)) % $Max } function New-RandomPassword { <# .SYNOPSIS Generates a cryptographically secure random password that meets AD complexity requirements. #> param( [int]$Length = 16 ) $upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ' $lower = 'abcdefghjkmnpqrstuvwxyz' $digits = '23456789' $special = '!@#$%&*?' # Guarantee at least one of each type $chars = [System.Collections.Generic.List[char]]::new() $chars.Add($upper[(Get-CryptoRandomInt -Max $upper.Length)]) $chars.Add($lower[(Get-CryptoRandomInt -Max $lower.Length)]) $chars.Add($digits[(Get-CryptoRandomInt -Max $digits.Length)]) $chars.Add($special[(Get-CryptoRandomInt -Max $special.Length)]) # Fill the rest randomly from all character sets $all = $upper + $lower + $digits + $special for ($i = $chars.Count; $i -lt $Length; $i++) { $chars.Add($all[(Get-CryptoRandomInt -Max $all.Length)]) } # Fisher-Yates shuffle using CSPRNG for ($i = $chars.Count - 1; $i -gt 0; $i--) { $j = Get-CryptoRandomInt -Max ($i + 1) $temp = $chars[$i] $chars[$i] = $chars[$j] $chars[$j] = $temp } return -join $chars }