Scripting with PSCredential

I see this question often: how can I pass a parameter value for a PSCredential that might be a credential object or it might be a user name? In the past I’ve used code like this:


begin {
Write-Verbose -Message "Starting $($myinvocation.mycommand)"
write-verbose -Message "Using volume $($volume.toUpper())"
#convert credential to a PSCredential if a string was passed.
if ( $credential -is [system.management.automation.psCredential]) {
Write-Verbose "Using PSCredential for $($credential.username)"
}
ElseIf ($Credential) {
Write-Verbose "Getting PSCredential for $credential"
$Credential=Get-Credential $credential
}
} #Begin

This assumes $Credential is a parameter. But then I realized, why not take advantage of parameter validation? I could use the [ValidateScript()] parameter attribute and insert some code to test the incoming value. If it is already a PSCredential, don’t do anything. But if it is a string, call Get-Credential and use the result.


Param (
[Parameter(Position=0)]
[ValidateNotNullorEmpty()]
[string]$Computername=$env:Computername,
[ValidateScript({
if ($_ -is [System.Management.Automation.PSCredential]) {
$True
}
elseif ($_ -is [string]) {
$Script:Credential=Get-Credential -Credential $_
$True
}
else {
Write-Error "You passed an unexpected object type for the credential."
}
})]
[object]$Credential

When using ValidateScript your code has to return True or False. Or you can also Write and error if you want to customize the exception message a bit. That’s what I’ve done here. With this code I can either use -Credential with a value like jdhitsolutions\administrator or a saved PSCredential object. Let me show you a simple script with this in action, plus I’ll address another common question about using credentials with WMI-based scripts and functions.


#requires -version 2.0

<#
This function demonstrates how you might pass a credential
object as a parameter
#>

Function Get-OSName {
[cmdletbinding()]

Param (
[Parameter(Position=0)]
[ValidateNotNullorEmpty()]
[string]$Computername=$env:Computername,
[ValidateScript({
if ($_ -is [System.Management.Automation.PSCredential]) {
$True
}
elseif ($_ -is [string]) {
$Script:Credential=Get-Credential -Credential $_
$True
}
else {
Write-Error "You passed an unexpected object type for the credential."
}
})]
[object]$Credential

)

#Never write the same line of code more than once if you can avoid it
$wmiCommand="Get-WmiObject -Class Win32_Operatingsystem -Property Caption,CSName -ComputerName $Computername"
Write-Verbose $wmiCommand

if ($Credential) {
#add the credential to the command string
Write-Verbose "Adding credential"
#escape the $ sign so that the command uses the variable name
$wmiCommand+=" -credential `$Script:Credential"
}

Write-Verbose "Creating a scriptblock from the command string"
$sb=[scriptblock]::Create($wmiCommand)

Try {
Write-Verbose "Invoking the command"
Invoke-Command -ScriptBlock $sb -errorAction Stop |
Select @{Name="Computername";Expression={$_.CSName}},
@{Name="OperatingSystem";Expression={$_.Caption}}
}
Catch {
Write-Warning $_.Exception.Message
}
Finally {
Write-Verbose "Finished"
}

} #close function

So the challenge is if I have a credential I need to use a Get-Wmiobject expression that uses it, otherwise run an expression without it. I’m a big believer in avoiding writing the same line of code more than once so I’ll create a command string with my basic WMI command.


$wmiCommand="Get-WmiObject -Class Win32_Operatingsystem -Property Caption,CSName -ComputerName $Computername"

In this example the value for $Computername will be expanded and inserted into the string. If no credential is passed then this is the command I’ll run. But if a credential is passed, then all I need to do is append it to my command string.


$wmiCommand+=" -credential `$Script:Credential"

You must pay attention to a very subtle detail: I am escaping the $ sign in the variable name. I do not want PowerShell to expand the variable. I want the command string to use the variable as variable. That is, if using a credential I need the command to be: Get-WmiObject -Class Win32_Operatingsystem -Property Caption,CSName -ComputerName SERVER01 -credential $Script:Credential”

The last step is to turn this command string into a script block so it can be executed.


$sb=[scriptblock]::Create($wmiCommand)

Armed with a scriptblock I can use Invoke-Command.


Invoke-Command -ScriptBlock $sb -errorAction Stop |
Select @{Name="Computername";Expression={$_.CSName}},
@{Name="OperatingSystem";Expression={$_.Caption}}

The end result is a function that I can run with no credentials. If I use a credential value like jdhitsolutions\administrator, I’ll get prompted for the password from Get-Credential. Or if I pass a saved credential, the function will use it.

These techniques are by no means the only solution but I find them simple to follow and effective.

14 thoughts on “Scripting with PSCredential”

    1. I suppose it is habit. Plus there always the chance I might have another variable to add to the command string that I DO want replaced.

  1. Any reason you don’t use the built-in CredentialAttribute parameter transform attribute?

    function test-credentialparam{
    Param(
    [Parameter()]
    [System.Management.Automation.PSCredential]
    [System.Management.Automation.CredentialAttribute()]
    $Credential
    )
    Process { $credential; }
    }

    test-credentialparam -credential ‘beef’ #should prompt for a password…

  2. Jeff -good. I haven’t done much with script validations.

    One thing though that always gets me.

    The following will not work:
    [ValidateNotNullorEmpty()]
    [string]$Computername=$env:Computername

    You cannot assign a value to an argument or it will override the NotNullOrEmpty. Try it.

    You will never be prompted for a computername. Remove the default and it will work.

    1. I guess it’s a belt and suspenders thing with me. But this prevents someone from accidentally using a variable that might be empty.

      myscript -comp $oops

      Don’t confuse this with Mandatory. You can’t make a parameter mandatory and give it a default value.

  3. Why not use a parameter set one for a credentials object and the other for the username and let the powershell engine do the binding.

    1. That is certainly another option but the function would still need code to handle the multiple parameter sets.

  4. Hi! I use the following approach to pass credentials:

    [CmdletBinding()]
    param (
    [Parameter(
    Position = 0,
    Mandatory = $true,
    ValueFromPipeline = $true
    )]
    [Alias(“CN”)]
    [String]$ComputerName,

    [Parameter(
    Position = 1,
    Mandatory = $false
    )]
    [Alias(“RunAs”)]
    [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty
    )

    process
    {
    Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName -Credential $Credential
    }

Comments are closed.