Add Credential support to PowerShell functions

Note

The original version of this article appeared on the blog written by @joshduffney. This article has been edited for inclusion on this site. The PowerShell team thanks Josh for sharing this content with us. Please check out his blog at duffney.io.

This article shows you how to add credential parameters to PowerShell functions and why you'd want to. A credential parameter is to allow you to run the function or cmdlet as a different user. The most common use is to run the function or cmdlet as an elevated user account.

For example, the cmdlet New-ADUser has a Credential parameter, which you could provide domain admin credentials to create an account in a domain. Assuming your normal account running the PowerShell session doesn't have that access already.

Creating credential object

The PSCredential object represents a set of security credentials such as a user name and password. The object can be passed as a parameter to a function that runs as the user account in that credential object. There are a few ways that you can create a credential object. The first way to create a credential object is to use the PowerShell cmdlet Get-Credential. When you run without parameters, it prompts you for a username and password. Or you can call the cmdlet with some optional parameters.

To specify the domain name and username ahead of time you can use either the Credential or UserName parameters. When you use the UserName parameter, you're also required to provide a Message value. The code below demonstrates using the cmdlet. You can also store the credential object in a variable so that you can use the credential multiple times. In the example below, the credential object is stored in the variable $Cred.

$Cred = Get-Credential
$Cred = Get-Credential -Credential domain\user
$Cred = Get-Credential -UserName domain\user -Message 'Enter Password'

Sometimes, you can't use the interactive method of creating credential objects shown in the previous example. Most automation tools require a non-interactive method. To create a credential without user interaction, create a secure string containing the password. Then pass the secure string and user name to the System.Management.Automation.PSCredential() method.

Use the following command to create a secure string containing the password:

ConvertTo-SecureString "MyPlainTextPassword" -AsPlainText -Force

Both the AsPlainText and Force parameters are required. Without those parameters, you receive a message warning that you shouldn't pass plain text into a secure string. PowerShell returns this warning because the plain text password gets recorded in various logs. Once you have a secure string created, you need to pass it to the PSCredential() method to create the credential object. In the following example, the variable $password contains the secure string $Cred contains the credential object.

$password = ConvertTo-SecureString "MyPlainTextPassword" -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential ("username", $password)

Now that you know how to create credential objects, you can add credential parameters to your PowerShell functions.

Adding a Credential Parameter

Just like any other parameter, you start off by adding it in the param block of your function. It's recommended that you name the parameter $Credential because that's what existing PowerShell cmdlets use. The type of the parameter should be [System.Management.Automation.PSCredential].

The following example shows the parameter block for a function called Get-Something. It has two parameters: $Name and $Credential.

