Working with credentials - PowerShell management - PowerShell in Depth, Second Edition (2015)

PowerShell in Depth, Second Edition (2015)

Part 2. PowerShell management

Chapter 12. Working with credentials

This chapter covers

· Creating a credential object

· Using credentials

· Supporting alternative credentials in your scripts

A great many PowerShell commands have a –Credential parameter. You can view the list by typing

Get-Help * -Parameter Credential

When you use this parameter, it generally allows the command to operate with the username and password you provide rather than the one you used to log on to the shell. It’s a great way to enable the principle of least privilege: Do as much work as you can with unprivileged credentials, but when more privilege is required, specify alternative credentials for that purpose. You’ll need to run PowerShell with elevated privileges (“Run as Administrator”) to complete some actions regardless of the credentials you use.

12.1. About credentials

Most of the time, you’ll find that a –Credential parameter will accept one of two objects: a string object or a credential object. The string object is easy: Provide a username, which can be a plain username, a DOMAIN\User-style username, or in User Principal Name (UPN) format, username@domainname, where domainname is fully qualified—that is, domainname.com, not just domainname. If you don’t specify a domain component, the credential will be assumed to be an account on the local machine.

Warning

Some cmdlets—such as Get-WmiObject—don’t accept credentials when working against the local machine.

If you have a credential you want to use for a local account on a remote machine, use the format COMPUTERNAME\user. In any event, when you specify a string, you’ll be graphically prompted for the password, as shown in figure 12.1, which depicts the ISE. You’ll see a similar graphical prompt if Get-Credential is called from the PowerShell console.

Figure 12.1. Using Get-Credential to get a username and password. The -username parameter can be used to partially complete the credential.

Note

The password will be masked as you enter it, to keep it secure.

And before you ask, no, you can’t also specify the password on the command line. Doing so would mean including a clear-text password, most likely for an administrator account, right on the command line or in a script. Because that’d be a horrible idea, Microsoft went to some lengths to make it impossible.

PowerShell v3 introduced an extra option to using Get-Credential. In PowerShell v2 you could use it with or without supplying a username like this:

Get-Credential

Get-Credential -Credential richard

Get-Credential -Credential mymachine\richard

Get-Credential -Credential mydomain\richard

That still applies in v3 and v4, but two parameters, –UserName and –Message, are now available that work like this:

Get-Credential -Message "Credentials needed for"

The message in the dialog box is replaced with the text you supply. Adding the username populates the dialog box, as shown in figure 12.2. This option remains in PowerShell v4.

Figure 12.2. Modified message in credential dialog box

Figure 12.2 was generated by using Get-Credential from the console. Subtle differences exist between it and figure 12.1, which was generated in the ISE. Both dialog boxes do the same job.

That doesn’t mean you have to enter your password each time you want to use an alternative credential. Rather than providing a username, you can create a complete credential object ahead of time, often storing it in a variable. You’ll be prompted for your password only once, and then the password will be stored as a secure string inside the credential object. Here’s an example of creating, examining, and using a credential object with the Get-Credential cmdlet:

PS C:\> $cred = Get-Credential COMPANY\DJones

You’ll be prompted for the password in the same graphical dialog box. Although this looks like a logon dialog box, the cmdlet isn’t authenticating or verifying the credentials. All it’s doing is creating a PSCredential object.

PS C:\> $cred | get-member

TypeName: System.Management.Automation.PSCredential

Name MemberType Definition

---- ---------- ----------

Equals Method bool Equals(System.Object obj)

GetHashCode Method int GetHashCode()

GetNetworkCredential Method System.Net.NetworkCredential GetNetworkC...

