PowerShell Password Generator: Create Secure Random Passwords

Every new user account, every service account reset, and every temporary password in your onboarding script needs a password that is both random and strong. Relying on simple Get-Random with a small character set is insufficient for production use — it is not cryptographically secure. This post builds a PowerShell generate random password solution using .NET’s cryptographic random number generator, enforces complexity rules, creates passphrases, converts output to SecureString, and handles bulk generation for mass provisioning.
Simple Get-Random Character Approach
The basic approach uses Get-Random to select characters from a set. This is acceptable for low-security scenarios like temporary Wi-Fi passwords, but not for AD accounts:
$charset = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%'
$length = 12
$password = -join (1..$length | ForEach-Object { Get-Random -InputObject $charset.ToCharArray() })
Write-Host "Generated: $password"
Generated: mK8!vQ2wXp#L
The limitation: Get-Random uses a pseudo-random number generator seeded from the system clock. In tight loops or adversarial scenarios, the output is predictable.
Cryptographically Secure with RNGCryptoServiceProvider
For production use, use System.Security.Cryptography.RandomNumberGenerator (.NET 6+) or the legacy RNGCryptoServiceProvider to ensure each byte comes from a cryptographically secure source:
function New-CryptoPassword {
param(
[int]$Length = 16,
[string]$Charset = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*'
)
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$bytes = [byte[]]::new($Length)
$rng.GetBytes($bytes)
$chars = $Charset.ToCharArray()
$password = -join ($bytes | ForEach-Object { $chars[$_ % $chars.Length] })
$rng.Dispose()
$password
}
$pwd = New-CryptoPassword -Length 16
Write-Host "Secure password: $pwd"
Ensure Complexity Rules Met
After generating the random string, verify it satisfies the complexity requirements. If it does not, regenerate until it does:
function New-ComplexPassword {
param([int]$Length = 14)
do {
$pwd = New-CryptoPassword -Length $Length
$hasUpper = $pwd -cmatch '[A-Z]'
$hasLower = $pwd -cmatch '[a-z]'
$hasDigit = $pwd -match '\d'
$hasSpecial = $pwd -match '[!@#$%^&*]'
$complex = $hasUpper -and $hasLower -and $hasDigit -and $hasSpecial
} while (-not $complex)
$pwd
}
Write-Host (New-ComplexPassword -Length 14)
Generate Passphrase from Word List
Passphrases — sequences of random words — are both more memorable and higher entropy than character-based passwords. Combine three or four random words with a separator:
function New-Passphrase {
param(
[int]$WordCount = 4,
[string]$Separator = '-'
)
# Load word list from file or use an inline sample
$wordList = @(
'Cloud','Pepper','Monkey','River','Delta','Copper','Forest','Spark',
'Tiger','Flame','Silver','Maple','Bridge','Storm','Gravel','Turbo'
)
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$buf = [byte[]]::new(4)
$words = 1..$WordCount | ForEach-Object {
$rng.GetBytes($buf)
$idx = [BitConverter]::ToUInt32($buf, 0) % $wordList.Count
$wordList[$idx]
}
$rng.Dispose()
($words -join $Separator) + (Get-Random -Minimum 10 -Maximum 99)
}
Write-Host (New-Passphrase)
Forest-Tiger-Maple-Storm47
Convert to SecureString
AD cmdlets and credential objects require SecureString rather than plain-text strings. Convert your generated password immediately after creation:
$plainText = New-ComplexPassword -Length 16
$secureString = ConvertTo-SecureString $plainText -AsPlainText -Force
# Use directly with Set-ADAccountPassword
Set-ADAccountPassword -Identity "jsmith" -NewPassword $secureString -Reset
# Or create a credential object
$cred = New-Object System.Management.Automation.PSCredential("jsmith", $secureString)
# Clear the plain-text variable from memory when done
Clear-Variable plainText
Bulk Password Generation
Generate a unique password for each user in a CSV file and output the mapping as a separate credentials file:
$users = Import-Csv "C:\HR\NewHires.csv"
$credsReport = $users | ForEach-Object {
$pwd = New-ComplexPassword -Length 14
Set-ADAccountPassword -Identity $_.SamAccountName `
-NewPassword (ConvertTo-SecureString $pwd -AsPlainText -Force) -Reset
[PSCustomObject]@{
Username = $_.SamAccountName
TempPassword = $pwd
CreatedAt = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
}
}
# Export to an encrypted file rather than plain CSV
$credsReport | Export-Csv "C:\HR\TempPasswords_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
Write-Host "Generated $($credsReport.Count) passwords"
Common Errors and Fixes
-
Get-Random is not cryptographically secure — use RNG for production.
Get-Randomuses a seeded pseudo-random generator. The output is statistically random but predictable if an attacker knows the seed time. For passwords, authentication tokens, and anything security-sensitive, always useRandomNumberGenerator.Create()instead. - Character set too small reduces entropy. A 12-character password from only lowercase letters has 26^12 ≈ 95 billion combinations. Adding uppercase, digits, and symbols increases this to 94^12 ≈ 475 trillion combinations. Use a full character set and a minimum length of 14 characters for passwords that must resist brute-force attacks.
Related Cmdlets / See Also
Wrapping Up
Use RandomNumberGenerator.Create() for cryptographically secure passwords, enforce complexity rules with a do-while retry loop, convert to SecureString immediately before passing to AD cmdlets, and clear plain-text password variables from memory as soon as they are no longer needed. For human-memorable secrets, passphrases beat character soup every time.


