How to store and retrieve secrets in PowerShell

Instead of hardcoding secrets such as passwords into your source code, you can use
PowerShell's SecretManagement module to retrieve secrets from a secret vault
that you create.

You use the SecretManagement module to register different vault extensions and
access vault secrets. These vault extensions are responsible for authentication and
securely storing and retrieving the secrets and depending on the extension, can store
secretlys locally or remotely in a cloud store like the Azure Key Vault.

The security of SecretManagement is dependent on the extension vaults it hosts. These vaults perform the actual functions of storing and retrieving the secrets.

Per Microsoft's Understanding the security features of SecretManagement and SecretStore, the extension vaults are responsible
for the authentication and security of their secret vaults and secrets, so you should make suree you take that into
consideration when deciding to use SecretManagment to store your secrets.

Local Secret Vault

To store our secrets locally we will use the default SecretStore vault extension which uses .NET cryptography APIs to
encrypt the secrets and then stores them on your local file system.

Install and Import Modules

Install-Module Microsoft.PowerShell.SecretManagement
Install-Module Microsoft.PowerShell.SecretStore

Import-Module Microsoft.PowerShell.SecretManagement
Import-Module Microsoft.PowerShell.SecretStore

Create Vault

By default SecretStore requires a password and you will be prompted to set a password when first creating the vault
and then again when you access the secret vault.

In order to create your local secret vault, you must register it with PowerShell's SecretManagement module using the Register-SecretVault cmdlet and specify the Microsoft.PowerShell.SecretStore extension module using the ModuleName parameter.

This cmdlet will register a new vault with the name MyVault using the extension Microsoft.PowerShell.SecretStore and sets it as the default secret vault.

Register-SecretVault -Name MyVault -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault

The Default parameter defines this as the default secret vault to use you use other cmdlets in the SecretManagement module and no vault name is specifed using the Name parameters.

Now you can view your registered secret vaults by using Get-SecretVault:

Get-SecretVault

Name    ModuleName                       IsDefaultVault
----    ----------                       --------------
MyVault Microsoft.PowerShell.SecretStore True

Where is the vault stored on the machine?

On Windows the vault is stored in: $env:LOCALAPPDATA\Microsoft\PowerShell\secretmanagement\secretvaultregistry\

For Non-Windows the vault is stored in: $HOME/.secretmanagement/secretvaultregistry/

Change vault password

If you need to change your vault's password, you can use the Set-SecretStorePassword cmdlet, which takes no parameters and will prompt you for the old and new passwords.

You can also chang the vault password using the Set-SecretStoreConfiguration cmdlet by passing a SecureString into the Password parameter:

Set-SecretStoreConfiguration

Configure your vault

You can change the configuration of your vault if you want to change the authentication type, password timeout, etc by using the Set-SecretStoreConfiguration cmdlet.

The default password timeout for the SecretStore extention vault is 15 minutes.

Add your secret to the vault

You of course want to actually use your new secret vault, so lets get to adding a secret to the vault.

You use the Set-Secret cmdlet to add secrets to your vault, and the secret value can be one of the types below:

  • byte[]
  • String
  • SecureString
  • PSCredential
  • Hashtable

The first time you use Set-Secret to create a secret you will be
prompted to set a new vault password. Don't forget this vault password.

Set-Secret -Name MySecret -Secret "SomeSecretPassword" -Vault MyVault

Vault MyVault requires a password.
Enter password:

You can let the Set-Secret cmdlet prompt you to enter the value of the secret
by omitting the Secret parameter:

Set-Secret -Name MySecret -Vault MyVault

cmdlet Set-Secret at command pipeline position 1
Supply values for the following parameters:
SecureStringSecret: ***********************

You can even store a credential into the secret store:

Set-Secret -Name MyCredential -Secret (Get-Credential) -Vault MyVault

PowerShell credential request
Enter your credentials.
User: username
Password for user username: ****************

You can retrieve the credential from the secret store, but like all secrets,
the secret value is returned as a SecureString.

Get-Secret -Name MyCredential -Vault MyVault

UserName                     Password
--------                     --------
username System.Security.SecureString

Add metadata to a secret

You can store non-sensitive metadata with your secrets, such as specifying the purpose of the secret, the author, creation date, etc. Metadata is stored as a key-value pair and the SecretStore vault extension supports the value types:

  • string
  • int
  • DateTime
$metadata = @{
    Purpose = 'Testing'
    Expires = (Get-Date).AddDays(30)
    Limit = 5
}
Set-Secret -Name MySecret -Secret NewSecret -Metadata $metadata

To view the secret's metadata, you use the Get-SecretInfo cmdlet:

Get-SecretInfo -Name TestSecret | Format-List *

Name      : TestSecret
Type      : String
VaultName : SecretStore
Metadata  : {[Limit, 5], [Expires, 6/23/2022 1:45:09 PM], [Purpose, Testing]}

