Public/Set-NSPSecret.ps1

function Set-NSPSecret {
    <#
    .SYNOPSIS
        Stores (or replaces) a named secret.

    .DESCRIPTION
        Writes to the SecretManagement vault (default 'NSP') unless -Scope File is given, which
        writes the DPAPI fallback file instead.

        If -Secret is omitted the value is read with Read-Host -AsSecureString, so it never lands
        in shell history, a script file, or a transcript. Prefer that.

    .PARAMETER Name
        Secret name, e.g. 'CW.Control.ApiKey'.

    .PARAMETER Secret
        The value: a [string], a [SecureString], or omitted (prompt). A plain string is accepted
        for scripted migration but is the least safe path.

    .PARAMETER Vault
        SecretManagement vault name. Defaults to 'NSP'.

    .PARAMETER Scope
        'Vault' (default) or 'File' (DPAPI fallback, current user + machine only).

    .EXAMPLE
        Set-NSPSecret -Name 'CW.Control.ApiKey'
        # prompts, stores in the NSP vault

    .EXAMPLE
        Set-NSPSecret -Name 'CW.Control.ApiKey' -Scope File
        # prompts, stores in %LOCALAPPDATA%\NSP\Secrets\CW.Control.ApiKey.sec
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '',
        Justification = 'Migration path: a plaintext value handed in by a tech or read from a legacy file must be converted to a SecureString to be stored securely.')]
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, Position = 0)][string]$Name,
        [Parameter(Position = 1)][object]$Secret,
        [string]$Vault = $script:NSPVaultName,
        [ValidateSet('Vault', 'File')][string]$Scope = 'Vault'
    )

    if (-not $PSBoundParameters.ContainsKey('Secret') -or $null -eq $Secret) {
        $secure = Read-Host -Prompt "Enter value for secret '$Name'" -AsSecureString
    } elseif ($Secret -is [System.Security.SecureString]) {
        $secure = $Secret
    } else {
        $secure = ConvertTo-SecureString -String ([string]$Secret) -AsPlainText -Force
    }

    if (-not $secure -or $secure.Length -eq 0) {
        throw "Set-NSPSecret: empty value for '$Name' - nothing stored."
    }

    if ($Scope -eq 'File') {
        $path = Get-NSPSecretFallbackPath -Name $Name
        $dir  = Split-Path -Parent $path
        if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
        if ($PSCmdlet.ShouldProcess($path, 'Write DPAPI-encrypted secret file')) {
            ConvertFrom-SecureString -SecureString $secure | Set-Content -LiteralPath $path -NoNewline -Encoding ascii
            Write-Host "Stored '$Name' at $path (DPAPI: current user + this machine only)." -ForegroundColor Green
        }
        return
    }

    if (-not (Get-Module -ListAvailable -Name 'Microsoft.PowerShell.SecretManagement')) {
        throw "SecretManagement is not installed. Run Initialize-NSPSecretStore first, or use -Scope File."
    }
    Import-Module Microsoft.PowerShell.SecretManagement -ErrorAction Stop
    if (-not (Get-SecretVault -Name $Vault -ErrorAction SilentlyContinue)) {
        throw "Vault '$Vault' is not registered. Run Initialize-NSPSecretStore first."
    }

    if ($PSCmdlet.ShouldProcess("$Vault\$Name", 'Set-Secret')) {
        Set-Secret -Name $Name -SecureString $secure -Vault $Vault -ErrorAction Stop
        Write-Host "Stored '$Name' in vault '$Vault'." -ForegroundColor Green
    }
}