Private/Clear-AllSecrets.ps1

<#
.NOTES
    Dot-sourced by Infrastructure.Secrets.psm1. Module-internal so it
    stays out of the module's public API (and out of the shared
    Module.Tests.ps1 export checks). Operator-facing exposure goes
    through the dedicated runner at scripts\Clear-AllSecrets.ps1
    instead - destructive workstation-maintenance utilities do not
    belong in the importable surface area where a stray
    `Import-Module Infrastructure.Secrets` could surface them by tab
    completion.
#>


function Clear-AllSecrets {
    <#
    .SYNOPSIS
        Removes every secret from every registered SecretManagement vault
        on this workstation, leaving vault registrations in place.

    .DESCRIPTION
        Walks every vault returned by Get-SecretVault, lists every secret
        in each, prompts the operator for a literal "yes" confirmation,
        then deletes them. Vault registrations are NOT removed - only
        their contents. Re-running setup-secrets in each consumer repo
        repopulates the store.

        Secrets stored under Microsoft.PowerShell.SecretStore share a
        single localstore, so the same name may appear in multiple
        registered vault names. Removing through one vault makes the
        second hit on the duplicate report "not found"; that branch is
        handled as a benign skip.

    .PARAMETER Force
        Skip the interactive confirmation. Intended for unattended
        callers (CI cleanup, scripted dev-loop reset). Interactive
        operators should leave it unset.

    .EXAMPLE
        Clear-AllSecrets

        Interactive: lists every (vault, name) pair, prompts for `yes`,
        then deletes.

    .EXAMPLE
        Clear-AllSecrets -Force

        Unattended: deletes without prompting. Use sparingly.
    #>

    [CmdletBinding()]
    param(
        [switch] $Force
    )

    $vaults = Get-SecretVault
    if (-not $vaults) {
        Write-Host 'No vaults registered. Nothing to do.' -ForegroundColor Yellow
        return
    }

    # Collect everything up front so the operator sees the blast radius
    # before any deletion happens.
    $victims = foreach ($v in $vaults) {
        foreach ($s in (Get-SecretInfo -Vault $v.Name -ErrorAction SilentlyContinue)) {
            [PSCustomObject]@{ Vault = $v.Name; Name = $s.Name }
        }
    }

    if (-not $victims) {
        Write-Host 'No secrets present in any vault. Nothing to do.' `
            -ForegroundColor Green
        return
    }

    Write-Host "About to delete $($victims.Count) secret entry/entries:" `
        -ForegroundColor Yellow
    $victims | Sort-Object Vault, Name | Format-Table -AutoSize | Out-Host

    if (-not $Force) {
        $answer = Read-Host "Type 'yes' to confirm (anything else aborts)"
        if ($answer -ne 'yes') {
            Write-Host 'Aborted.' -ForegroundColor Yellow
            return
        }
    }

    $removed = 0
    $failed  = 0
    foreach ($t in $victims) {
        try {
            Remove-Secret -Vault $t.Vault -Name $t.Name -ErrorAction Stop
            Write-Host "[OK] $($t.Vault)/$($t.Name)" -ForegroundColor DarkGray
            $removed++
        }
        catch {
            # Shared-store deletions via a second registered vault land
            # here as "secret not found" - the underlying file already
            # has the entry gone. Treat that as benign; surface anything
            # else as a real failure.
            if ($_.Exception.Message -match 'not found') {
                Write-Host "[skip] $($t.Vault)/$($t.Name) (already gone via shared store)" `
                    -ForegroundColor DarkGray
            }
            else {
                Write-Host "[FAIL] $($t.Vault)/$($t.Name): $($_.Exception.Message)" `
                    -ForegroundColor Red
                $failed++
            }
        }
    }

    Write-Host ''
    Write-Host "Removed: $removed, Failed: $failed" -ForegroundColor Cyan
}