save PSCredential in the file
Asked Answered
S

3

30

I know I can save password to the file:

Read-Host "Enter Password" -AsSecureString |  ConvertFrom-SecureString | Out-File $passwordfile

and read it from file:

$secpasswd = (Get-Content $passwordfile | ConvertTo-SecureString)

and then create PSCredential object:

$credential = New-Object System.Management.Automation.PSCredential($user, $secpasswd)

But can I save $credential in the file, so username and his password were kept together?

Sauers answered 13/10, 2016 at 19:19 Comment(0)
T
58

Update on non-Windows Platforms

A lot has changed since this answer was first written. Modern versions of PowerShell are based on .net core, and run cross-platform. The underlying type that enables this whole answer is called [securestring] and the security and encryption that backs it comes from the Data Protection API (DPAPI) on Windows, which is not open source and not available cross-platform.

As such, while you can possibly use the same code here on non-Windows platforms, do note that there will be absolutely no encryption backing it.

tl;dr: don't use this on non-Windows platforms!

More information available in this excellent answer on a related question.


To store and retrieve encrypted credentials easily, use PowerShell's built-in XML serialization (Clixml):

$credential = Get-Credential

$credential | Export-CliXml -Path 'C:\My\Path\cred.xml'

To re-import:

$credential = Import-CliXml -Path 'C:\My\Path\cred.xml'

The important thing to remember is that by default this uses the Windows data protection API, and the key used to encrypt the password is specific to both the user and the machine that the code is running under.

As a result, the encrypted credential cannot be imported by a different user nor the same user on a different computer.

By encrypting several versions of the same credential with different running users and on different computers, you can have the same secret available to multiple users.

By putting the user and computer name in the file name, you can store all of the encrypted secrets in a way that allows for the same code to use them without hard coding anything:

Encrypter

# run as each user, and on each computer

$credential = Get-Credential

$credential | Export-CliXml -Path "C:\My\Secrets\myCred_${env:USERNAME}_${env:COMPUTERNAME}.xml"

The code that uses the stored credentials:

$credential = Import-CliXml -Path "C:\My\Secrets\myCred_${env:USERNAME}_${env:COMPUTERNAME}.xml"

The correct version of the file for the running user will be loaded automatically (or it will fail because the file doesn't exist).

Templar answered 13/10, 2016 at 19:36 Comment(8)
I had an idea to save credentials on my laptop and then use them on the server, not hardcoding them in the script. Looks like it's impossible. But can I use my saved encrypted password on other computer at last?Sauers
@Alex if you save them on one computer you cannot load them on another unless you use your own encryption key; but if you do that you have the problem of getting / transporting / using the key when you decrypt, and keeping it secret. You could reload them on your computer and then (perhaps) pass them to another computer at run time, over some encrypted transport (like through PowerShell remoting) perhaps? It depends on what you're trying to do.Templar
What if there are more than one users? Can we store the credentials of more than one users? If yes, then how to retrieve the credentials of a particular user?Tatary
@HarshJaswal in this example, the myCred part of the filename describes which credential is being stored, while the ${env:USERNAME} part describes which user encrypted (and can subsequently decrypt) the credential. If you want more users, then this code works as is. If you want more credentials, you need only replace the myCred part of the name which something descriptive. You still need to encrypt each credential once for each user who needs to access it.Templar
this is the best solution I've found in the internet. thanksShermanshermie
@Shermanshermie thanks yes it's very useful on Windows PowerShell. But PowerShell is now cross platform, and this doesn't work anymore on PowerShell Core (it might work in Core on Windows, but it definitely won't on Mac & Linux), which is very unfortunate. Just something to be aware of.Templar
unfortunately this WILL BREAK once you change your windows account password! You will not be able to do import-clixml on your saved XML files!Competence
McVitas that should only be the case for forced password resets (like an admin changed your AD password). AFAIK doing a normal password reset where you have and enter the original password would keep things intact. Are you seeing different behavior?Templar
S
4

Building on Briantist & Graham: This will ask for a credential and store it on first run, then reuse it on subsequent runs from the same code. In my case drive H is the user's home directory, used for tidiness, not security.

# the path to stored credential
$credPath = "H:\Secrets\Cred_${env:USERNAME}_${env:COMPUTERNAME}.xml"
# check for stored credential
if ( Test-Path $credPath ) {
    #crendetial is stored, load it 
    $cred = Import-CliXml -Path $credPath
} else {
    # no stored credential: create store, get credential and save it
    $parent = split-path $credpath -parent
    if ( -not ( test-Path $parent ) ) {
        New-Item -ItemType Directory -Force -Path $parent
    }
    $cred = get-credential
    $cred | Export-CliXml -Path $credPath
}

This block of code can be chucked in any script that needs it and the problem is more-or-less solved from then on.

Could possibly also check for a successful credential before writing it if the application permits it. Note that if the password changes the user must delete the file. [edit: missing parenthesis in code]

Shirline answered 6/7, 2018 at 8:38 Comment(2)
Yeah but how do you check the credentials once you've gotten them? It appears you are just checking if the path exists.. which seems insufficient.Perlaperle
Not sure what you want to test but if you want to check that it's actually loaded a credential you could use: ($cred.getType().Name -eq 'PSCredential' )Shirline
C
0

The command Export-Clixml is definitely the way to go, but for non-windows users, keep this in mind which comes from the page for it on learn.microsoft.com : Important - Export-Clixml only exports encrypted credentials on Windows. On non-Windows operating systems such as macOS and Linux, credentials are exported as a plain text stored as a Unicode character array. This provides some obfuscation but does not provide encryption.

Here's the page on Export-Clixml which has excellent help on it; example 3 specifically addresses using it for this purpose, with the note I pasted above: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/export-clixml?view=powershell-7.3

Coloratura answered 10/11, 2023 at 19:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.