Secure Passwords With PowerShell Module

This module will securely store passwords in PowerShell with ease. I wrote this back in 2019 while working on replacing some Python code with PowerShell. Recently I needed to use this on another sever and found that it throws a key error.

Import-cliXml: Key not valid for use in specified state.

It makes sense but was something that I hadn’t thought about when initially writing the module.

To get past that issue I added two new methods. It’s a simple export to CSV on the original server and import of the CSV on the other server(s) to create the XML.

The base use of this module allows you store credentials, fetch, and delete and will keep passwords out of your scripts. You can find usage examples in the script itself and you can store passwords for databases, sftp, or Windows accounts.

Using This Module

Simply copy the code, save as PSVault.psm1 and place it where you store your modules or add it to the default PowerShell modules location. Then you should be able to import and use like any other module.

Secure Passwords PowerShell Module Code

<#
.Synopsis
  PS-Vault.psm1: Simple password vault for PowerShell scripts.
  Author: Charles Nichols
  Updated: Sept. 2022 > Added ability to export and import to other servers.
 
.Description
  Get, Set, and Delete passwords securely within PowerShell.
 
  Export-ExternalCredsFrom-CSV and Import-ExternalCredsFrom-CSV are work arounds
  where password files cannot be used on other machines or accounts where created
  on another machine. You can export from the source machine then import on
  new machine to move passwords.
 
.Parameter Account
  The account associated with the password; also used to load the correct password.
 
.Parameter Password
  The password.
 
.Example
  # Add a PSCredential Object.
  Import-Module "E:\MyScripts\PS-Vault.psm1"
  $auth = Set-Creds -Account "MyAccount" -Password "MyPassword"
 
.Example
  # Delete a PSCredential Object.
  Import-Module "E:\MyScripts\PS-Vault.psm1"
  Remove-Creds -Account "MyAccount"
 
  # Retrieve a PSCredential Object.
  Import-Module "E:\MyScripts\PS-Vault.psm1"
  $auth = Get-Creds -Account "MyAccount"
 
  # Get the password in plain text.
  Get-Password -SecurePassword $auth.Password
 
  .Author
  C. Nichols, 2019
#>
 
# Set path to your secure storage location.
$SafeStore = "E:\PSScripts\SecStore"
 
<#
.Synopsis
    Fetch a creditial by account name.
 
.Description
    Get account auth data for use in scripts.
 
.Parameter Account
    The account or ID used to extract an encrypted password.
 
.Returns
    System.Management.Automation.PSCredential
#>
Function Get-Creds {
    Param(
        [parameter(Mandatory=$true)]
        [String]$Account
    )
    $Creds = $null
    $Store = "$SafeStore\$($Account).xml" 
    if (Test-Path -Path $Store) { 
        $Creds = Import-CliXml -Path $Store
    } else {
        Write-Host "File not found."
    }
 
    return $Creds
}
 
<#
.Synopsis
    Store a creditial by account name.
 
.Description
    Set account auth data for use in scripts.
 
.Parameter Account
    The account name or ID.
 
.Parameter $Password
    Password to be encrypted.
 
.Output
    CliXml file.
#>
Function Set-Creds {
    Param(
        [parameter(Mandatory=$true)]
        [String]$Account,
        [parameter(Mandatory=$true)]
        [String]$Password
    )
 
    $Exists = Test-Path $SafeStore
    If (-not $Exists) {
        New-Item -ItemType directory -Path $SafeStore
    }
 
    $secPass = ConvertTo-SecureString $Password -AsPlainText -Force
    $Cred = New-Object System.Management.Automation.PSCredential ($Account, $secPass)
    $Store = "$SafeStore\$($Account).xml"
 
    $Cred | Export-CliXml -Path $Store
}
 
<#
.Synopsis
    Store a creditial for any account.
 
.Description
    Will display a user logon dialog and set the account auth data for use in scripts.
    Beneficial if you need to store a password for use in tasks or with an account you
    are not currently logged on with.
 
.Output
    CliXml file.
#>
Function Set-CredsForSpecificAccount {
    Get-Credential | Export-CliXml -Path $SafeStore
}
 
<#
.Synopsis
    Remove a creditial by account name.
 
.Description
    This will remove the physical XML file from the server.
 
.Parameter Account
    The account name or ID.
 
.Output
    CliXml file.
#>
Function Remove-Creds {
    Param(
        [parameter(Mandatory=$true)]
        [String]$Account
    )
 
    $Store = "$SafeStore\$($Account).xml" 
    Remove-Item -Path $Store
}
 
<#
.Synopsis
    Converts an encrypted SecureString to plain text.
 
.Description
    Use if you need to verify or view a password stored
    as a SecureString.
 
.Parameter SecurePassword
    Password as SecureString.
 
.Output
    string
#>
Function Get-Password {
    Param(
        [parameter(Mandatory=$true)]
        [SecureString]$SecurePassword
    )
    $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword)
    [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
}
 
<#
.Synopsis
    Creates a list of creds from another server.
 
.Description
    Use if you need to grab all the passwords from one server to
    another. Passwords are encrypted to server as well as user account.
    This will remove the CSV file upon completion.
 
