๐ŸŽ New User? Get 20% off your first purchase with code NEWUSER20 ยท โšก Instant download ยท ๐Ÿ”’ Secure checkout Register Now โ†’
Menu

Categories

PowerShell SecretManagement: A Practical 2026 Guide

PowerShell SecretManagement: A Practical 2026 Guide
PowerShell SecretManagement vault - Dargslan 2026

Hard-coded passwords in PowerShell scripts are everywhere. They sit in scheduled task XML, in Jenkinsfiles, in shared drives, in OneNote pages. The Microsoft SecretManagement module finally gives PowerShell a clean, vendor-agnostic way to store and retrieve secrets, with a pluggable vault provider model. This guide covers the working patterns we have settled on.

Why a vault module

Three reasons. First, secrets in scripts get committed to git. Second, scheduled tasks under a service account leak the password to anyone who can read the XML. Third, even when secrets are encrypted with DPAPI, the key is the local user โ€” the secret travels with that user's profile and cannot be shared. SecretManagement abstracts the storage so the script does not care whether the secret lives in a local SecretStore, in Azure Key Vault, or in KeePass.

Install the modules

Install-Module Microsoft.PowerShell.SecretManagement
Install-Module Microsoft.PowerShell.SecretStore
Install-Module Az.KeyVault                          # only if using AKV

SecretManagement is the API. SecretStore, Az.KeyVault, KeePass and others are vault providers โ€” the actual storage.

SecretStore (local)

SecretStore is the default local vault. It is encrypted with a password that you set on first use, and can be configured to never prompt (suitable for unattended scheduled tasks):

Register-SecretVault -Name Local -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault
Set-SecretStoreConfiguration -Authentication Password -Interaction None `
    -Password (ConvertTo-SecureString -String 'S3cret!' -AsPlainText -Force)
Set-Secret -Name 'SqlBackup' -Secret (Get-Credential)

For a service account that runs unattended, set the configuration to None authentication only after you have confirmed the host is access-controlled. That removes the prompt at the cost of weaker at-rest protection.

Azure Key Vault provider

For a multi-host fleet that already uses Azure, AKV is the right back-end. Register it once per host:

Connect-AzAccount -Identity              # managed identity on Azure VMs
Register-SecretVault -Name AKV -ModuleName Az.KeyVault `
    -VaultParameters @{ AZKVaultName = 'kv-prod'; SubscriptionId = '...' }
Get-SecretInfo -Vault AKV
$pw = Get-Secret -Name 'sqlbackup-svc' -Vault AKV -AsPlainText

Use a managed identity for the host and grant it Get + List on the AKV access policy. No secret ever lands on disk.

Using secrets in scripts

The end-state script reads like this โ€” no passwords, no secure-string juggling:

