Core/Export-TestCredentialRecord.ps1

function Export-TestCredentialRecord {
    <#
    .SYNOPSIS
        Writes a provider's credential record, with its secret protected

    .DESCRIPTION
        Every provider that bootstraps a durable identity writes the same kind of record: a few
        public fields naming what the credential is for, and one secret that must never sit on
        disk readable. This is the one place that record is written.

        The secret goes to one of two places, and the record is the authority on which: into a
        SecretStore vault under -UseSecretStore, which is encrypted and portable, or into the
        record itself protected by the platform, which is DPAPI on Windows and unprotected with a
        warning anywhere else. A record written before the vault was proven usable would name a
        secret that was never stored, so the vault is initialised first and the record last.

        The record is written as UTF-8 bytes rather than through Set-Content, which on Windows
        PowerShell prepends a byte order mark that a strict JSON reader rejects. The folder is
        restricted to the current user before the file exists in it, and the file again
        afterwards.

    .PARAMETER Path
        Where to write the record.

    .PARAMETER Record
        The public fields, in the order they should appear. schemaVersion and createdUtc belong
        to the caller; the protection fields are added here.

    .PARAMETER Secret
        The secret to protect.

    .PARAMETER SecretField
        The record field the protected secret is written to when it stays in the record, such as
        tokenProtected or passwordProtected. Import-TestCredentialRecord reads the same field.

    .PARAMETER SecretName
        The name the secret is stored under in the vault, under -UseSecretStore.

    .PARAMETER UseSecretStore
        Keep the secret in a SecretStore vault rather than in the record.

    .PARAMETER VaultName
        The vault to use with -UseSecretStore.

    .PARAMETER VaultPassword
        The vault's password, when it is not the module default.

    .OUTPUTS
        PSCustomObject with Path, Protection, VaultName and SecretName.

    .EXAMPLE
        PS> Export-TestCredentialRecord -Path $path -Record ([ordered]@{ schemaVersion = 1; baseUrl = $url; username = $name }) -Secret $token -SecretField tokenProtected -SecretName "Lab-$host" -VaultName 'AuthentikEnvironment' -Confirm:$false

        DESCRIPTION: Writes a record with the token DPAPI-protected
        OUTPUT: Path and Protection 'DPAPI'
        USE CASE: Every Export-<Provider>Credential

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


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', 'Secret',
        Justification = 'The secret arrives in memory as text from an API or a rotation and is protected here before it touches disk.')]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [System.Collections.IDictionary]$Record,

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

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

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

        [Parameter()]
        [switch]$UseSecretStore,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$VaultName,

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

    if (-not $PSCmdlet.ShouldProcess($Path, 'Write the credential record')) {
        return $null
    }

    $folder = Split-Path -Path $Path -Parent
    if ($folder -and -not (Test-Path -LiteralPath $folder)) {
        $null = New-Item -ItemType Directory -Path $folder -Force
        $null = Protect-TestFile -Path $folder -Confirm:$false
    }

    $payload = [ordered]@{}
    foreach ($key in $Record.Keys) { $payload[$key] = $Record[$key] }

    if ($UseSecretStore) {
        if ([string]::IsNullOrWhiteSpace($VaultName)) { throw '-UseSecretStore needs -VaultName.' }
        $vault = Initialize-TestSecretVault -VaultName $VaultName -VaultPassword $VaultPassword -Install -Confirm:$false
        if (-not $vault -or -not $vault.Available) {
            throw "Vault '$VaultName' is not usable, so the secret was not stored."
        }
        $null = Set-TestVaultSecret -VaultName $VaultName -SecretName $SecretName -PlainText $Secret -Confirm:$false
        $payload['protection'] = 'SecretStore'
        $payload['vaultName'] = $VaultName
        $payload['secretName'] = $SecretName
    }
    else {
        $protected = Protect-TestSecret -PlainText $Secret
        $payload['protection'] = $protected.Method
        $payload[$SecretField] = $protected.Value
    }

    $json = $payload | ConvertTo-Json -Depth 10
    [System.IO.File]::WriteAllBytes($Path, [System.Text.Encoding]::UTF8.GetBytes($json))
    $null = Protect-TestFile -Path $Path -Confirm:$false

    return [PSCustomObject]@{
        Path       = $Path
        Protection = $payload['protection']
        VaultName  = $(if ($UseSecretStore) { $VaultName } else { $null })
        SecretName = $(if ($UseSecretStore) { $SecretName } else { $null })
    }
}