public/New-Password.ps1

function New-Password() {
    <#
    .SYNOPSIS
        Generates a new cryptographically secure password as a char array.
 
    .DESCRIPTION
        Generates a new cryptographically secure password with a given length
        using the supplied characters.
     
    .PARAMETER Chars
        An string of characters to use to build the password.
         
    .PARAMETER CharSets
        Optional. An array of choices that leverages a group of characters to use to build
        the password. The default is 'LatinAlphaUpperCase', 'LatinAlphaLowerCase', 'Digits', 'SpecialHybrid'
 
    .PARAMETER Length
        Optional. The length of the password that is generated. The default is 16
 
    .PARAMETER Validate
        Optional. A script block that validates the password to ensure it meets
        standards. The default checks for one uppercase, one lowercase, one digit,
        one special character.
     
    .PARAMETER AsSecureString
        A flag that instructs the result to be returned as a SecureString.
 
    .PARAMETER AsString
        A flag that instructs the result to be returned as a string.
     
    .EXAMPLE
        PS C:\> $pw = New-Password -Length 16 -AsSecureString -CharSets 'LatinAlphaUpperCase', 'LatinAlphaLowerCase', 'Digits', 'SpecialHybrid'
        Generates a new password with 16 characters with 1 upper, 1 lower, 1 digit, and one special character.
 
    #>

    Param(

        [int] $Length,

        [ValidateSet('LatinAlphaUpperCase', 'LatinAlphaLowerCase', 'Digits', 'Hyphen', 'Underscore', 'Brackets', 'Special', 'Space')]
        [string[]] $CharSets = $null,
        
        [string] $Chars = $null,
        
        [ScriptBlock] $Validate = $null,
        
        [Switch] $AsSecureString,

        [Switch] $AsString
    )

    if($Length -eq 0) {
        $Length = 16;
    }

    $sb = New-Object System.Text.StringBuilder
    if(!$CharSets -and $CharSets.Length -gt 0) {
        $set = (Merge-PasswordCharSets $CharSets)
        if($set -and $set.Length -gt 0) {
            $sb.Append($set) | Out-Null  
        }
    }

    if(![string]::IsNullOrWhiteSpace($Chars)) {
        $sb.Append($Chars)  | Out-Null  
    } 

    if($sb.Length -eq 0) {
        $sets = Merge-PasswordCharSets -CharSets (@('LatinAlphaUpperCase', 'LatinAlphaLowerCase', 'Digits', "SpecialHybrid"))
        $sb.Append($sets)  | Out-Null  
    }

    $permittedChars = $sb.ToString();
    $password = [char[]]@(0) * $Length;
    $bytes = [byte[]]@(0) * $Length;


    while( (Test-Password $password -Validate $Validate) -eq $false) {
        $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
        $rng.GetBytes($bytes);
        $rng.Dispose();

        for($i = 0; $i -lt $Length; $i++) {
            $byte = $bytes[$i]
            if($byte -eq 0) {
                $index = 0;
            } else {
                $index = [int]($byte % $permittedChars.Length)
            }
            $index = [int] ($bytes[$i] % $permittedChars.Length);
            $password[$i] = [char] $permittedChars[$index];
        }
    }

    
    if($AsString.ToBool()) {
        return  (-join $password)
    }
    
    if($AsSecureString.ToBool()) {
        $secureString = New-Object System.Security.SecureString;
        foreach($char in $password) {
            $secureString.AppendChar($char)
        }
        return $secureString;
    }

    # return char array as it is not immutable like a string
    return $password;
}