$cred = Get-Secret -Name 'SqlBackup'
Invoke-Sqlcmd -ServerInstance sql01 -Database master -Credential $cred `
    -Query 'BACKUP DATABASE [app] TO DISK = N''\\backups\app.bak'''

If the secret is a token rather than a credential, use -AsPlainText and pass the value to the API. Never write it to disk.

Rotation pattern

Rotation should be a single command, not a manual edit of every script. Wrap it:

function Rotate-DargslanSecret {
    param([string]$Name)
    $new = -join ((33..126) | Get-Random -Count 32 | %{[char]$_})
    Set-Secret -Name $Name -Secret (ConvertTo-SecureString $new -AsPlainText -Force)
    return $new       # apply to the target system out-of-band
}

Audit who can read

For SecretStore, audit is filesystem ACLs on %LOCALAPPDATA%\Microsoft\PowerShell\secretmanagement\localstore. For AKV, audit is the access policy plus the Diagnostic Logs (SecretGet events streamed to Log Analytics).

Production patterns: HashiCorp Vault, Azure Key Vault and rotation

The local Microsoft.PowerShell.SecretStore is excellent for a workstation. For shared automation it is the wrong shape: anyone with the password can decrypt the file. Production scripts should target a centralised vault that authenticates the caller, audits every read, and supports rotation without rewriting code.

Register an Azure Key Vault extension

The Az.KeyVault module ships with a SecretManagement extension. Once registered, the consuming script never sees an Azure SDK call โ€” it asks for Get-Secret 'sql-prod-conn' and the extension handles auth, retrieval and caching.

Install-Module Az.KeyVault -Scope CurrentUser
Register-SecretVault `
    -Name AzKv `
    -ModuleName Az.KeyVault `
    -VaultParameters @{ AZKVaultName = 'kv-prod-eu' } `
    -DefaultVault

# Caller code is identical to the local store
$conn = Get-Secret -Name 'sql-prod-conn' -AsPlainText

The Azure CLI login or a managed identity provides the credential. On a build agent, attach a system-assigned identity and grant it Key Vault Secrets User โ€” no secret to manage the secret manager.

HashiCorp Vault with the SecretManagement.Vault extension

For multi-cloud or on-prem fleets, HashiCorp Vault gives you dynamic database credentials, transit encryption and detailed audit. Register it the same way:

Install-Module SecretManagement.Hashicorp.Vault.KV -Scope AllUsers
Register-SecretVault `
    -Name HCVault `
    -ModuleName SecretManagement.Hashicorp.Vault.KV `
    -VaultParameters @{
        VaultServer = 'https://vault.corp.local:8200'
        VaultAuthType = 'AppRole'
        VaultAppRoleId = $env:VAULT_ROLE_ID
        VaultAppRoleSecretId = $env:VAULT_SECRET_ID
        VaultAPIVersion = 'V2'
        Namespace = 'ops'
        KVPath = 'apps/dargslan'
    }

Notice the AppRole ID and SecretID come from environment variables injected by the CI pipeline. They are short-lived and scoped to one job; even a leak in build logs has limited blast radius.

Rotation without breaking callers

The single biggest win of a vault abstraction is that rotation becomes invisible to scripts. Set up a rotation runbook and let the vault generate the new credential, push it to the dependent system, and the next Get-Secret returns the new value.