You can add metadata to an existing secret but keep in mind that this will
overwrite any existing metadata that the secret has.

Set-SecretInfo -Name MySecret -Metadata @{Purpose = "This is a test secret."}
Get-SecretInfo -Name MySecret | Select-Object Name, Metadata

Name     Metadata
----     --------
MySecret {[Purpose, This is a test secret.]}

Retrieve your secret

You can retrieve your secrets your the vault by using the Get-Secret cmdlet, which will return your secret as a SecureString but you can use the AsPlainText switch to return your secret as an unencrypted string.

Get-Secret MySecret
System.Security.SecureString

Get-Secret -Name MySecret -AsPlainText
SomeSecretPassword

List all secrets

You can get a list of all your secrets:

Get-SecretInfo

Name         Type         VaultName
----         ----         ---------
MyCredential PSCredential MyVault
MySecret     SecureString MyVault

Update your secret

If you wish to update a secret value, you use the same Set-Secret cmdlet that you
previously used to create the secret.

Set-Secret -Name MySecret -Vault MyVault

Remove your secret

When you need to remove your secret, you can use the Remove-Secret cmdlet:

Remove-Secret -Name MySecret -Vault MyVault -WhatIf
What if: Performing the operation "Remove secret by name from vault" on target "MyVault".

Remove-Secret -Name MySecret -Vault MyVault

Get-Secret -Name MySecret -Vault MyVault

Get-Secret: The secret MySecret was not found.

Resetting your secret vault

If you need to reset your secret vault and delete all secrets and restore the
vault to the default configuration you can use the Reset-SecretStore cmdlet.

Removing your secret vault

It does not appear that there is a cmdlet to actually delete your secret vault,
since if you use Unregister-SecretVault it will only unregister it
and if you register a new vault with the same name, it will restore the previous
vault with that name, like you can see here:

Get-SecretVault

Name    ModuleName                       IsDefaultVault
----    ----------                       --------------
MyVault Microsoft.PowerShell.SecretStore True

Get-Secret -Name MySecret -AsPlainText
SomeSecretPassword

Unregister-SecretVault -Name MyVault

Get-SecretVault
<nothing returned>

Register-SecretVault -Name MyVault -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault

Get-Secret -MySecret -AsPlainText
Vault MyVault requires a password.
Enter password:
**************
SomeSecretPassword

If you wish to remove your secret vault, you must use Remove-Secret on each secret in your vault:

Get-SecretInfo -Vault MyVault | Remove-Secret -WhatIf

Get-SecretInfo -Vault MyVault | Remove-Secret

Example

This is a quick example of mounting an SMB network share
using a secret that contains the network credentials.

<#
.SYNOPSIS
    Example of using secrets from a SecretStore
    by mounting a SMB share as a network drive.

.DESCRIPTION
    Example of using secrets from a SecretStore
    by mounting a SMB share as a network drive
    and storing the share's credentials as a
    secret.

.NOTES
    Author:         jpann [at] impostr-labs.com
    Created on:   	11-03-2023
#>

Import-Module Microsoft.PowerShell.SecretManagement
Import-Module  Microsoft.PowerShell.SecretStore

# Register our secret vault if it does not already exist
if (-not(Get-SecretVault -Name SMBVault -ErrorAction SilentlyContinue)) {
    Write-Host "Creating new vault ..."

    Register-SecretVault -Name SMBVault -ModuleName Microsoft.PowerShell.SecretStore
}

# If the secret for our network share credentials does not
# already exist create it.
if (-not(Get-Secret -Name SMBCredentials -Vault SMBVault -ErrorAction SilentlyContinue)) {
    Write-Host "Creating new secret ..."

    $secretArgs = @{
        Name        = SMBCredentials
        Vault       = SMBVault
        Secret      = (Get-Credential)
        Metadata    = @{Purpose = "Network credentials for this network path."}
    }

    Set-Secret @secretArgs
}

$networkDrive = "X:"
$networkPath = Read-Host "Enter network path you wish to mount as drive $networkDrive\ "

if ($networkPath) {
    if (-not(Test-Path $networkDrive -PathType Container)) {
        # Get credentials from secret
        $smbCredentials = Get-Secret -Name SMBCredentials -Vault SMBVault

        $smbArgs = @{
            LocalPath = $networkDrive
            RemotePath = $networkPath
            UserName = $smbCredentials.UserName
            Password = $smbCredentials.GetNetworkCredential().Password
        }

        Write-Host "Mounting SMB share $networkPath as $networkDrive ..."
        New-SmbMapping @smbArgs

        # Remove the variable that was containing the credentials
        Remove-Variable -Name smbCredentials
    } else {
        Write-Error "Drive $networkDrive already exists!"
        exit 1
    }
}

Write-Host "Removing SMB mapping ..."
Remove-SmbMapping -LocalPath $networkDrive -Force