Credential Management
Note
Handle passwords and credentials safely in PowerShell using SecureString, PSCredential, Get-Credential, and encrypted export/import — never plaintext.
Overview
Every script that needs a password eventually has to answer: where does this password live, and who can read it? PowerShell's credential objects — SecureString and PSCredential — exist to keep passwords out of plaintext variables, script files, and command history. This page covers the safe patterns; treat anything that puts a real password in plaintext (even briefly) as a bug to fix, not a shortcut to take.
Basic Syntax
$credential = Get-Credential
$securePassword = Read-Host -AsSecureString "Enter password"
$credential | Export-Clixml -Path "C:\Secure\cred.xml"
$credential = Import-Clixml -Path "C:\Secure\cred.xml"
Key Points
SecureStringencrypts a value in memory; it is not the same as encrypting it on diskExport-Clixmlon a credential encrypts it with Windows DPAPI, tied to the current user AND machine- A credential exported on one machine cannot be imported and decrypted on another
ConvertTo-SecureString -AsPlainText -Forceis for building test data, not for storing real secrets
Getting Credentials Interactively
# Prompts with a proper Windows credential dialog / console prompt
$credential = Get-Credential
# Pre-fill the username, only prompt for the password
$credential = Get-Credential -UserName "svc-backup"
# Custom prompt message
$credential = Get-Credential -Message "Enter credentials for the backup service"
# Access the pieces
$credential.UserName
$credential.Password # SecureString - not readable directly
$credential.GetNetworkCredential().Password # plaintext, use sparingly and only in-memory
GetNetworkCredential().Password Defeats the Point
# Only do this when a specific API genuinely requires a plaintext string,
# and never log, write, or display the result
$plainPassword = $credential.GetNetworkCredential().Password
.GetNetworkCredential().Password decrypts the password back to a plaintext string in memory. It's sometimes unavoidable (some .NET APIs only accept plaintext), but treat the resulting variable as radioactive — don't Write-Output it, don't log it, and let it go out of scope as soon as possible. Building SecureStrings
# Prompt the user (safest - password never touches script code or history)
$securePassword = Read-Host -AsSecureString "Enter password"
# Build from a known plaintext value (only for test/bootstrap scenarios)
$securePassword = ConvertTo-SecureString "TempP@ss123!" -AsPlainText -Force
# Combine into a PSCredential object
$username = "svc-backup"
$credential = New-Object System.Management.Automation.PSCredential($username, $securePassword)
# PowerShell 7+ shorthand
$credential = [PSCredential]::new($username, $securePassword)
Storing Credentials Securely
Export/Import with Clixml (DPAPI)
# Save a credential, encrypted to the current user + machine
$credential = Get-Credential
$credential | Export-Clixml -Path "C:\Secure\backup-credential.xml"
# Later, in an unattended script, load it back
$credential = Import-Clixml -Path "C:\Secure\backup-credential.xml"
# Use it
$session = New-PSSession -ComputerName "server01" -Credential $credential
DPAPI Credentials Are User- and Machine-Bound
A credential exported with Export-Clixml can only be decrypted by the same Windows user account on the same machine that exported it. This is a feature, not a limitation — it means the exported .xml file is useless to anyone who copies it elsewhere. It also means you can't just copy a credential file to another server and expect it to work; each machine needs its own export, run under the account that will actually use it.
For Cross-Machine or Team Secrets
# Windows Credential Manager (per-user, cross-script on the same machine)
# Requires the CredentialManager module: Install-Module CredentialManager
New-StoredCredential -Target "MyApp" -UserName "svc-app" -Password "P@ssw0rd" -Persist LocalMachine
$credential = Get-StoredCredential -Target "MyApp"
# For real secret management across machines/teams, use a dedicated vault
# instead of file-based storage - e.g. Azure Key Vault
# Install-Module Az.KeyVault
$secret = Get-AzKeyVaultSecret -VaultName "MyVault" -Name "BackupServicePassword" -AsPlainText
Pick the Right Tool for the Blast Radius
A single script on a single machine, run by a single person: Export-Clixml/Import-Clixml is fine. Multiple scripts on the same machine sharing a secret: Windows Credential Manager. Secrets shared across a team or multiple servers: a real secret manager (Azure Key Vault, HashiCorp Vault, etc.) — not a shared file, however "encrypted."
Using Credentials
# Remote sessions
$session = New-PSSession -ComputerName "server01" -Credential $credential
Invoke-Command -ComputerName "server01" -Credential $credential -ScriptBlock { Get-Service }
# Authenticating a web request
$headers = @{ Authorization = "Basic " + [Convert]::ToBase64String(
[Text.Encoding]::ASCII.GetBytes("$($credential.UserName):$($credential.GetNetworkCredential().Password)")
)}
Invoke-RestMethod -Uri "https://api.example.com/data" -Headers $headers
# Running a scheduled task as a specific account
Register-ScheduledTask -TaskName "Backup" -Action $action -Trigger $trigger `
-User $credential.UserName -Password $credential.GetNetworkCredential().Password
Important Parameters
| Parameter | Type | Description | Example |
|---|---|---|---|
-UserName | String | Get-Credential: pre-fill the username | Get-Credential -UserName "svc" |
-Message | String | Get-Credential: custom prompt text | -Message "Enter creds" |
-AsSecureString | Switch | Read-Host: mask input and return SecureString | Read-Host -AsSecureString |
-AsPlainText | Switch | ConvertTo-SecureString: input is plaintext | -AsPlainText -Force |
-Path | String | Export/Import-Clixml: file location | Export-Clixml -Path "cred.xml" |
-Credential | PSCredential | Pass to remoting/auth cmdlets | Invoke-Command -Credential $cred |
Common Patterns
# Pattern 1: Bootstrap a credential file once, reuse it in unattended scripts
$credPath = "C:\Secure\backup-credential.xml"
if (-not (Test-Path $credPath)) {
(Get-Credential -Message "One-time setup: enter backup account credentials") |
Export-Clixml -Path $credPath
}
$credential = Import-Clixml -Path $credPath
# Pattern 2: Fall back to prompting if no stored credential exists
$credPath = "C:\Secure\app-credential.xml"
$credential = if (Test-Path $credPath) {
Import-Clixml -Path $credPath
} else {
Get-Credential
}
# Pattern 3: Build a credential from separate username/password variables
$username = "svc-app"
$securePassword = ConvertTo-SecureString $env:APP_PASSWORD -AsPlainText -Force
$credential = [PSCredential]::new($username, $securePassword)
Real-World Examples
Example: Unattended Script Using a Pre-Staged Credential
Scenario: A scheduled backup script needs to authenticate to a remote server without any human present to type a password.
$credPath = "C:\Secure\backup-credential.xml"
if (-not (Test-Path $credPath)) {
throw "No stored credential found at $credPath. Run the one-time setup script first."
}
$credential = Import-Clixml -Path $credPath
try {
$session = New-PSSession -ComputerName "fileserver01" -Credential $credential
Invoke-Command -Session $session -ScriptBlock {
Copy-Item -Path "D:\Backups\*.bak" -Destination "\\archive\backups\" -Force
}
} finally {
if ($session) { Remove-PSSession -Session $session }
}
Explanation: The credential is set up once, interactively, by a human (see Pattern 1 above), then loaded silently by the scheduled task. Because Export-Clixml ties the encryption to the specific user account and machine running the scheduled task, the stored file is useless if stolen and copied elsewhere.
Tips & Tricks
Clear Plaintext Variables When You're Done
$plainPassword = $credential.GetNetworkCredential().Password
# ... use $plainPassword for the one API call that needs it ...
$plainPassword = $null
Remove-Variable plainPassword -ErrorAction SilentlyContinue
Write-Output. Command History Remembers Everything You Type
# BAD - now sitting in plaintext in (Get-PSReadlineOption).HistorySavePath
$securePassword = ConvertTo-SecureString "RealPassword123" -AsPlainText -Force
# GOOD - never type the real password as literal text
$securePassword = Read-Host -AsSecureString "Enter password"
ConvertTo-SecureString -AsPlainText— is preserved in that history file. Always get real passwords via prompt, environment variable, or a vault, never as literal text in a command. Related Topics
- User Account Tasks - Creating the accounts these credentials authenticate as
- Network Operations / API Interactions - Using credentials to authenticate requests