src/Get-RandomPassword.ps1
function Get-RandomPassword { <# .EXTERNALHELP ..\PSPasswordGen-help.xml #> [CmdletBinding()] Param( [ValidateRange(1, [int]::MaxValue)][int]$Length = 12, [ValidateRange(1, [int]::MaxValue)][int]$Count = 20, [switch]$NoNumerals, [switch]$NoLowerCase, [switch]$NoUpperCase, [switch]$NoSymbols, [switch]$NoAmbiguous, [switch]$NoVowels, [switch]$NoMetaCharacters, [switch]$NoNeedToEscape, [char[]]$ExcludeChars = @() ) $UIMessages = data {} try { Import-LocalizedData -BindingVariable UIMessages -ErrorAction Stop } catch { Import-LocalizedData -BindingVariable UIMessages -UICulture 'en-US' } Set-Variable -Name SupportPowerShellVersion -Value 3 -Option ReadOnly if($PSVersionTable.PSVersion.Major -lt $SupportPowerShellVersion) { Write-Warning -Message $UIMessages.ThisVersionIsNotSupported.Replace('$SupportPowerShellVersion', $SupportPowerShellVersion) } function ContainsAny { param( [System.Collections.Generic.IEnumerable[char]]$One, [System.Collections.Generic.IEnumerable[char]]$Another ) $oneSet = New-Object -TypeName "System.Collections.Generic.HashSet[char]" -ArgumentList $One $oneSet.IntersectWith($Another) $oneSet.Count -gt 0 } Set-Variable -Name ByteMaxValueDividedByPasswordLength -Value ([System.Math]::Truncate([byte]::MaxValue / $Length)) -Option ReadOnly Set-Variable -Name BytesLengthToRepresentPasswordLength -Value ([System.Math]::Truncate([System.Math]::Log([uint32]::MaxValue+1, [byte]::MaxValue+1))) -Option ReadOnly Set-Variable -Name NumeralChars -Value '0123456789' -Option ReadOnly Set-Variable -Name LowerCaseChars -Value 'abcdefghijklmnopqrstuvwxyz' -Option ReadOnly Set-Variable -Name UpperCaseChars -Value 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' -Option ReadOnly Set-Variable -Name SymbolChars -Value '!\"#$%&''()*+,-./:;<=>?@[\]^_`{|}~' -Option ReadOnly Set-Variable -Name NumeralsPattern -Value (New-Object -TypeName "Regex" -ArgumentList "[$($NumeralChars)]") -Option ReadOnly Set-Variable -Name LowerPattern -Value (New-Object -TypeName "Regex" -ArgumentList "[$($LowerCaseChars)]") -Option ReadOnly Set-Variable -Name UpperPattern -Value (New-Object -TypeName "Regex" -ArgumentList "[$($UpperCaseChars)]") -Option ReadOnly Set-Variable -Name SymbolPattern -Value (New-Object -TypeName "Regex" -ArgumentList "[$($SymbolChars -replace '([\[\]\\])', '\$1')]") -Option ReadOnly Set-Variable -Name CandidateCharSet -Value (New-Object -TypeName "System.Collections.Generic.HashSet[char]" -ArgumentList ($NumeralChars + $LowerCaseChars + $UpperCaseChars + $SymbolChars)) -Option ReadOnly $CandidateCharSet.ExceptWith([char[]]$ExcludeChars) if($NoNumerals) { $CandidateCharSet.ExceptWith([char[]]$NumeralChars) } if($NoLowerCase) { $CandidateCharSet.ExceptWith([char[]]$LowerCaseChars) } if($NoUpperCase) { $CandidateCharSet.ExceptWith([char[]]$UpperCaseChars) } if($NoSymbols) { $CandidateCharSet.ExceptWith([char[]]$SymbolChars) } if($NoAmbiguous) { $CandidateCharSet.ExceptWith([char[]]'B8G6I1l0OQDS5Z2') } if($NoVowels) { $CandidateCharSet.ExceptWith([char[]]'01aeiouyAEIOUY') } if($NoMetaCharacters) { $CandidateCharSet.ExceptWith([char[]]'!"#$%&''()*,-;<=>?@[\]^`{|}~') } if($NoNeedToEscape) { $CandidateCharSet.ExceptWith([char[]]'!"$%''-\`') } Write-Verbose $UIMessages.TheseAreCandidateChars.Replace('$CandidateCharSet', $([System.Linq.Enumerable]::ToArray($CandidateCharSet) -join '')) if($CandidateCharSet.Count -eq 0) { Write-Error -Message $UIMessages.ThereAreNoCharacters -ErrorAction Stop } if((-not $NoNumerals) -and -not (ContainsAny -One $CandidateCharSet -Another $NumeralChars)) { Write-Error -Message $UIMessages.ThereAreNoNumerals -ErrorAction Stop } if((-not $NoLowerCase) -and -not (ContainsAny -One $CandidateCharSet -Another $LowerCaseChars)) { Write-Error -Message $UIMessages.ThereAreNoLowerCase -ErrorAction Stop } if((-not $NoUpperCase) -and -not (ContainsAny -One $CandidateCharSet -Another $UpperCaseChars)) { Write-Error -Message $UIMessages.ThereAreNoUpperCase -ErrorAction Stop } if((-not $NoSymbols) -and -not (ContainsAny -One $CandidateCharSet -Another $SymbolChars)) { Write-Error -Message $UIMessages.ThereAreNoSymbols -ErrorAction Stop } $CandidateCharList=[System.Linq.Enumerable]::ToArray($CandidateCharSet) for($i = 0; $i -lt $Count; $i++) { $password="" $randomGenerator = [System.Security.Cryptography.RandomNumberGenerator]::Create() while($password.length -lt $Length) { [int]$index=-1 do { [byte[]]$randomBytes=@(0) $randomGenerator.GetBytes($randomBytes) $index=$randomBytes[0] } while($index -ge $CandidateCharList.Length) if($password.length -lt $Length - 1) { $password=$password+$CandidateCharList[$index] } else { [int]$position=-1 while($true) { [byte[]]$randomBytes=New-Object -TypeName byte[] -ArgumentList $BytesLengthToRepresentPasswordLength $randomGenerator.GetBytes($randomBytes) [Array]::Resize([ref]$randomBytes, 4) $randomInt32=[System.BitConverter]::ToInt32($randomBytes, 0) -band [int]::MaxValue $position=$randomInt32 % $Length if($ByteMaxValueDividedByPasswordLength -eq 0 -or $randomBytes[0] / $Length -le $ByteMaxValueDividedByPasswordLength) { break } } $password=$password.substring(0,$position)+$CandidateCharList[$index]+$password.substring($position) $various = 0 Write-Debug "Candidate password is $($password)" if($NoNumerals -or $NumeralsPattern.IsMatch($password)) { $various++ } else { Write-Debug "Candidate password has no numerals." } if($NoLowerCase -or $LowerPattern.IsMatch($password)) { $various++ } else { Write-Debug "Candidate password has no lower case letters." } if($NoUpperCase -or $UpperPattern.IsMatch($password)) { $various++ } else { Write-Debug "Candidate password has no upper case letters." } if($NoSymbols -or $SymbolPattern.IsMatch($password)) { $various++ } else { Write-Debug "Candidate password has no symbols." } if($various -lt [Math]::Min(4, $Length)) { $password = $password.Remove(0, 1) } } } $password } } |