.Parameter CSVPath
    Path to CSV.
 
.Output
    CliXml (multiple)
#>
Function Import-ExternalCredsFromCSV {
    Param(
        [parameter(Mandatory=$true)]
        [String]$CSVPath
    )
   
    $StoreExists = Test-Path $SafeStore
    If (-not($StoreExists)) {
        New-Item -Path $BasePath -ItemType Directory
    }
 
    $BaseFile = "Plain_Text_Password_List_Export.csv"
 
    if ($CSVPath.ToLower().Contains(".csv")) {
        $BasePath = Split-Path $CSVPath -Parent
        $CSVPath = "{0}\{1}" -f $BasePath, $BaseFile
    } else {
        $CSVPath = "{0}\{1}" -f $CSVPath, $BaseFile
    }
 
    $RecCount = 0
    $FileCount = 0
    $CSVExists = Test-Path $CSVPath
    if ($CSVExists) {
        Get-Content -Path $CSVPath | ForEach-Object {
 
            $RecCount += 1
 
            $Parts = $_.Split(',')
            $Account = $Parts[0].Trim()
            $PAssword = $Parts[1].Trim()
            $secPass = ConvertTo-SecureString $Password -AsPlainText -Force
            $Cred = New-Object System.Management.Automation.PSCredential ($Account, $secPass)
            $Store = "$SafeStore\$($Account).xml"
            $Cred | Export-CliXml -Path $Store
 
            if (Test-Path -Path $Store) {
                $FileCount += 1
            }
        }
    } else {
        Write-Host "File not found: Invalid path."
    }
   
    if ($RecCount -eq $FileCount) {
        Remove-Item -Path $CSVPath
    } else {
        Write-Host "Record mismatch: Verify all accounts saved. Not removing export CSV. Please remove manually once corrected."
    }
}
 
<#
.Synopsis
    Export a list of creds from current server.
 
.Description
    Use if you need to grab all the passwords from one server to
    another. Passwords are encrypted to server as well as user account.
    This will create a list of unencrypted passwords for use with
    Import-ExternalCredsFromCSV.
 
.Parameter CSVPath
    Path to CSV.
 
.Output
    CSV
#>
Function Export-ExternalCredsToCSV {
    Param(
        [parameter(Mandatory=$true)]
        [String]$CSVPath
    )
    $ExportList = @()
    $Exists = Test-Path $SafeStore
    If ($Exists) {
        $FPath = "{0}\*" -f $SafeStore
        Get-ChildItem -Path $FPath -Include "*.xml" | ForEach-Object {
            $Creds = Import-CliXml -Path $_.FullName
            $Account = $Creds.UserName
            $Password = Get-Password -SecurePassword $Creds.Password
            $StrLine = "{0},{1}" -f $Account, $Password
            $ExportList += $StrLine
        }
    }
    $BaseFile = "Plain_Text_Password_List_Export.csv"
 
    if ($SafeStore.ToLower().Contains(".csv")) {
        $BasePath = Split-Path $SafeStore -Parent
 
        if (-not(Test-Path -Path $BasePath)) {
            New-Item -Path $BasePath -ItemType Directory
        }
        $CSVPath = "{0}\{1}" -f $BasePath, $BaseFile
    } else {
        $CSVPath = "{0}\{1}" -f $CSVPath, $BaseFile
    }
 
    $ExportList | Set-Content -Path $CSVPath
}
 
Export-ModuleMember -Function Get-Creds
Export-ModuleMember -Function Set-Creds
Export-ModuleMember -Function Set-CredsForSpecificAccount
Export-ModuleMember -Function Remove-Creds
Export-ModuleMember -Function Get-Password
Export-ModuleMember -Function Import-ExternalCredsFromCSV
Export-ModuleMember -Function Export-ExternalCredsToCSV
 
<# Testing ...
 
PS C:\Users\NICHOLSCD_x> using module "E:\PSScripts\PSModules\PS-Vault.psm1"
 
PS C:\Users\NICHOLSCD_x> Set-Creds -Account "mohawke" -Password "P@ssw0rd"
 
PS C:\Users\NICHOLSCD_x> $Auth = Get-Creds -Account "mohawke"
 
PS C:\Users\NICHOLSCD_x> $Auth.GetType()
 
IsPublic IsSerial Name                                     BaseType                                                                                                                                                                                              
-------- -------- ----                                     --------                                                                                                                                                                                              
True     True     PSCredential                             System.Object                                                                                                                                                                                         
 
PS C:\Users\NICHOLSCD_x> $Auth.UserName
mohawke
 
PS C:\Users\NICHOLSCD_x> Get-Password -SecurePassword $Auth.Password
P@ssw0rd
 
PS C:\Users\NICHOLSCD_x> Remove-Creds -Account "mohawke"
 
# Export from one server then import to another. This should create a key on the new server.
# ** You may need to run as the account that will be using the creds.
 
PS C:\Users\NICHOLSCD_x> Export-ExternalCredsTo-CSV -CSVPath "E:\PSScripts\SecStore"
PS C:\Users\NICHOLSCD_x> Import-ExternalCredsFrom-CSV -CSVPath "E:\PSScripts\SecStore"
#>

Hope this is useful to someone.