I have a piece of PowerShell code that I recently started using in some of my PowerShell scripts that manages PSCredentials. I wanted to review here before I used it in some future posts.

A lot of SharePoint cmdlets accept a PSCredential object as a parameter, which in turn uses a SecureString object for storing the account's password. Best practices dictates that you should treat the credentials for the PSCredential like you would any other account — choose a suitably complex or long password that you don't write down or tell anyone and that if you want to automate the use of these credentials you should supply the account passwords in encrypted files.

The balance between security and convenience is a tradeoff. While Microsoft has provided a relatively secure interface for providing the credentials to an account, following the best practice can be limiting. Sometimes you need a quick and dirty script that does what you want and can be modified easily without any extra steps (the technical term for this may be "laziness").

The technique I describe is not secure. The account's username and password are specified in plain text right in the script, along with any other accounts used in the script. Anyone who can read the script now knows these values. This technique goes against every security best practice out there. I justify using the technique only if you understand the security tradeoff and when it is suitable for the problem you are attempting to solve. I'm not recommending this as a technique you should always use or to replace any existing secure techniques.

The technique can be expressed as a one-liner:

$my_credential = New-Object System.Management.Automation.PSCredential "DOMAIN\user", (ConvertTo-SecureString -String "PASSWORD" -AsPlainText -Force)

Simply, you create the PSCredential object for the account and convert the plain text password to a SecureString inline.

Here's a real-world example for when I would use this technique. For our projects, each developer receives a virtual machine that contains Windows, SQL Server, SharePoint, Visual Studio, and other development tools or project-specific applications. When we build these VMs, we want to build them as identically as possible and ideally the only difference is the server name. This allows reproducibility between environments. If each developer is using the exact same software configured in exactly the same way, issues in one environment should be reproducible in another. For a project I create a set of service accounts that are used by all of the development VMs. In most cases these service accounts have the same easy-to-remember password (e.g. DevelopmentPassword123!) and everyone on the team knows what these are since looking up passwords when you're developing can involve a jarring task switch while you are deep in thought. I'm not saying you should store all passwords in your scripts. I'm saying that in this scenario we understand the security/convenience tradeoff and have decided that it's okay to store a known password for an account in the scripts used to build the farm.

Now the one-liner is fine, but in a script I don't want to have to search for all these lines when I make updates or if I reuse (portions of) the script for other projects. To solve this, I set up a hash table of accounts near the top of my script that is called out with comments and easy to edit.

Each item in the hash table has three fields: username, password, and credential. Username and password are plain text and contain the account's username and password. The credential field stores the PSCredential object for the account that can be used by cmdlets and is created at runtime.

Let's see how this works:

PS> $accounts = @{}
PS> $accounts.Add("Admin", @{"username" = ("CONTOSO\admin-sp"); "password" = "Contoso!"})
PS> $accounts.Add("Farm", @{"username" = ("CONTOSO\svc-sp-farm"); "password" = "Contoso!"})
PS>
PS> $accounts

Name                           Value
----                           -----
Farm                           {password, username}
Admin                          {password, username}


PS> $accounts.Admin

Name                           Value
----                           -----
password                       Contoso!
username                       CONTOSO\admin-sp


PS> $accounts.Farm

Name                           Value
----                           -----
password                       Contoso!
username                       CONTOSO\svc-sp-farm


PS>

As you can see, this portion is straightforward. I've created a hash table and added two items with an easy to reference key, and another hash table as the value that contains the username and plain text password. Next, we'll add the PSCredential object to each account's item:

PS> Foreach ($account in $accounts.keys) {
>>     $accounts.$account.Add(`
>>     "credential", `
>>     (New-Object System.Management.Automation.PSCredential $accounts.$account.username, `
>>     (ConvertTo-SecureString -String $accounts.$account.password -AsPlainText -Force)))
>> }
>>
PS> $accounts

Name                           Value
----                           -----
Farm                           {password, credential, username}
Admin                          {password, credential, username}


PS> $accounts.Admin

Name                           Value
----                           -----
password                       Contoso!
credential                     System.Management.Automation.PSCredential
username                       CONTOSO\admin-sp


PS> $accounts.Farm

Name                           Value
----                           -----
password                       Contoso!
credential                     System.Management.Automation.PSCredential
username                       CONTOSO\svc-sp-farm


PS> 

So you see I created the PSCredential objects all at once, after specifying all of the account credential information. And we can use each part individually:

PS> $accounts.Farm.username
CONTOSO\svc-sp-farm
PS> $accounts.Farm.password
Contoso!
PS> $accounts.Farm.credential

UserName                                    Password
--------                                    --------
CONTOSO\svc-sp-farm     System.Security.SecureString


PS> 

Now let's tie it all together for a script:

$accounts = @{}
$accounts.Add("Admin", @{"username" = ("CONTOSO\admin-sp"); "password" = "Contoso!"})
$accounts.Add("Farm", @{"username" = ("CONTOSO\svc-sp-farm"); "password" = "Contoso!"})

Foreach ($account in $accounts.keys) {
	$accounts.$account.Add(`
	"credential", `
	(New-Object System.Management.Automation.PSCredential $accounts.$account.username, `
	(ConvertTo-SecureString -String $accounts.$account.password -AsPlainText -Force)))
}

References

Share