Administrators often have to store passwords in automation scenario directly in the body of PowerShell scripts. As you know, it is extremely insecure when used in a productive environment, since other server users or administrators can see the password in clear text. So it is better to use a more secure way to use passwords in PowerShell scripts, or to encrypt passwords if interactive input cannot be used.
It is safe to prompt a user to enter password in the script interactively using the Get-Credential cmdlet. For example, let’s prompt a user for the username and password and save them in the PSCredential object:
$Cred = Get-Credential
When addressing the properties of the PSCredential variable, you can find the specified user name.
$Cred.Username
However, when trying to display the user password, the following text will be returned: System.Security.SecureString, since the password is saved as SecureString.
$Cred.Password
The PSCredential object we have saved in the $Cred variable can be used in cmdlets that support this type of objects.
The $Cred.Username and $Cred.Password parameters can be used in cmdlets that don’t support PSCredential objects, but require input of user credentials.
You can also use Read-Host cmdlet with the AsSecureString attribute to prompt a user to input the password.
$pass = Read-Host "Enter your password" –AsSecureString
In this case you won’t be able to view the contents of $pass variable, in which the password is stored.
In the ways of using password in PowerShell scripts considered above, an interactive password input has been used when running the script. However, these methods are not applicable for scenarios run automatically or using the Task Scheduler.
In this case, it is more convenient to encrypt the account credentials (name and password) and save them to an encrypted text file on the disk or use directly in the script.
Thus, using ConvertFrom-SecureString cmdlet you can convert a password from SecureString format to an encrypted string (it is encrypted using Windows Data Protection API — DPAPI). You can display the encrypted password on the screen or save it to a file:
$Cred.Password| ConvertFrom-SecureString | Set-Content c:\ps\passwordfile.txt
To use the encrypted password from the file, you must convert it back to the SecureString format using the ConvertTo-SecureString cmdlet:
$username = 'corp\admin'
$pass = Get-Content c:\ps\passwordfile.txt | ConvertTo-SecureString
$creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username, $pass
This way you got a PSCredential object with user credentials in the $creds variable.
However, if you try and copy the passwordfile.txt to another computer or use for another user (not for the one who created the password), you will see that $creds.password
variable is empty and doesn’t contain a password. The matter is that DPAPI encryption uses the private keys stored in the user profile. You won’t be able to decrypt the password file without key.
ConvertTo-SecureString : Key not valid for use in specified state. "Cannot process argument because the value of argument "password" is null. Change the value of argument "password" to a non-null value."
If the script is started under another user (service) account or on another computer, you will have to use another encryption method different from DPAPI. You can specify the external encryption key using –Key or –SecureKey parameters.
For example, you can generate a 256-bit AES key in PowerShell and use it to decrypt the file. Save this key to the text file password_aes.key.
$AESKey = New-Object Byte[] 32
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($AESKey)
$AESKey | out-file C:\ps\password_aes.key
Now you can save your password to the file using this key:
$Cred.Password| ConvertFrom-SecureString -Key (get-content C:\ps\password_aes.key)| Set-Content c:\ps\passwordfile.txt
So, you have got two files: a file containing the encrypted password (passwordfile.txt) and another one with the encryption key (password_aes.key).
You can transfer them to another computer and try to get the password from the file (you can store the file with the key in your shared network folder).
$pass = Get-Content c:\ps\passwordfile.txt | ConvertTo-SecureString -Key (get-content \\srv1\Shared\password_aes.key)
$pass
If you don’t want to take the trouble of a separate file with the AES key, you can integrate the encryption key directly into the script. Then use the following instead of the key in both cases:
[Byte[]] $key = (1..16)
$Cred.Password| ConvertFrom-SecureString –Key $key| Set-Content c:\ps\passwordfile.txt
For decryption:
[Byte[]] $key = (1..16)
$pass = Get-Content c:\ps\passwordfile.txt | ConvertTo-SecureString -Key $key
As you can see, the password is not empty, so it has been successfully decrypted and may be used on other computers.
Finally, here is the most uncomfortable thing. It is very easy to get a password from a PSCredential object in the clear text:
$Cred.GetNetworkCredential().password
You can also do it for SecureString:
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pass)
[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
As you see, this is the reason why you must not save passwords of privileged accounts, like Domain Admins, anywhere but on the DCs.