src/public/New-Passphrase.ps1

#Requires -Module PwnedPassCheck

#TODO: Support for passphrases?
#TODO: Test rate limiting better, possibly reduce/remove it to speed up the process

<#
.SYNOPSIS
Generates one or more passphrases based on the specified parameters.
 
.DESCRIPTION
The New-Passphrase function generates memorable passphrases by randomly selecting words from a word list
and combining them with separators and optional numbers. It supports customizable word count, separator
characters, case formatting, and optional pwned password checking.
 
.PARAMETER WordCount
Specifies the number of words to include in the passphrase. Default is 3.
Alias: Words
 
.PARAMETER Separator
Specifies the character used to separate words in the passphrase. Valid values: "-", "_", "None", ".", ",", "+", "=". Default is "-".
 
.PARAMETER Case
Specifies the case formatting for words in the passphrase. Valid values: "Lowercase", "Uppercase", "Titlecase", "RandomCase". Default is "Titlecase".
 
.PARAMETER ExcludeNumbers
Excludes numbers from the end of the passphrase. By default, a random number (0-99) is appended.
 
.PARAMETER PwnCheck
Checks if the generated passphrase has been pwned using the PwnedPassCheck module. If pwned, generates a new passphrase.
 
.PARAMETER NumberOfPassphrases
Specifies the number of passphrases to generate. Default is 1.
 
.PARAMETER OutputPath
Specifies the file path to save the generated passphrases. If specified, passphrases are appended to the file.
 
.PARAMETER CopyToClipboard
Copies the generated passphrases to the clipboard.
 
.PARAMETER NoProgress
Suppresses the progress bar display during passphrase generation.
 
.EXAMPLE
New-Passphrase
 
Generates a single passphrase with 3 titlecase words separated by hyphens and a random number at the end.
 
.EXAMPLE
New-Passphrase -WordCount 4 -Separator "_" -Case Uppercase -ExcludeNumbers
 
Generates a passphrase with 4 uppercase words separated by underscores without numbers.
 
.EXAMPLE
New-Passphrase -NumberOfPassphrases 5 -PwnCheck -OutputPath "C:\Passphrases.txt"
 
Generates 5 passphrases, checks if they've been pwned, and saves them to the specified file.
 
.EXAMPLE
New-Passphrase -Case RandomCase -Separator "None" -CopyToClipboard
 
Generates a passphrase with random case words without separators and copies it to the clipboard.
 
.NOTES
Requires the PwnedPassCheck module for the PwnCheck functionality.
Word list is retrieved from https://raw.githubusercontent.com/srsbod/PassPhraseWordList/refs/heads/main/en.txt
#>

function New-Passphrase {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "", Justification = "Not actually changing anything on the system.")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "", Justification = "Write-Host is fine here.")]
    [CmdletBinding(DefaultParameterSetName = "Simple")]
    param (
        [Parameter(Mandatory = $false)]
        [Alias("Words")]
        [int]$WordCount = 3,
        [Parameter(Mandatory = $false)]
        [ValidateSet("-", "_", "None", ".", ",", "+", "=")]
        [string]$Separator = "-",
        [Parameter(Mandatory = $false)]
        [ValidateSet("Lowercase", "Uppercase", "Titlecase", "RandomCase")]
        [string]$Case = "Titlecase",
        [Parameter(Mandatory = $false)]
        [switch]$ExcludeNumbers,
        [Parameter(Mandatory = $false)]
        [Switch]$PwnCheck,
        [Parameter(Mandatory = $false)]
        [int]$NumberOfPassphrases = 1,
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$OutputPath,
        [Parameter(Mandatory = $false)]
        [Switch]$CopyToClipboard,
        [Parameter(mandatory = $false)]
        [switch]$NoProgress
    )

    begin {
        $Passphrases = @()
        $GeneratedPassphrases = 0
        $WordListURL = "https://raw.githubusercontent.com/srsbod/PassPhraseWordList/refs/heads/main/en.txt"
        try {
            $WordList = (Invoke-RestMethod -Uri $WordListURL) -split "`n" | Where-Object { $_.Trim() -ne "" }
        } catch {
            Write-Error "Failed to retrieve word list from $WordListURL"
            exit 1
        }
    }

    process {

        while ($GeneratedPassphrases -lt $NumberOfPassphrases) {

            if (-not $NoProgress) {
                $ProgressPercentage = [math]::Round(($GeneratedPassphrases / $NumberOfPassphrases) * 100)
                Write-Progress -Activity "Generating Passphrases" -Status "$ProgressPercentage% Complete:" -PercentComplete $ProgressPercentage
            }
            
            $Words = @()
            for ($i = 0; $i -lt $WordCount; $i++) {
                $RandomWord = Get-Random -InputObject $WordList
                switch ($Case) {
                    "Lowercase" { $RandomWord = $RandomWord.ToLower() }
                    "Uppercase" { $RandomWord = $RandomWord.ToUpper() }
                    "Titlecase" { $RandomWord = ($RandomWord.Substring(0, 1).ToUpper() + $RandomWord.Substring(1).ToLower()) }
                    "RandomCase" {
                        if (Get-Random -Minimum 0 -Maximum 2 -AsBoolean) {
                            $RandomWord = $RandomWord.ToUpper()
                        } else {
                            $RandomWord = $RandomWord.ToLower()
                        }
                    }
                }
                $Words += $RandomWord
            }
            
            if ($Separator -eq "None") {
                $Separator = ""
            }
            
            $Passphrase = $Words -join $Separator
            
            if (-not $ExcludeNumbers) {
                $RandomNumber = Get-Random -Minimum 0 -Maximum 99
                $Passphrase += $RandomNumber
            }
               

            if ($PwnCheck) {
                $Pwned = Get-PwnedPassword $Passphrase | Select-Object -ExpandProperty SeenCount
                if ($Pwned -gt 0) {
                    Write-Host "Passphrase $Passphrase is pwned, generating a new one instead - Pwned count: $Pwned" -ForegroundColor Yellow
                    continue
                }
            }

            $Passphrases += $Passphrase
            $GeneratedPassphrases++
        }

        if ($OutputPath) {
            $Passphrases | Out-File $OutputPath -Append -Force
        }

        if ($CopyToClipboard) {
            $Passphrases | Set-Clipboard
        }

        Write-Output $Passphrases
    }

    end {

    }
}