# Rotate a service account password and write the new value back
$new = -join ((48..57)+(65..90)+(97..122) | Get-Random -Count 32 | % {[char]$_})
Set-ADAccountPassword -Identity 'svc-sql' -Reset `
    -NewPassword (ConvertTo-SecureString $new -AsPlainText -Force)

Set-Secret -Vault AzKv -Name 'sql-prod-conn' -Secret $new

Schedule this via a managed identity on a logging-enabled host. Pair with an alert on Set-Secret failure so you know within minutes if a rotation could not write back.

Common pitfalls

  • Storing the vault password in the script. Defeats the purpose. Use a managed identity, AppRole, or a one-time bootstrap that the operator unlocks interactively.
  • Mixing local SecretStore with shared automation. The local store is per-user, per-machine. Two build agents will not see the same secret. Always pick a centralised backend for any pipeline.
  • No audit on the vault itself. Every Get-Secret should write an audit event. Without it you cannot prove who read what during an incident.
  • Forgetting to delete the secret from history. A secret that was committed to git is still in the repo even after a force-push. Treat any committed secret as compromised, rotate, then clean history.
  • Caching plain text in long-running sessions. If a script needs the secret for an hour, hold it as SecureString and only convert to plain text at the moment of use.

Migration playbook: from $cred prompts to a vault

Most teams have years of scripts that ask for credentials interactively or โ€” worse โ€” store them encoded in a config file. Migrating to SecretManagement does not require a rewrite; it requires a wrapper and a phased cutover.

  1. Inventory. Grep your script repository for Get-Credential, ConvertTo-SecureString, Import-Clixml, and any references to password in config files. The output is your migration backlog.
  2. Wrapper function. Drop a Get-DargslanCredential helper into your shared module that tries Get-Secret first and falls back to Get-Credential. Existing callers see no change.
  3. Vault selection. Local SecretStore for individual operators, Azure Key Vault for cloud automation, Hashicorp for multi-cloud. Decide once per fleet, not per script.
  4. Bootstrap. Seed the vault from your existing encrypted XML files using Import-Clixml | Set-Secret. Verify each entry round-trips before deleting the source file.
  5. Cutover. Switch the wrapper to fail closed (no fallback) one team at a time. The first team to break wins early adopter status; the last team has all the lessons captured.
  6. Rotation. Add a quarterly rotation runbook for any secret older than 90 days. The vault makes rotation trivial; the discipline still has to be human.

The most common reason migrations stall is the shared service-account password in 30 places problem. Solve it by making one team responsible for the rotation and giving every consumer read-only access via the vault โ€” the password becomes a vault entry rather than a string copied into thirty scripts.

Operational metrics: knowing your vault is healthy

A vault deployment without metrics drifts toward irrelevance. Operators stop trusting it, scripts quietly fall back to hard-coded credentials, and within a year the original investment is undone. Four numbers, captured weekly, keep the deployment honest and visible to the people who funded it.

The first metric is read latency at the 95th percentile. Vault calls happen on the hot path of every script run, so a regression from 80 ms to 400 ms shows up as build slowdowns long before anyone files a ticket. Capture latency at the wrapper function and ship the timing to your observability stack alongside the script name.

The second is secret age distribution. Plot the count of secrets by last-rotation date in 30-day buckets. A healthy distribution leans heavily on the 0โ€“30 day bucket. A long tail past 180 days is the visual evidence that rotation discipline has slipped, and it is the single most useful chart to show in a security review.

The third is read-to-write ratio per consumer. A pipeline that reads a secret once per hour for a year is normal; one that reads thirty times per minute is either a bug or an attacker enumerating. Build an alert on consumer-level read rate that triggers above the 99th percentile of the previous week.

The fourth is orphaned secrets: entries in the vault that no caller has accessed in 90 days. They cost nothing to store but represent unmanaged risk. A monthly cleanup PR that lists candidates for removal โ€” with a 30-day grace period before deletion โ€” keeps the vault lean. Owners reclaim what they need; everything else dies cleanly.

Together these four numbers tell the security team that the vault is alive, the operations team that it is fast, and the finance team that it is not accumulating waste. The dashboard takes about two days to build and pays for itself by the first incident where you can prove who read what, when, and from where.

FAQ

Does this replace LAPS?

No. LAPS manages the local Administrator password on the box. SecretManagement is for application credentials your scripts use.

Can I use it on Linux PowerShell 7?

Yes. SecretStore on Linux uses the same encrypted file format. AKV works identically.

What about HashiCorp Vault?

Community provider SecretManagement.Hashicorp.Vault.KV works fine and follows the same pattern.

How do I prevent Get-Secret -AsPlainText from being abused?

Wrap calls in JEA, log every Get-Secret via ScriptBlock logging, and alert on unexpected callers.

Can SecretManagement work offline?

Yes โ€” the local SecretStore needs no network. Azure Key Vault and Hashicorp obviously require connectivity. For air-gapped fleets, a local instance of Vault or a file-based encrypted store with hardware-backed keys is the usual answer.

How do I migrate from existing $cred = Get-Credential prompts?

Wrap the prompt in a function that first tries Get-Secret, and only falls back to Get-Credential if the vault has no entry. Existing scripts pick up the new behaviour with no edits.

What is the difference between Set-Secret and Set-SecretInfo?

Set-Secret writes the value. Set-SecretInfo updates metadata (description, tags) without touching the value. Use the latter for ownership labelling and rotation timestamps.

Does this work in PowerShell 5.1?

The modules require PowerShell 7+. On legacy hosts, use the equivalent CredentialManager module or upgrade โ€” PowerShell 7 installs side-by-side with 5.1 and is the supported path forward.

Related Dargslan resources

Share this article:
Dargslan Editorial Team (Dargslan)
About the Author

Dargslan Editorial Team (Dargslan)

Collective of Software Developers, System Administrators, DevOps Engineers, and IT Authors

Dargslan is an independent technology publishing collective formed by experienced software developers, system administrators, and IT specialists.

The Dargslan editorial team works collaboratively to create practical, hands-on technology books focused on real-world use cases. Each publication is developed, reviewed, and...

Programming Languages Linux Administration Web Development Cybersecurity Networking

Stay Updated

Subscribe to our newsletter for the latest tutorials, tips, and exclusive offers.