function Get-Something {
    param(
        $Name,
        [System.Management.Automation.PSCredential]$Credential
    )

The code in this example is enough to have a working credential parameter, however there are a few things you can add to make it more robust.

  • Add the [ValidateNotNull()] validation attribute to check that the value being passed to Credential. If the parameter value is null, this attribute prevents the function from executing with invalid credentials.

  • Add [System.Management.Automation.Credential()]. This allows you to pass in a username as a string and have an interactive prompt for the password.

  • Set a default value for the $Credential parameter to [System.Management.Automation.PSCredential]::Empty. Your function you might be passing this $Credential object to existing PowerShell cmdlets. Providing a null value to the cmdlet called inside your function causes an error. Providing an empty credential object avoids this error.

Tip

Some cmdlets that accept a credential parameter do not support [System.Management.Automation.PSCredential]::Empty as they should. See the Dealing with Legacy Cmdlets section for a workaround.

Using credential parameters

The following example demonstrates how to use credential parameters. This example shows a function called Set-RemoteRegistryValue, which is out of The Pester Book. This function defines the credential parameter using the techniques describe in the previous section. The function calls Invoke-Command using the $Credential variable created by the function. This allows you to change the user who's running Invoke-Command. Because the default value of $Credential is an empty credential, the function can run without providing credentials.

function Set-RemoteRegistryValue {
    param(
        $ComputerName,
        $Path,
        $Name,
        $Value,
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )
        $null = Invoke-Command -ComputerName $ComputerName -ScriptBlock {
            Set-ItemProperty -Path $using:Path -Name $using:Name -Value $using:Value
        } -Credential $Credential
}

The following sections show different methods of providing credentials to Set-RemoteRegistryValue.

Prompting for credentials

Using Get-Credential in parentheses () at run time causes the Get-credential to run first. You are prompted for a username and password. You could use the Credential or UserName parameters of Get-credential to pre-populate the username and domain. The following example uses a technique called splatting to pass parameters to the Set-RemoteRegistryValue function. For more information about splatting, check out the about_Splatting article.

$remoteKeyParams = @{
    ComputerName = $env:COMPUTERNAME
    Path = 'HKLM:\SOFTWARE\Microsoft\WebManagement\Server'
    Name = 'EnableRemoteManagement'
    Value = '1'
}

Set-RemoteRegistryValue @remoteKeyParams -Credential (Get-Credential)

Get a credential at runtime

Using (Get-Credential) seems cumbersome. Normally, when you use the Credential parameter with only a username, the cmdlet automatically prompts for the password. The [System.Management.Automation.Credential()] attribute enables this behavior.

$remoteKeyParams = @{
    ComputerName = $env:COMPUTERNAME
    Path = 'HKLM:\SOFTWARE\Microsoft\WebManagement\Server'
    Name = 'EnableRemoteManagement'
    Value = '1'
}

Set-RemoteRegistryValue @remoteKeyParams -Credential duffney

Prompt for credentials

Note

To set the registry value shown, these examples assume you have the Web Server features of Windows installed. Run Install-WindowsFeature Web-Server and Install-WindowsFeature web-mgmt-tools if required.

Provide credentials in a variable

You can also populate a credential variable ahead of time and pass it to the Credential parameter of Set-RemoteRegistryValue function. Use this method with Continuous Integration / Continuous Deployment (CI/CD) tools such as Jenkins, TeamCity, and Octopus Deploy. For an example using Jenkins, check out Hodge's blog post Automating with Jenkins and PowerShell on Windows - Part 2.

This example uses the .NET method to create the credential object and a secure string to pass in the password.

$password = ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential ("duffney", $password)

$remoteKeyParams = @{
    ComputerName = $env:COMPUTERNAME
    Path = 'HKLM:\SOFTWARE\Microsoft\WebManagement\Server'
    Name = 'EnableRemoteManagement'
    Value = '1'
}

Set-RemoteRegistryValue @remoteKeyParams -Credential $Cred

For this example, the secure string is created using a clear text password. All of the previously mentioned CI/CD have a secure method of providing that password at run time. When using those tools, replace the plain text password with the variable defined within the CI/CD tool you use.

Run without credentials

Since $Credential defaults to an empty credential object, you can run the command without credentials, as shown in this example:

$remoteKeyParams = @{
    ComputerName = $env:COMPUTERNAME
    Path = 'HKLM:\SOFTWARE\Microsoft\WebManagement\Server'
    Name = 'EnableRemoteManagement'
    Value = '1'
}

Set-RemoteRegistryValue @remoteKeyParams

Dealing with legacy cmdlets

Not all cmdlets support credential objects or allow empty credentials. Instead, the cmdlet wants username and password parameters as strings. There are a few ways to work around this limitation.

Using if-else to handle empty credentials

In this scenario, the cmdlet you want to run doesn't accept an empty credential object. This example adds the Credential parameter to Invoke-Command only if it's not empty. Otherwise, it runs the Invoke-Command without the Credential parameter.

function Set-RemoteRegistryValue {
    param(
        $ComputerName,
        $Path,
        $Name,
        $Value,
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

    if($Credential -ne [System.Management.Automation.PSCredential]::Empty) {
        Invoke-Command -ComputerName:$ComputerName -Credential:$Credential  {
            Set-ItemProperty -Path $using:Path -Name $using:Name -Value $using:Value
        }
    } else {
        Invoke-Command -ComputerName:$ComputerName {
            Set-ItemProperty -Path $using:Path -Name $using:Name -Value $using:Value
        }
    }
}

Using splatting to handle empty credentials

This example uses parameter splatting to call the legacy cmdlet. The $Credential object is conditionally added to the hash table for splatting and avoids the need to repeat the Invoke-Command script block. To learn more about splatting inside functions, see the Splatting Parameters Inside Advanced Functions blog post.

function Set-RemoteRegistryValue {
    param(
        $ComputerName,
        $Path,
        $Name,
        $Value,
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

        $Splat = @{
            ComputerName = $ComputerName
        }

        if ($Credential -ne [System.Management.Automation.PSCredential]::Empty) {
            $Splat['Credential'] = $Credential
        }

        $null = Invoke-Command -ScriptBlock {
            Set-ItemProperty -Path $using:Path -Name $using:Name -Value $using:Value
        } @splat
}

Working with string passwords

The Invoke-Sqlcmd cmdlet is an example of a cmdlet that accepts a string as a password. Invoke-Sqlcmd allows you to run simple SQL insert, update, and delete statements. Invoke-Sqlcmd requires a clear-text username and password rather than a more secure credential object. This example shows how to extract the username and password from a credential object.

The Get-AllSQLDatabases function in this example calls the Invoke-Sqlcmd cmdlet to query a SQL server for all its databases. The function defines a Credential parameter with the same attribute used in the previous examples. Since the username and password exist within the $Credential variable, you can extract those values for use with Invoke-Sqlcmd.

The user name is available from the UserName property of the $Credential variable. To obtain the password, you have to use the GetNetworkCredential() method of the $Credential object. The values are extracted into variables that are added to a hash table used for splatting parameters to Invoke-Sqlcmd.

function Get-AllSQLDatabases {
    param(
        $SQLServer,
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty
    )

        $UserName = $Credential.UserName
        $Password = $Credential.GetNetworkCredential().Password

        $splat = @{
            UserName = $UserName
            Password = $Password
            ServerInstance = 'SQLServer'
            Query = "Select * from Sys.Databases"
        }

        Invoke-Sqlcmd @splat
}

$credSplat = @{
    TypeName = 'System.Management.Automation.PSCredential'
    ArgumentList = 'duffney',('P@ssw0rd' | ConvertTo-SecureString -AsPlainText -Force)
}
$Credential = New-Object @credSplat

Get-AllSQLDatabases -SQLServer SQL01 -Credential $Credential

Continued learning credential management

Creating and storing credential objects securely can be difficult. The following resources can help you maintain PowerShell credentials.