Get-SecureStoreList.ps1

<#
.SYNOPSIS
Summarises keys, secrets, and certificates stored in SecureStore.

.DESCRIPTION
Get-SecureStoreList enumerates the SecureStore folder structure, returning a PSCustomObject
with arrays of key files, secret files, and certificate metadata. Certificates nearing expiry
trigger warnings to aid proactive renewal.

.PARAMETER FolderPath
Optional SecureStore base path. Defaults to the module's standard location.

.PARAMETER ExpiryWarningDays
Number of days before expiry that certificates should be flagged.

.INPUTS
None.

.OUTPUTS
PSCustomObject describing inventory and certificate health.

.EXAMPLE
Get-SecureStoreList

Lists the SecureStore contents using the default path.

.EXAMPLE
Get-SecureStoreList -FolderPath '/srv/app/secrets' -ExpiryWarningDays 45

Lists assets from a custom location and warns about certificates expiring within 45 days.

.NOTES
Only metadata is returned; secret values remain encrypted on disk.

.LINK
New-SecureStoreSecret
#>

function Get-SecureStoreList {
    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$FolderPath = $script:DefaultSecureStorePath,

        [Parameter()]
        [ValidateRange(1, 365)]
        [int]$ExpiryWarningDays = 30
    )

    begin {
        if (-not (Get-Command -Name 'Sync-SecureStoreWorkingDirectory' -ErrorAction SilentlyContinue)) {
            . "$PSScriptRoot/Sync-SecureStoreWorkingDirectory.ps1"
        }
    }

    process {
        $paths = Sync-SecureStoreWorkingDirectory -BasePath $FolderPath

        $keyFiles = @(Get-ChildItem -LiteralPath $paths.BinPath -Filter '*.bin' -File -ErrorAction SilentlyContinue)
        $secretFiles = @()
        $secretFiles += Get-ChildItem -LiteralPath $paths.SecretPath -File -ErrorAction SilentlyContinue

        if ($paths.LegacySecretPath -and (Test-Path -LiteralPath $paths.LegacySecretPath)) {
            $secretFiles += Get-ChildItem -LiteralPath $paths.LegacySecretPath -File -ErrorAction SilentlyContinue
        }

        if ($secretFiles.Count -gt 0) {
            $secretFiles = @($secretFiles | Group-Object -Property Name | ForEach-Object { $_.Group[0] })
        }
        $certFiles = @(Get-ChildItem -LiteralPath $paths.CertsPath -File -ErrorAction SilentlyContinue)

        $certificateDetails = @()
        foreach ($file in $certFiles) {
            $entry = [PSCustomObject]@{
                Name        = $file.Name
                FullName    = $file.FullName
                Thumbprint  = $null
                NotAfter    = $null
                ExpiresSoon = $false
            }

            $certificate = $null
            try {
                switch ($file.Extension.ToLowerInvariant()) {
                    '.pem' {
                        # PEM files contain base64 encoded DER blocks; strip headers before conversion.
                        $content = Read-SecureStoreText -Path $file.FullName -Encoding ([System.Text.Encoding]::ASCII)
                        $base64 = ($content -replace '-----BEGIN CERTIFICATE-----', '' -replace '-----END CERTIFICATE-----', '' -replace '\s', '')
                        if (-not [string]::IsNullOrWhiteSpace($base64)) {
                            $raw = [Convert]::FromBase64String($base64)
                            $certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($raw)
                        }
                    }
                    '.cer' { $certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($file.FullName) }
                    '.crt' { $certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($file.FullName) }
                    '.der' { $certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($file.FullName) }
                    default { }
                }

                if ($certificate) {
                    $entry.Thumbprint = $certificate.Thumbprint
                    $entry.NotAfter = $certificate.NotAfter
                    if ($certificate.NotAfter -le (Get-Date).AddDays($ExpiryWarningDays)) {
                        $entry.ExpiresSoon = $true
                        # Use warnings instead of errors so automation can continue while highlighting risk.
                        Write-Warning "Certificate '$($file.Name)' expires on $($certificate.NotAfter.ToString('u'))."
                    }
                }
            }
            catch {
                # Verbose output avoids leaking certificate content while still exposing diagnostics.
                Write-Verbose "Failed to parse certificate '$($file.FullName)': $($_.Exception.Message)"
            }
            finally {
                if ($certificate -and ($certificate -is [System.IDisposable])) {
                    $certificate.Dispose()
                }
            }

            $certificateDetails += $entry
        }

        [PSCustomObject]@{
            BasePath     = $paths.BasePath
            Keys         = $keyFiles.Name
            Secrets      = $secretFiles.Name
            Certificates = $certificateDetails
        }
    }
}