Open any PowerShell script that has been in production for more than a year and you will probably find at least one of these patterns: a password as a literal string, a hashed credential file with the key sitting in the same folder, or a comment that says "TODO: move this to env var" from 2021. Every single one of those is a security incident waiting to happen.
Microsoft fixed this. Two officially supported modules β Microsoft.PowerShell.SecretManagement and Microsoft.PowerShell.SecretStore β give you a clean, vault-agnostic API for storing and retrieving credentials. The same script can talk to a local encrypted file, Azure Key Vault, AWS Secrets Manager, HashiCorp Vault, or 1Password without code changes. This guide is the practical setup, with patterns for both interactive use and unattended CI/CD jobs. Free PDF cheat sheet at the end.
Table of Contents
Why SecureString and Export-Clixml are not enough
For years the standard PowerShell answer to "how do I store a password" was: Get-Credential | Export-Clixml -Path .\creds.xml. This works, but it is not actually secure in the way most people assume. The encrypted file is bound to the user account and the machine β copy it to another box and it cannot be decrypted. Stay on the same box and any process running as the same user can decrypt it. There is no audit trail, no rotation, no scoping, and no way to revoke a single secret.
SecretManagement separates two concerns. The module is a thin wrapper that exposes Get-Secret, Set-Secret, Remove-Secret, and Get-SecretInfo. The actual storage is handled by a pluggable vault: an extension module that knows how to talk to a particular backend (local file, cloud KV, password manager). Your scripts call the API; you swap the vault by changing one registration. That is the whole design.
Installing the modules
Both modules ship from the PowerShell Gallery. They install cleanly side-by-side with anything else:
Install-Module -Name Microsoft.PowerShell.SecretManagement -Scope CurrentUser
Install-Module -Name Microsoft.PowerShell.SecretStore -Scope CurrentUser
SecretStore is the reference local-vault implementation β encrypted file, AES-256, password-protected. It is the right choice when you want a real vault but do not yet need a cloud backend. Other vault providers (Azure Key Vault, 1Password, KeePass) are separate modules; install them only when you need them.
Registering your first vault
# Register the local SecretStore as the default vault
Register-SecretVault -Name LocalStore -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault
# First-time configuration - sets master password
Set-SecretStoreConfiguration -Authentication Password -PasswordTimeout 900 -Interaction Prompt
The first Set-Secret or Get-Secret call after that prompts for the master password and unlocks the vault for 15 minutes (900 seconds). After timeout you are prompted again. List vaults with Get-SecretVault; you can register many at once and pick which one to use per call.
Storing and retrieving secrets
# Store a string secret
Set-Secret -Name "GitHub-PAT" -Secret "ghp_xxxxxxxxxxxxxxxxxxxx"
# Store a credential object
$cred = Get-Credential -UserName "svc-deploy"
Set-Secret -Name "DeployServiceAccount" -Secret $cred
# Store a structured hashtable
Set-Secret -Name "DB-Connection" -Secret @{
Server = "prod-sql01"
Database = "AppDb"
User = "appuser"
Password = "P@ssw0rd!"
}
# Retrieve
$pat = Get-Secret -Name "GitHub-PAT" -AsPlainText
$cred = Get-Secret -Name "DeployServiceAccount" # PSCredential
$db = Get-Secret -Name "DB-Connection" -AsPlainText
Three storage shapes β string, PSCredential, hashtable β cover virtually every real-world need. -AsPlainText opts in to getting the value as a plain string instead of a SecureString; default behaviour is the safe one.
Unattended access for scheduled tasks and CI
The whole point of a vault breaks if you have to type a password every time a scheduled job runs. SecretStore supports an unattended mode β but you have to be deliberate about it, because the trade-off is real.
# Allow the vault to unlock without prompting (single-user host only)
$securePass = Read-Host -AsSecureString -Prompt "Master password"
Set-SecretStoreConfiguration -Authentication Password -Password $securePass `
-Interaction None -PasswordTimeout 0 -Confirm:$false
This stores the configuration so the vault can be unlocked programmatically, but the master password itself still has to come from somewhere. The robust pattern in production:
- Run the scheduled task as a dedicated service account.
- Store the SecretStore master password in a managed service account (gMSA) wrapper or in the OS credential manager (Windows Credential Manager).
- At task start, read the master password from the OS, unlock the vault, run the work, exit. Each step is logged.
For CI/CD on a hosted runner, the cleaner answer is to skip the local SecretStore and register a remote vault provider instead β Azure Key Vault with a managed identity, for example. The pipeline never sees the secrets at all.
Other vault providers
The point of SecretManagement is the same script works against any registered vault. Common providers:
Az.KeyVault.Extensionβ read/write Azure Key Vault. Auth via managed identity, service principal, or interactive.SecretManagement.1Passwordβ community module, talks to the 1Password CLI.SecretManagement.Hashicorp.Vaultβ community module for HashiCorp Vault.SecretManagement.KeePassβ KeePass-backed vault.
# Register Azure Key Vault as a second vault
Register-SecretVault -Name CorpKV -ModuleName Az.KeyVault.Extension -VaultParameters @{
AZKVaultName = "kv-corp-prod"
SubscriptionId = "00000000-0000-0000-0000-000000000000"
}
# Read from a specific vault
Get-Secret -Vault CorpKV -Name "Prod-DbAdmin"
Real-world script patterns
API client wrapper
function Invoke-MyApi {
param([string]$Endpoint, [string]$Method = 'GET')
$token = Get-Secret -Name "MyApi-Token" -AsPlainText
$headers = @{ Authorization = "Bearer $token" }
Invoke-RestMethod -Uri "https://api.example.com$Endpoint" `
-Method $Method -Headers $headers
}
Database connection
$db = Get-Secret -Name "DB-Connection" -AsPlainText
$cs = "Server=$($db.Server);Database=$($db.Database);User Id=$($db.User);Password=$($db.Password);"
Invoke-Sqlcmd -ConnectionString $cs -Query "SELECT TOP 10 * FROM dbo.Orders"
Bulk credential rotation
$secrets = Get-SecretInfo
foreach ($s in $secrets) {
if ($s.Name -like "Svc-*-Password") {
$new = New-RandomPassword -Length 24
Set-Secret -Name $s.Name -Secret $new
Write-Host "Rotated $($s.Name)"
}
}
Rotation and audit
SecretStore has no built-in audit log. If you need who-read-what, you need a vault provider that does (Azure Key Vault writes every access to Azure Monitor; HashiCorp Vault has its own audit devices). For local SecretStore, the pragmatic approach is: wrap Get-Secret in your own helper that logs to a file or to the Windows event log every time a script asks for a credential, and route all production scripts through that helper.
Rotation is a script you run on a schedule. Generate a new value, update the live system that consumes the secret (database password, API key, certificate), then write the new value into the vault. Always update the system before the vault β if you fail mid-rotation, you want the vault to still match reality.
Cheat sheet
Everything in this guide on a single printable PDF: PowerShell SecretManagement Cheat Sheet.
FAQ
Is SecretStore really secure for production?
It is much more secure than Export-Clixml or environment variables, and is fine for single-machine production workloads with a controlled service account. For shared infra, multi-host deployments, or anything that requires audit trails, use a real vault backend (Azure Key Vault, HashiCorp Vault).
Where is the SecretStore file stored?
On Windows, under %LOCALAPPDATA%\Microsoft\PowerShell\secretmanagement\localstore\. On Linux/macOS, under ~/.secretmanagement/localstore/. The file is AES-256 encrypted; the master password is the only thing that decrypts it.
Can multiple users share a SecretStore?
Not natively β SecretStore is per-user. For a shared vault, use Azure Key Vault, HashiCorp Vault, or another remote provider. Multiple users can register the same remote vault and authenticate with their own credentials.
How do I migrate from Export-Clixml to SecretManagement?
Read the old XML with Import-Clixml, extract the credential, and call Set-Secret. Then delete the old file. Plan a one-time script that walks the file system for stale creds.xml files.
Does this work in PowerShell 5.1?
Yes. Both modules support Windows PowerShell 5.1 and PowerShell 7.x.
What happens if I forget the master password?
You lose every secret in that store. There is no recovery. Treat the master password the same way you treat a root key: backed up offline, in a sealed envelope, in a safe.
Can I script the initial vault setup?
Yes β pass the master password as a SecureString to Set-SecretStoreConfiguration -Password. Useful for provisioning new dev workstations from a setup script.