functions/New-Password.ps1

# <copyright file="New-Password.ps1" company="Endjin Limited">
# Copyright (c) Endjin Limited. All rights reserved.
# </copyright>

<#
.SYNOPSIS
Returns a random password containing alpha-numerics and symbols.

.DESCRIPTION
Returns a cryptographically secure random-generated password containing upper-case, lower-case, numeric and symbol characters.

.PARAMETER Length
The length of the password to be generated.

.PARAMETER ValidSymbols
A character array containing the characters that will be treated as symbols when validating the password strength.

.PARAMETER KeyVaultName
The optional key vault where the password will be stored.

.PARAMETER KeyVaultSecretName
The key vault secret name where the password will be stored.

.OUTPUTS
SecureString

.EXAMPLE

$pwd = New-Password
$pwd = New-Password -Length 18

#>

function New-Password
{
    [CmdletBinding(DefaultParameterSetName="Default", SupportsShouldProcess)]
    param
    (
        [Parameter(ParameterSetName="Default", Position = 0)]
        [Parameter(ParameterSetName="UseKeyVault", Position = 0)]
        [Parameter(Position = 0)]
        [ValidateRange(12, 256)]
        [int] $Length = 16,

        [Parameter(ParameterSetName="Default")]
        [Parameter(ParameterSetName="UseKeyVault")]
        [ValidateNotNull()]
        [char[]] $ValidSymbols = '!@#$%^&*'.ToCharArray(),

        [Parameter(ParameterSetName="Default")]
        [Parameter(ParameterSetName="UseKeyVault")]
        [switch] $PassThru,

        [Parameter(Mandatory=$true, ParameterSetName="UseKeyVault")]
        [string] $KeyVaultName,
        
        [Parameter(Mandatory=$true, ParameterSetName="UseKeyVault")]
        [string] $KeyVaultSecretName
    )

    $useKeyVault = $PSCmdlet.ParameterSetName -eq "UseKeyVault"

    if ($useKeyVault -and $PSCmdlet.ShouldProcess($KeyVaultName, "Connect to key vault")) {
        _EnsureAzureConnection -AzPowerShell | Out-Null
    }
    
    # reference: https://gist.github.com/onlyann/00d9bb09d4b1338ffc88a213509a6caf
    $characterList = 'a'..'z' + 'A'..'Z' + '0'..'9' + $ValidSymbols
    
    $iterations = 0
    do {
        $password = ""
        for ($i = 0; $i -lt $length; $i++) {
            $randomIndex = [System.Security.Cryptography.RandomNumberGenerator]::GetInt32(0, $characterList.Length)
            $password += $characterList[$randomIndex]
        }

        $hasLowerChar = $password -cmatch '[a-z]'
        $hasUpperChar = $password -cmatch '[A-Z]'
        $hasDigit = $password -match '[0-9]'
        $hasSymbol = $password.IndexOfAny($ValidSymbols) -ne -1

        $iterations++
    }
    until ($hasLowerChar -and $hasUpperChar -and $hasDigit -and $hasSymbol)
    
    Write-Verbose "Password generated after $iterations iterations"
    $securePassword = $password | ConvertTo-SecureString -AsPlainText
    $password = $null

    if ($useKeyVault) {
        if ($PSCmdlet.ShouldProcess($KeyVaultName, "Store password in key vault")) {
            Write-Host "Storing password in key vault [VaultName=$KeyVaultName, SecretName=$KeyVaultSecretName]"
            Set-AzKeyVaultSecret -VaultName $KeyVaultName `
                                    -Name $KeyVaultSecretName `
                                    -SecretValue $securePassword `
                                    -ContentType "text/plain" `
                | Out-Null
        }
    }

    if ($PassThru) {
        $securePassword
    }
}