Adventures in PowerShell Alchemy: Turning Code into Solutions

Secrets in Sight, Safely Stored: How to Actually Handle Credentials in PowerShell and Automation

Secrets in Sight, Safely Stored: How to Actually Handle Credentials in PowerShell and Automation

“Just for now.”

Those three words got us into this mess.

In our last post, we walked through all the places secrets go to die: registry keys, scheduled tasks, environment variables, PowerShell history files. If it was convenient, we put a password there because the script needed to run, the backup had to fire, or the token just needed to work one more time.

Now it’s time to do better.

This post isn’t about shame. It’s about options. Secure, repeatable, automation-friendly options for storing and retrieving secrets without leaving them lying around for anyone (or any malware) to find. Whether you’re managing one system or automating at scale, here are the tools, patterns, and tradeoffs to know.


What Secure Enough Looks Like

Let’s get real: no solution is perfect. But good secret hygiene comes down to three things:

  • Keep secrets off disk in plaintext
  • Restrict access to only what’s needed, when it’s needed
  • Store in a system that supports automated retrieval with auditing or encryption

We’re defending against local snooping, accidental Git commits, over-permissive access, and plain old human forgetfulness. Our goal is to reduce the blast radius when (not if) something slips.


Windows Credential Manager: Built-In and Basic

For local, interactive scripts, Credential Manager is a decent first step. It’s easy to use, encrypted with DPAPI, and built into Windows.

✅ When to Use:

  • Scripts run by the logged-in user
  • Storing credentials for internal apps, API calls, or service accounts

💻 Example:

# Save a credential
$cred = Get-Credential
New-StoredCredential -Target "MyAppCreds" -UserName $cred.UserName -Password $cred.GetNetworkCredential().Password -Persist LocalMachine

# Retrieve it later
$stored = Get-StoredCredential -Target "MyAppCreds"
$securePassword = ($stored | ConvertTo-SecureString -AsPlainText -Force)
$psCred = New-Object PSCredential($stored.UserName, $securePassword)

🔗 CredentialManager module

It’s not portable across machines or users, but it beats creds.txt.


Secure Strings + Certificates: OS-Level Protections

Secure Strings alone aren’t secure. But combine them with certificates or DPAPI, and you get a local encryption option that can work without vault software.

🔐 Option 1: DPAPI (user-scoped)

$secure = ConvertTo-SecureString "MySecret123!" -AsPlainText -Force
$secure | ConvertFrom-SecureString | Set-Content secret.txt

This can only be decrypted by the same user on the same machine:

$secure = Get-Content secret.txt | ConvertTo-SecureString

🛡️ Option 2: Encrypt with a certificate

For broader use or CI pipelines, encrypting with a certificate gives you more control:

Protect-CmsMessage -To CN="My Automation Cert" -Content "SuperSecret123!" > secret.cms
Unprotect-CmsMessage -Path secret.cms

📌 Tip: Use certs with non-exportable private keys to tighten control.


PowerShell SecretManagement: One Interface, Many Backends

Microsoft’s SecretManagement module lets you securely fetch secrets from any supported vault using a common command set:

# Install the SecretManagement module
Install-Module -Name Microsoft.PowerShell.SecretManagement -Scope CurrentUser -Force

# Install a vault extension (e.g., built-in encrypted store)
Install-Module -Name Microsoft.PowerShell.SecretStore -Scope CurrentUser -Force

# One-time setup
Register-SecretVault -Name MyVault -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault

# Store a secret
Set-Secret -Name "MySQL-Admin" -Secret "Summer2025!"

# Retrieve later
$secret = Get-Secret -Name "MySQL-Admin"

This backend can be:

  • The built-in SecretStore (encrypted with DPAPI)
  • Azure Key Vault
  • KeePass, Bitwarden, LastPass, HashiCorp Vault (with extensions)

🔗 SecretManagement Docs


Azure Key Vault: Cloud-Scale Secret Storage

When you’re scripting for Azure workloads or cloud-first automation, Azure Key Vault is the gold standard.

With proper role assignments and Managed Identity, your scripts don’t need to store anything. They fetch secrets dynamically from the vault:

Connect-AzAccount -Identity   # Assumes a managed identity
Get-AzKeyVaultSecret -VaultName 'MyVault' -Name 'Prod-API-Key'

✔️ Secrets are audited
✔️ Access is role-controlled
✔️ No credentials in sight

You can even integrate this with Azure Automation or GitHub Actions for full cloud-native pipelines.


CI/CD Pipelines: Secrets Done Right in DevOps

Hardcoding secrets into GitHub Actions, Azure DevOps, or any pipeline config is asking for trouble. Fortunately, all major platforms support encrypted secrets:

📦 GitHub Actions

env:
  API_KEY: ${{ secrets.MY_API_KEY }}

⚙️ Azure DevOps

Use Library-secured variables or Key Vault-linked variable groups.

No more copy/pasting secrets into YAML. Store them centrally and reference by name.


Refactoring: The Antidote to Regret

Found a script that still says $password = "Admin2024!"?

Here’s the safer pattern:

Before:

Invoke-RestMethod -Uri $uri -Headers @{ Authorization = "Bearer hardcoded-token" }

After:

# Import the SecretManagement module
Import-Module -Name Microsoft.PowerShell.SecretManagement

# Store a token securely
Set-Secret -Name "API-Token" -Secret "abc123supersecrettoken"

# Retrieve it later
$token = Get-Secret -Name "API-Token"
Invoke-RestMethod -Uri $uri -Headers @{ Authorization = "Bearer $token" }

Or better yet, pull a fresh token via OAuth each time, using client credentials and not hardcoded secrets at all.


Final Thoughts: No More Excuses

There’s no shortage of ways to store secrets safely. If you’ve ever said, “I’ll fix this later,” this is later.

🔑 Use Credential Manager for local scripts.
🔐 Encrypt with certs or DPAPI when vaults aren’t an option.
🗝️ Use SecretManagement when you want flexibility.
☁️ Use Key Vault when working in the cloud.
⚙️ Keep secrets out of repos and pipelines with proper CI practices.

“Just for now” is how secrets leak. But with these tools, your code can stop spilling and start respecting the sensitive info it handles.


🔧 Tools Mentioned


👣 Next Steps

If your environment is already littered with legacy secrets, circle back to our previous post and use the Find-PlainTextSecrets function to clean up.

Then come back here to future-proof your automation.

🧽 You can’t mop up a leaked secret, but you can write code that never spills in the first place.

Share this

Jeff Pollock Avatar