GetObjectData Method System.Void GetObjectData(System.Runtim...

GetType Method type GetType()

ToString Method string ToString()

Password Property System.Security.SecureString Password {g...

UserName Property string UserName {get;}

When you use the credential, PowerShell will hand it off to Windows and carry out normal security and authentication protocols. As you can see by piping to Get-Member, this is a simple object:

PS C:\> $cred

UserName Password

-------- --------

COMPANY\DJones System.Security.SecureString

Notice that the password isn’t accessible in clear text, so it’s somewhat safer in the credential object. The password can only be decrypted using the private key, which exists only on the computer where the credential was created. Even if you use the ConvertFrom-SecureString cmdlet, you don’t get the plain-text password.

PS C:\> convertfrom-securestring $cred.password

01000000d08c9ddf0115d1118c7a00c04fc297eb01000000897b1b8a84101a498e418f03a19

495f00000000002000000000003660000c0000000100000004070c5b1564d0b5e59e355a4c8

a79fcf0000000004800000a000000010000000f77c0a3324fb6a51c52065d7ef21d6cf18000

000163f5bf2d8c230fab1053846c98cfa957e9f87f92aec22b214000000caad18fd9f97a94e

3ffa0c36a9cb3312fae1662b

PS C:\>

The only way to return the plain-text password is to invoke the GetNetwork-Credential() method:

PS C:\> $cred.GetNetworkCredential() | Select username, password

UserName Password

-------- --------

DJones P@ssw0rd

Now before you start having a panic attack, understand that this command only works in the current session that created the $cred variable. As soon as you close the session, the variable is destroyed. All of this is in memory. If you have PowerShell sessions open and logged on with administrator credentials or saved credential objects, be sure to lock your desktop should you walk away. That should be Common Sense Security 101.

The benefit of all of this fuss with Get-Credential is that by specifying the entire credential object from a command, you won’t be prompted for the password again. To use this credential object, provide the variable as the value for any cmdlet that has the –Credential parameter:

PS C:\> Get-WmiObject -Class Win32_BIOS -Computer SERVER1 -Credential $cred

When the Get-WmiObject cmdlet runs, it’ll use the username and password in the $cred variable, not the credentials of the current user. Note that WMI only permits the use of alternative credentials for remote computers. You’ll get an error if you try to specify and use a credential against the local machine.

Because security should be a big deal to you and credentials in PowerShell have a few quirks, let’s spend a few minutes looking at how to use them.

12.2. Using credentials

Here are some tricks and caveats for using credentials:

· When a command targets multiple computers, such as when you specify multiple names to a -ComputerName parameter, whatever credential you provide to the -Credential parameter will be used for every one of the computers. There’s no way to specify a different credential for each computer. If you have to do that, run the command one time for each credential you need to provide.

· You can run Get-Credential in your PowerShell profile (the script that runs when PowerShell starts) to have PowerShell create a credential object for you each time the shell starts. Assuming you have the profile store that credential in a variable, it’ll be ready to use for as long as you keep that shell session open. This is a useful trick if there’s a credential you commonly use. You can create as many credential objects as you need but remember that you’ll be prompted for all of those passwords! You’ll create a new set of credentials for each session because you can’t use them across PowerShell sessions.

· Some commands default to a credential other than the one you opened the shell with. For example, the Active Directory module (included in Windows Server 2008 R2 and later) lets you map PSDrives to Active Directory domains, and you can specify a credential when doing so. When you change to an Active Directory “drive,” any Active Directory commands will inherit the credential used to map the drive. This only works for accessing the drive, not if you use the cmdlets against a remote domain. This is meant to be a convenient way of having multiple authenticated connections opened at the same time, without the need to manage credential objects or constantly retype passwords.

· When creating Active Directory accounts, remember the following when adding a password to the account:

o The Microsoft cmdlets and Active Directory provider can work directly with a secure string.

o The Quest cmdlets and Active Directory Services Interfaces (ADSI)-based scripts expect a plain string, so it has to be passed as $cred.GetNetwork-Credential().Password.

· It’s usually better to create a credential object and use that rather than attempt to create the credential in the call to the cmdlet. So use

· $cred = Get-Credential

· Get-WmiObject –Class Win32_OperatingSystem `

–ComputerName Myserver –Credential $cred

instead of

Get-WmiObject –Class Win32_OperatingSystem `

–ComputerName Myserver –Credential (Get-Credential)

This is because WMI may create the connection with your current credentials before generating the credential and it won’t swap to using the new credential. As a result your connection will fail.

· The WMI cmdlets have an –Authentication parameter. This has nothing to do with credentials; it sets the level of Distributed Component Object Model (DCOM) authentication and encryption of DCOM connections.

For the majority of your work in PowerShell, this information should suffice. But sometimes you need to get a little crazy.

12.3. Crazy credentials ideas

We’re constantly asked if there’s any way to save the password so that a script can run some command and pass an alternative credential without anyone needing to type the password again. The general idea is that people want to write some script or tool and then give that to people who don’t have permission to do whatever task the script performs. By “encoding” the password inside the script, they feel, they can have the command run without needing to give people the underlying permissions.

Generally speaking, this is a Bad Idea Of Epic Proportions. If you want someone to be able to do something, give them permission to do it. But we know how the real world works, and so we’ll entertain this crazy notion and explore how you might do it—but we’ll call out the associated risks, too, so that you’re making a smart decision.

12.3.1. Packaging your script

First, we suggest using a commercial tool that can package (not compile, although some will use that term) your script into an executable and that enables you to specify alternative credentials. The tool generally encrypts the credentials, and it takes care of running the script under those credentials. This approach has several upsides:

· The password isn’t in clear text, although it’s encrypted with a static shared secret, which means it’s hardly unbreakable. But it should be sufficient to deter most ordinary users.

· Your script can contain any commands, not just ones that offer a –Credential parameter.

· Users running the script won’t even necessarily know it’s a PowerShell script. Done properly, it’ll look like a stand-alone Windows executable.

You’ll have to have PowerShell installed on any machine where the packaged script runs, but that’s become less of a burden now that PowerShell is a default part of the Windows operating system. Tools that can package scripts in this fashion include PrimalScript and PowerShellStudio (formerly known as PrimalForms) from SAPIEN Technologies (www.sapien.com/software) and PowerGUI (www.powergui.org); there may even be others by the time you read this.

12.3.2. Saving a credential object

What if you don’t want to use a packager? Well, about the only other option is to save a credential object to disk and then have a script read it back in. This approach does limit you to commands that offer a –Credential parameter, which is far from every command on the planet.

Tip

The Start-Process and Invoke-Command cmdlets offer a -Credential parameter; you can use them to run about any other command or script, meaning you could potentially write a “bootstrap” script that launched a second one under alternative credentials.

We’ve emphasized what a bad idea this is, right? Okay, then here’s how you’d do it. Start by creating the password as a secure string and saving it to disk:

Read-Host –Prompt "Password" –AsSecureString |

ConvertFrom-SecureString |

Out-File C:\Password.txt

The password.txt file (or whatever you name it) will look something like this:

PS C:\> get-content .\password.txt

01000000d08c9ddf0115d1118c7a00c04fc297eb010000007471fb40e68f7e488e168bee016

034c80000000002000000000003660000c000000010000000348a2572718ecdd58149fbd7fa

887d930000000004800000a00000001000000023ee801dc09d5a2ef1138d153f9a6d6b18000

000b0c6389e4e1919f844b4a3b435be2e9087e2e813ccb2921c14000000d9e7273e09fa337d

6b19d679bad775935705bdc3

Safe enough for ordinary users, perhaps. To read that back in and turn it into a credential object, use this:

PS C:\> $cred = New-Object -Type System.Management.Automation.PSCredential

-ArgumentList "username",(Get-Content C:\Password.txt | ConvertTo-

SecureString)

PS C:\> $cred

UserName Password

-------- --------

username System.Security.SecureString

That gives you a credential object in $cred, which you can then pass to whatever -Credential parameter you like. The downside? As written, this will all work only on a single computer. When PowerShell performs that encryption, it does so using a locally stored encryption key. Move the script and password file to another machine and it won’t work, because the local encryption key will be different.

You can extend this technique by adding the –Key parameter to ConvertFrom-SecureString and ConvertTo-SecureString, supplying your own encryption/decryption key for each. Unfortunately, that puts your encryption key in clear text for anyone to read, making it trivial for even an unsophisticated user to decrypt the password. We told you this was a Bad Idea Of Epic Proportions. There isn’t any way around it, unless you create some hidden place for encryption keys to live and hope that your users (or an attacker) won’t think to look there.

Note

There’s also a free utility called PShellExec that you can look into. It’s intended to encode/encrypt a script’s contents for more secure execution. Again, it isn’t a perfect security mechanism, but you can decide if it’s sufficient for your needs.

12.3.3. Creating a credential without the GUI

A variation on this theme is to create a credential object without resorting to Get-Credential. As easy as it is to use, it does require a graphical component that you may want to skip. Perhaps you want a console-only approach. First, prompt for the username:

PS C:\> $username=Read-Host "Enter a username"

Enter a username: mycompany\bill

Next, you need a password:

PS C:\> $password=Read-Host "Enter the password" -AsSecureString

Enter the password: ***********

Finally, create a new PSCredential object as you did earlier with the New-Object cmdlet:

PS C:\> $cred=New-Object System.Management.Automation.PSCredential

$username,$password

PS C:\> $cred

UserName Password

-------- --------

Mycompany\bill System.Security.SecureString

You don’t have to specify the username. Perhaps you have a script where you want to use the Domain Administrator account. You might include code like this:

$admin="$env:userdomain\Administrator"

$password=Read-Host "Enter the password for $admin" -AsSecureString

$adminCredential=New-Object System.Management.Automation.PSCredential

$admin,$password

The $admin variable will be the Administrator account from the current user’s domain. By using the %USERDOMAIN% environment variable, you avoid hardcoding any domain names, which makes this code much easier to reuse.

Or if you want something handy that you can use as a one-liner, ideally something you’ll use interactively and not in a script, you can do this:

PS C:\> $cred = new-object PSCredential "mydomain\admin",

(convertto-securestring "P@ssw0rd" -AsPlainText -Force)

This will create a PSCredential object for MyDomain\Admin using the password. Anyone looking over your shoulder would see the password. It’d also be recorded if you’re using a transcript. A compromise might be this variation of a previous command:

PS C:\> $cred = new-object PSCredential "mydomain\admin",

( (Read-Host "Enter Password" -AsSecureString)

Enter Password: ********

PS C:\>

There’s not much benefit to a one-liner other than you might feel pretty cool.

12.3.4. Supporting credentials in your script

One last idea, and perhaps not that crazy at all, is to provide support for alternative credentials in your own scripts. This approach is useful if your script is running cmdlets that support alternative credentials. The following listing shows one way you might tackle this.

Listing 12.1. Supporting alternative credentials in a script

The PowerShell version number sets the minimum version of PowerShell that can run this script. In this case it can run under version 3.0 or 4.0. The script has a -Credential parameter as well as a -ComputerName parameter .

The script tests whether you’re running on the local machine and reminds you that you can’t use alternative credentials on the local machine before stopping. If it’s not the local machine, a command is created that’ll be executed using the alternative credentials.

If the value is already a PSCredential object, the script will use it :

if ($credential -is [System.Management.Automation.PSCredential]) {

Write-Verbose "Using a passed PSCredential object"

$Cred=$credential

}

All you’re doing is defining a new variable. Otherwise, any value will be treated as a string , which is in turn passed to Get-Credential:

elseif ($credential -is [string]) {

Write-Verbose "Getting credentials for $credential"

$Cred=Get-Credential -credential $credential

}

If $Cred is defined, then the script runs a WMI command that uses it . Otherwise, it runs without credentials:

if ($cred) {

Write-Verbose "Appending credential to command"

$command+=" -Credential `$cred"

}

else {

Write-Verbose "Executing without credentials"

}

#invoke the command expression

Write-Verbose "Running the WMI command"

Invoke-Expression $command

12.4. Summary

This chapter has covered the basics of credentials in PowerShell, including how to make reusable credential objects and even, if you’re bound and determined, how to persist a credential object or password on disk for later reuse. We beg you to be careful with credentials, and if you end up doing something silly and someone discovers your Domain Admin password as a result, well, we warned you.