Core/Get-TestVaultSecret.ps1

function Get-TestVaultSecret {
    <#
    .SYNOPSIS
        Reads the service app's private key back out of a SecretStore vault

    .DESCRIPTION
        The inverse of Set-TestVaultSecret, and the path Connect-TestEnvironment takes
        when the credential record says the key lives in a vault rather than the certificate
        store.

        The store is unlocked before the read rather than after a failure. A locked store's read
        error is a prompt on an interactive host and a hang on any other, so waiting to discover
        it is the one ordering that has no good outcome.

        If SecretManagement is absent the error names the fix rather than the symptom: the key
        is in a vault this machine cannot open, and either the module is installed or a new key
        is minted.

    .PARAMETER VaultName
        Vault holding the secret

    .PARAMETER SecretName
        Name of the secret

    .PARAMETER VaultPassword
        Password to unlock the store with. When none is supplied the module default is tried,
        then the default of each of the three earlier modules, because the store is shared per
        user and may have been configured by any of them.

    .OUTPUTS
        System.String, the base64-encoded PFX.

    .EXAMPLE
        PS> Get-TestVaultSecret -VaultName TestEnvironment -SecretName $name

        DESCRIPTION: Retrieves the stored private key
        OUTPUT: The base64-encoded PFX
        USE CASE: Called by Connect-TestEnvironment -UseSecretStore

    .NOTES
        Author: Jeffrey Stuhr
        Blog: https://www.techbyjeff.net
        LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '',
        Justification = 'Default lab vault password, matching the one Initialize-TestSecretVault registers with.')]
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$VaultName,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$SecretName,

        [Parameter()]
        [System.Security.SecureString]$VaultPassword
    )

    if (-not (Get-Module -ListAvailable -Name Microsoft.PowerShell.SecretManagement)) {
        Write-Error ("The private key is stored in the SecretStore vault '$VaultName', but " +
            'Microsoft.PowerShell.SecretManagement is not installed on this machine. Install it, or run ' +
            'New-TestServiceApp -Force to mint a new key.') -ErrorAction Stop
        return
    }

    Import-Module Microsoft.PowerShell.SecretManagement -ErrorAction Stop -Verbose:$false

    if (Get-Module -ListAvailable -Name Microsoft.PowerShell.SecretStore) {
        Import-Module Microsoft.PowerShell.SecretStore -ErrorAction SilentlyContinue -Verbose:$false

        # The same ladder Initialize-TestSecretVault climbs, for the same reason. The store is
        # per USER and shared with everything that has ever configured it, and each of the
        # three earlier modules had its own default - so a store that OktaTestEnvironment set
        # up is one this module's default cannot open. Trying only the current default here
        # meant Connect-TestEnvironment -UseSecretStore failed on exactly the machines that
        # had used those modules, with a message telling the caller to unlock a store they
        # had never been asked for a password to.
        #
        # A caller who names a password gets that password alone: the legacy defaults are a
        # fallback for the unattended case, not an override of an explicit choice.
        $attempts = if ($VaultPassword) {
            @($VaultPassword)
        }
        else {
            foreach ($default in 'TestEnvironmentPassword', 'OktaTestEnvironmentPassword',
                                 'ADTestEnvironmentPassword', 'EntraTestEnvironmentPassword') {
                ConvertTo-SecureString -String $default -AsPlainText -Force
            }
        }

        foreach ($attempt in $attempts) {
            try {
                Unlock-SecretStore -Password $attempt -ErrorAction Stop
                break
            }
            catch {
                # Wrong password, or a store that needs no unlocking at all. Either way the
                # read below is the authority on whether the store is open, and its error
                # names the fix.
                Write-Verbose "Unlock-SecretStore declined this password: $($_.Exception.Message)"
            }
        }
    }

    $secret = $null
    try {
        $secret = Get-Secret -Name $SecretName -Vault $VaultName -ErrorAction Stop
    }
    catch {
        throw (New-Object System.Exception(
            "Could not read secret '$SecretName' from vault '$VaultName': $($_.Exception.Message). " +
            'If the vault is locked, unlock it with Unlock-SecretStore.', $_.Exception))
    }

    if (-not $secret) {
        Write-Error "Secret '$SecretName' was not found in vault '$VaultName'." -ErrorAction Stop
        return
    }

    if ($secret -is [System.Security.SecureString]) {
        # Marshalled back rather than round-tripped through ConvertFrom-SecureString, which off
        # Windows returns hex-encoded plaintext instead of anything encrypted and would quietly
        # produce the wrong string here.
        $pointer = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secret)
        try { return [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($pointer) }
        finally { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($pointer) }
    }

    return [string]$secret
}