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.

Friday Fun – Get Ranked Object

Earlier this week on Google Plus, Hal Rottenberg posted a PowerShell idea he had. His goal was to identify a group of objects that would be statistically significant. For example, given a collection of files, group the files by size but ranked by size so that you might have a group for the largest files, then big files, then average files and finally small files. His idea was to calculate a rank using a logarithm of the file size. His code was quick and dirty so I took it, as I usually do, and ran with it. The result is a function that will analyse any group of objects and rank them based on a numeric property. Continue reading

Add WhatIf Support to Your PowerShell Scripts

In one of my recent articles for SMB IT, I included a PowerShell module. In the article I referenced that I included support for -Whatif in one of the functions. I was asked on Twitter to explain what I meant and how it works. So here goes. Continue reading

Friday Fun – Get Number Object

You most likely know that I’m all about the object and the PowerShell pipeline. Everything in PowerShell is an object. Pipe something to Get-Member and you can discover all of the object’s properties and methods (ie its members). Some objects, like strings, have many methods but very few properties. Some objects, like numbers have very little of either. I mean, what can you really do with a number? Well, I can have a bit of fun with one and maybe teach a few PowerShell concepts along the way. Continue reading

Simple Where Filters

The comment about how awkward it is in PowerShell to filter out folders with Get-ChidlItem, or its alias dir, came up the other day on Twitter. I’ll be the first to admit that running a DIR command and wanting to skip folders, or perhaps you only want top level folders, is more cumbersome than we would like in PowerShell v2. In PowerrShell v3 this has been addressed but for now we’re stuck with expressions like this:

[cc lang=”PowerShell”]
dir c:\work -rec | where {!$_.PSIsContainer} | group extension | sort count
[/cc]

If you find yourself frequently performing this type of filtering in the shell, here’s a tip that can save a little typing. Create these two functions:

[cc lang=”PowerShell”]
function IsDir {process {$_ | Where {$_.PSIsContainer}}}
function NotDir {process {$_ | Where {!$_.PSIsContainer}}}
[/cc]

These are so simple I’m not even going to include a text download. You can call them whatever you want. The functions are written as filtering functions, notice the use of Process script block. In short, these are wrappers for the standard Where-Object filter we’re used to using. But now I can do this:

[cc lang=”PowerShell”]
dir c:\work -rec | notdir | group extension | sort count
[/cc]

Or perhaps this:

[cc lang=”PowerShell”]
dir c:\work | isdir | Select FullName,@{Name=”Size”;Expression={(dir $_.fullname -rec | notdir | measure length -sum).sum}} | format-table -auto
[/cc]

If you find yourself needing this every day, put the function definitions in your profile. You can use this same technique for other common filters you tend to use often. What else can you come up with?