One of the most common tasks out in the field is the need to run PowerShell scripts that require credentials to be saved in some form of another so that they can be fed into scripts to be executed autonomously.  This is particularly common in cloud environments where the current user context in which the scripts are run (e.g. within a scheduled task) are insufficient or not appropriate for the remote execution.   Office 365 management is a perfect example of this, where often a credential object must be passed in order to connect and execute the cmdlets.

The quick and dirty (and all too common) approach is to simply have some code like the following:

Here we are simply writing the username and password directly into the PowerShell script as plaintext and then creating a PowerShell credential object that can then be passed onto things like Connect-MsolService.

It works, but obviously terribly insecure.  Particularly since these accounts are often high powered administrator accounts.

A better approach is to leverage the inherent ‘secure string’ nature of PowerShell credential objects and the way it represents passwords.  Specifically, you can export the ‘secure string’ into a readable string by doing something like the following:

This will give you a nice long, complex looking string like so (shortened for conciseness):  1000000d08c9ddf0115d1…..d3d491bb6d740864122a041d11

You can store that string into a text file, and when needed, read it back in and reverse it back into a ‘Secure String’ object and feed into a credential object creation by doing the following:

What is effectively happening here is that PowerShell is using the native Windows Data Protection API (DAPI) functionality to encrypt the password from the ‘secure string’ into a text string.  This string can be written to a plain text file, but the way that DAPI works is that the encryption is such that only the original user on the original machine the encryption was performed on can decrypt the string back into a ‘Secure string’ to be reused.

While not a 100% foolproof solution, it is a pretty effective way to secure the password as it reduces the attack vectors significantly.  At a minimum as long as the credentials to the account that originally ran the encryption are protected, even if a would be malicious administrator had access to the machine (and thus would have had access to your plaintext passwords stored in your script), they wouldn’t be able to reverse engineer the password that has been stored.

From that perspective your process to have a PowerShell script with a secure ‘saved’ password would be as follows:

  1. Run the Get-Credential command to prompt an administrator to provide the credentials they wish to save
  2. Convert the secure-string object that is part of that credential object into a text string (which is now encrypted) and store that in a file
  3. For scripts that need the saved credentials, read in the file, decrypt the string and recreate the credential object and feed to the appropriate cmdlets.

There a few key caveats with this approach:

  • The script that runs and reads the saved credentials, must be run on the same machine and in the same user context.
  • This means you can’t just copy the ‘saved credential’ file to other machines and reuse it.
  • In the scenario where your scripts are run as scheduled tasks under a service account, be aware that in order to prompt an admin to provide the credentials in the first place, the service account requires ‘Interactive’ ability.  This means the service account, at least temporarily, needs ‘log on locally’ to give you that interactive session.
  • In order for DAPI to work, the GPO setting Network Access: Do not allow storage of passwords and credentials for network authentication must be set to Disabled (or not configured).  Otherwise the encryption key will only last for the lifetime of the user session (i.e. upon user logoff or a machine reboot, the key is lost and it cannot decrypt the secure string text)

Now, assuming the above caveats are no good for you.  Say, for whatever reason, you can’t give your service accounts the ability to log on locally, even temporarily.  Or, the scripts that need these saved passwords need to be run on a bajillion machines and you can’t afford to log onto each one to create a secure string unique for that machine.  There is one alternative, albeit a less secure one, and realistically speaking, only slightly more secure then storing it as plain text.

In the above example, when you are performing the Convert-FromSecureString cmdlet with no parameters, you are effectively telling PowerShell to do the encryption using DAPI. You can however also provide a specific AES Key for it to use to perform the encryption instead.  In the below example, I’m generating a random AES Key to use:

To re-read the password, the following is done:

In that scenario, the AES Key is your secret, and anyone who knows the AES Key can decrypt your password, so it’s not a great idea to simply embed the AES key into your script.   So for the above example, I export the AES Key as a separate text file, that I would then recommend you secure using something like NTFS ACLs.  That way at a minimum, your security barrier is access to that ‘password file’.

Arguably you could have just done that with a plain text password file, but adding the extra layers of encryption/decryption is intended to stop the generic lazy malicious/curious/whatever administrator (or user!) from easily seeing your passwords.

With this, the AES approach to encrypting your password the process now becomes:

  1. Run the Get-Credential command to prompt an administrator to provide the credentials they wish to save
  2. Generate a random AES Key to convert the secure-string object.  Store the AES key and the secure string text as separate files.  Secure these files with NTFS permissions.
  3. For scripts that need the saved credentials, read in both files and recreate the credential object and feed to the appropriate cmdlets.

