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. 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.

    • 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.

    • 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
    }

  5. Pingback: Parameter validation « Mario's Blog

Comments are closed.