Providers/AD/Private/Remove-ADTestSecretVault.ps1
|
function Remove-ADTestSecretVault { <# .SYNOPSIS Removes a SecretStore vault created for AD test environment .DESCRIPTION Safely removes a SecretStore vault and all its stored secrets. Includes safety checks and error handling. .PARAMETER VaultName Name of the secret vault to remove. Defaults to "ADTestEnvironment" .PARAMETER Force Force removal without additional confirmation .PARAMETER ResetSecretStore Also reset the global SecretStore configuration to defaults. When used with -Force, sets SecretStore to no authentication mode. Without -Force, uses interactive Reset-SecretStore (prompts for new password). WARNING: This will affect ALL SecretStore vaults on the system. Use with caution if you have other vaults configured. .PARAMETER GlobalVault The vault was created at AllUsers scope instead of CurrentUser scope. This parameter helps ensure proper vault removal. .EXAMPLE Remove-ADTestSecretVault Removes the default ADTestEnvironment vault .EXAMPLE Remove-ADTestSecretVault -ResetSecretStore Removes the default vault AND resets SecretStore configuration .EXAMPLE Remove-ADTestSecretVault -VaultName "ProdVault" -ResetSecretStore -Force Removes a custom vault, resets SecretStore, without prompts .EXAMPLE Remove-ADTestSecretVault -GlobalVault -Force Removes a global vault (AllUsers scope) without prompts .OUTPUTS PSCustomObject containing vault removal results .NOTES Author: Jeffrey Stuhr Version: 1.0.0 Last Updated: 2025-08-05 Security Notes: - All secrets in the vault will be permanently deleted - Vault configuration will be removed from the system - This operation cannot be undone #> [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([PSCustomObject])] param( [Parameter()] [ValidateNotNullOrEmpty()] [string]$VaultName = "ADTestEnvironment", [Parameter()] [switch]$Force, [Parameter()] [switch]$ResetSecretStore, [Parameter()] [switch]$GlobalVault ) begin { $correlationId = [System.Guid]::NewGuid() Write-Verbose "Starting Remove-ADTestSecretVault - CorrelationId: $correlationId" $result = @{ CorrelationId = $correlationId VaultName = $VaultName VaultExists = $false VaultRemoved = $false SecretsRemoved = 0 SecretStoreReset = $false Errors = @() Warnings = @() } } process { try { # -GlobalVault has to be read here, not merely declared. New-ADTestSecretVault # creates an AllUsers scope vault only when elevated, and that registration is # machine-wide, so removing it needs the same elevation. The switch was # previously declared, documented with its own example, and threaded all the way # down from Remove-ADEnvironment - and then never read. An unelevated global # teardown therefore failed with whatever error SecretManagement happened to # raise, instead of the clear warning the creation path gives. if ($GlobalVault) { if (Test-ADTestAdministrator) { Write-Verbose 'Removing global vault (AllUsers scope) as administrator' } else { $notAdmin = 'GlobalVault requested but not running as administrator - ' + 'an AllUsers scope vault cannot be unregistered' Write-Warning $notAdmin $result.Warnings += $notAdmin } } # Check if SecretStore modules are available $modulesAvailable = $true try { Import-Module Microsoft.PowerShell.SecretManagement -Force -ErrorAction Stop Import-Module Microsoft.PowerShell.SecretStore -Force -ErrorAction Stop Write-Verbose "SecretStore modules imported successfully" } catch { $modulesAvailable = $false $result.Warnings += "SecretStore modules not available - vault may not exist" Write-Verbose "SecretStore modules not available: $($_.Exception.Message)" } if ($modulesAvailable) { # Check if vault exists $existingVault = Get-SecretVault -Name $VaultName -ErrorAction SilentlyContinue if ($existingVault) { $result.VaultExists = $true Write-Verbose "Found vault '$VaultName' - proceeding with removal" try { # Delete the secrets before unregistering the vault. # # Unregister-SecretVault does NOT remove them. It removes the vault # REGISTRATION; the secrets stay in the SecretStore backing store # indefinitely. The previous version counted them, reported that # number as SecretsRemoved, and deleted none - so a teardown claimed # to have cleaned up and left everything behind. Every run since the # module was written has been accumulating them. # # Filtered on the Source metadata that Set-ADTestPasswordSecret # stamps, and NOT on everything the vault can enumerate. SecretStore # is a single store per user: every registered vault name sees the # SAME secrets, so deleting all of them would destroy secrets that # belong to other vaults and other tools. # # Enumeration is attempted whether or not -Force was passed. It was # previously skipped under -Force to avoid a password prompt, which # meant the common unattended path never cleaned anything at all. A # locked store simply throws here, and that is handled below. $ownedSecrets = @() try { $ownedSecrets = @(Get-SecretInfo -Vault $VaultName -ErrorAction Stop | Where-Object { $_.Metadata -and $_.Metadata['Source'] -eq 'ADTestEnvironment' }) Write-Verbose "Vault reports $($ownedSecrets.Count) secret(s) created by this module" } catch { Write-Verbose ("Cannot enumerate vault contents (the store may be locked): " + "$($_.Exception.Message)") $result.Errors += ("Could not enumerate secrets in '$VaultName': " + "$($_.Exception.Message)") } $secretCountMsg = if ($ownedSecrets.Count -gt 0) { "$($ownedSecrets.Count) secrets" } else { 'contents' } if ($Force -or $PSCmdlet.ShouldProcess("SecretStore Vault: $VaultName", ("Remove vault " + "and $secretCountMsg"))) { # SecretsRemoved counts what was actually deleted, not what was # found. The two differing is the whole reason this was wrong. foreach ($secret in $ownedSecrets) { try { Remove-Secret -Name $secret.Name -Vault $VaultName -ErrorAction Stop $result.SecretsRemoved++ } catch { Write-Verbose ("Failed to remove secret '$($secret.Name)': " + "$($_.Exception.Message)") $result.Errors += ("Could not remove secret '$($secret.Name)': " + "$($_.Exception.Message)") } } Unregister-SecretVault -Name $VaultName -ErrorAction Stop $result.VaultRemoved = $true Write-Verbose "Successfully removed vault: $VaultName" Write-Verbose "Removed $($result.SecretsRemoved) secret(s) created by this module" } else { Write-Verbose "Vault removal cancelled by ShouldProcess" } } catch { $errorMsg = "Failed to remove vault '$VaultName': $($_.Exception.Message)" $result.Errors += $errorMsg Write-Error $errorMsg } } else { Write-Verbose "Vault '$VaultName' does not exist - nothing to remove" $result.Warnings += "Vault '$VaultName' was not found (may have been already removed)" } # Reset SecretStore configuration if requested if ($ResetSecretStore) { try { Write-Verbose "Resetting SecretStore configuration..." # Check if SecretStore is configured $storeConfig = Get-SecretStoreConfiguration -ErrorAction SilentlyContinue if ($storeConfig) { if ($Force -or $PSCmdlet.ShouldProcess("SecretStore Configuration", ("Reset to no " + "authentication"))) { # Reset SecretStore using proper parameters try { # Try to reset with the known default password and set to no authentication $defaultPassword = Get-ADTestDefaultVaultPassword $resetArgs = @{ Password = $defaultPassword Authentication = 'None' Interaction = 'None' Force = $true ErrorAction = 'Stop' } Reset-SecretStore @resetArgs Write-Verbose "SecretStore reset with known password to no authentication" } catch { # If that fails, try resetting to no authentication # without specifying the current password Write-Verbose "Trying alternative reset method: $($_.Exception.Message)" $fallbackArgs = @{ Authentication = 'None' Interaction = 'None' Force = $true ErrorAction = 'Stop' } Reset-SecretStore @fallbackArgs Write-Verbose "SecretStore reset to no authentication" } $result.SecretStoreReset = $true Write-Verbose ("Successfully reset SecretStore configuration (no authentication " + "required)") } else { Write-Verbose "SecretStore reset cancelled by ShouldProcess" } } else { Write-Verbose "SecretStore is not configured - nothing to reset" } } catch { $errorMsg = "Failed to reset SecretStore configuration: $($_.Exception.Message)" $result.Errors += $errorMsg Write-Warning $errorMsg } } } } catch { $errorMsg = "Failed to remove AD test secret vault: $($_.Exception.Message)" $result.Errors += $errorMsg Write-Error $errorMsg } } end { Write-Verbose "Completed Remove-ADTestSecretVault - CorrelationId: $correlationId" return [PSCustomObject]$result } } |