Functions/New-Password.ps1

<#
.SYNOPSIS
    This function generates a new password.
.DESCRIPTION
    This function generates a new password.
    This function is superior to [System.Web.Security.Membership]::GeneratePassword() as it is able
    to guarantee a minimum number of lowercase characters, uppercase characters and digits.
#>

function New-Password {
    [CmdletBinding(PositionalBinding=$false)]
    param (
        # The number of characters in the password.
        [Parameter(Mandatory=$true)]
        [ValidateScript({ $_ -gt 0 })]
        [Int]$numberOfCharacters,

        # The minimum number of lowercase characters in the password.
        # Set to -1 to indicate no lowercase characters should be present in the password.
        [Parameter(Mandatory=$false)]
        [ValidateScript({ $_ -ge -1 })]
        [Int]$minLowercaseCharacters = 1,

        # The minimum number of uppercase characters in the password.
        # Set to -1 to indicate no uppercase characters should be present in the password.
        [Parameter(Mandatory=$false)]
        [ValidateScript({ $_ -ge -1 })]
        [Int]$minUppercaseCharacters = 1,

        # The minimum number of numerical digits in the password.
        # Set to -1 to indicate no numerical digits should be present in the password.
        [Parameter(Mandatory=$false)]
        [ValidateScript({ $_ -ge -1 })]
        [Int]$minDigits = 1,

        # The minimum number of special characters in the password.
        # Set to -1 to indicate no special characters should be present in the password.
        [Parameter(Mandatory=$false)]
        [ValidateScript({ $_ -ge -1 })]
        [Int]$minSpecialCharacters = 1,

        # A list containing the special characters to use.
        # If this parameter is provided, it will be used as the special character set,
        # instead of the default special character set.
        [Parameter(Mandatory=$false)]
        [ValidateNotNull()]
        [ValidateScript({ $_.length -and !($_ | Where-Object { $_.GetType() -ne [String] }) })]
        [PSObject[]]$specialCharacters
    )

    # Verify inputs
    if ($minLowercaseCharacters + $minUppercaseCharacters + $minDigits + $minSpecialCharacters -gt $numberOfCharacters) {
        Write-Error "Invalid combination of inputs: the sum of the minimum number of each category of characters is greater than the total number of characters."
        return ""
    }
    elseif ($minLowercaseCharacters + $minUppercaseCharacters + $minDigits + $minSpecialCharacters -eq -4) {
        Write-Error "The minimum number of characters in each category will result in an empty password."
        return ""
    }

    # Store password characters as a list
    $password = @()

    # Generate list of lowercase characters (97 to 122 in ASCII)
    $lowercaseCharacters = 97 .. 122 | ForEach-Object -Process {
        "$([Char]$_)"
    }

    # Add lowercase characters to password
    for ($i = 0; $i -lt $minLowercaseCharacters; ++$i) {
        $password += Get-Random -InputObject $lowercaseCharacters -Count 1
    }

    # Generate list of uppercase characters (65 to 90 in ASCII)
    $upperCaseCharacters = 65 .. 90 | ForEach-Object -Process {
        "$([Char]$_)"
    }

    # Add uppercase characters to password
    for ($i = 0; $i -lt $minUppercaseCharacters; ++$i) {
        $password += Get-Random -InputObject $upperCaseCharacters -Count 1
    }

    # Generate list of digits (48 to 57 in ASCII)
    $digits = 48 .. 57 | ForEach-Object -Process {
        "$([Char]$_)"
    }

    # Add digits to password
    for ($i = 0; $i -lt $minDigits; ++$i) {
        $password += Get-Random -InputObject $digits -Count 1
    }

    # Generate list of special characters (33 to 47, 58 to 64, 91 to 96 in ASCII), if not provided
    if (!$specialCharacters) {
        $specialCharacters = ((33 .. 47) + (58 .. 64) + (91 .. 96)) | ForEach-Object -Process {
            "$([Char]$_)"
        }
    }

    # Add special characters to password
    for ($i = 0; $i -lt $minSpecialCharacters; ++$i) {
        $password += Get-Random -InputObject $specialCharacters -Count 1
    }

    # Generate character set containing remaining characters
    $allCharacters = @()
    if ($minLowercaseCharacters -ge 0) {
        $allCharacters += $lowercaseCharacters
    }
    if ($minUppercaseCharacters -ge 0) {
        $allCharacters += $upperCaseCharacters
    }
    if ($minDigits -ge 0) {
        $allCharacters += $digits
    }
    if ($minSpecialCharacters -ge 0) {
        $allCharacters += $specialCharacters
    }

    # Fill up password with remaining characters
    $numRemainingCharacters = $numberOfCharacters - $password.length
    for ($i = 0; $i -lt $numRemainingCharacters; ++$i) {
        $password += Get-Random -InputObject $allCharacters -Count 1
    }

    # Randomly permute password
    $password = $password | Sort-Object { Get-Random }

    # Join password into a string and return
    return ($password -join "")
}