The main differences with this approach are:

  • The password files can be generated once and subsequently used on any machine by any user, as long as they can read in the files
  • The security barrier here are the NTFS permissions on the password file.

Script Template

Now, if you’ve made it this far, I congratulate you and reward you with this – all that mumbo jumbo that I spoke about above has been packaged together into a nice, easy to use PowerShell script template. This template is structured so that you can simply embed your core ‘worker’ code into the main function, and the template will handle all the rest.  It includes the following handy features:

  • Two ‘modes’:  One to collect and prepare the credential files (i.e. interactively), and another to execute the core worker code (i.e. the mode you run in the scheduled task)
  • Provides the user the option of which encryption method they wish to use, the more secure DPAPI (default) way or the more flexible AES way.
  • Code will determine automatically which method to use for decryption based on what credential files have been created
  • PLUS all those boring error handling and logging functions are included for no extra charge


Download Link via GitHub

Office 365, PowerShell, Security

Join the conversation! 11 Comments

  1. C:\Scripts\Script-Template-WithCreds.ps1
    At C:\Scripts\Script-Template-WithCreds.ps1:55 char:1
    + [CmdletBinding()]
    + ~~~~~~~~~~~~~~~~~
    Unexpected attribute ‘CmdletBinding’.
    At C:\Scripts\Script-Template-WithCreds.ps1:56 char:1
    + Param(([switch]$PrepareCredentials, [switch]$Execution, $param1, $par …
    + ~~~~~
    Unexpected token ‘Param’ in expression or statement.
    At C:\Scripts\Script-Template-WithCreds.ps1:56 char:74
    + … m(([switch]$PrepareCredentials, [switch]$Execution, $param1, $param2)
    + ~
    Missing closing ‘)’ in expression.
    + CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : UnexpectedAttribute

    • Hi HiltonT,

      Thanks for the heads up! It looks like a rogue ‘(‘ character snuck in 🙂
      I’ve fixed the gist repo now, but if you want to modify your own version, you’ll note that in line 46 of the code it has param(([switch]….. please remove one of the ‘(‘ characters and you’ll be good to go!


  2. Great article. I was happy to see you pointing out that rebooting can cause the encrypted file to not read anymore. Sadly my server has the network access policy set to disabled already. I will post back if I find a solution, I would rather stick with this one if possible.

    • Hi Chris,

      Glad you found the post helpful. Unfortunately I couldn’t find a way to work around the DPAPI issue with that GPO setting applied. Many hairs was lost doing so 🙂 It’s actually specifically the reason why I added the ‘less secure’ AES key file approach.

      One other technique that was suggested to me via a colleague was that the AES key approach could be further improved by utilising the private key of a PKI certificate to actually be the source of that AES key. The process would basically involve:

      1) Generate/obtain a certificate from any PKI cert authority
      2) Import the certificate with the private key (for security, do not allow it to be exported) in the computer certificate store
      3) Grant ACLs to the certificate to allow your service account to read the private key of that certificate
      4) Update the scripts to look for that specific certificate and read the private key and use that as the AES key for encrypting the password
      5) This approach effectively secures your password under the cryptographic certficiate store mechanisms within Windows

      Never got around to streamlining this approach to make it practical though, but you’re welcomed to give it a crack 🙂


  3. Hi,
    I have a question. I got to create the AES.key and file.txt, but now I would want to execute my script .PS1. How do I do that using this script ??

    • Hi Anderson,

      Simply fill in the part of the script that I have commented and use the $credObject as your creoudential parameter. Then when you execute the script using the -Execution parameter – it will trigger that part of the script to read in the credentials and exectute your code accordingly.

  4. Hi David,

    Fantastic article. I’ve decided to try portions of the code and during my testing I’ve come across an issue I’m hoping you can help with.
    I’ve successfully exported the secure string to a csv and I import it back in.
    I can verify that the variable contains the string I’ve previously exported, but when I attempt to ConvertTo-SecureString I get the following error:
    ConvertTo-SecureString : Cannot bind argument to parameter ‘String’ because it is null.
    Any suggestions?


    • Hi Sreck

      The error your described is most commonly due to the variable name being wrong (i.e. typo and hence referring to a non-existent variable, giving a null value) or the parameter is not correctly defined and auto-parameter mapping is not working.

      Which example set of code are you trying? If you show me your sample code, can probably work it out from here 🙂


  5. Hi David,

    Just dropped by to say great article!




Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: