Use PowerShell
The Shell Is Calling
The Shell Is Calling
Apr 25th
My previous posts about the Scripting Games had many references to how parameters were used in scripts and functions. Since those posts, I’ve had several requests to expand on that, so here we go…
In PowerShell V1, when we wrote scripts or functions, we had two main ways to retrieve parameters.
First off, we could parse $Args.
function Do-Something ()
{
$FirstArgument = $args[0]
$SecondArgument = $args[1]
...
This was a particularly bad approach (in most cases), since users did not get any help from tab completion on parameter names and they had to get the right order to make things work (meaning they had to be very familiar with the order of things required.
PowerShell promised us a better, more enlightened manner of getting input into our functions and scripts, declaring parameters.
function Do-Something ()
{
param ($FirstArgument, $SecondArgument)
...
Declaring our parameters like this allows a user to specify the parameter name and let a user take advantage of tab completion to find the parameters allowed. Parameters could be specified in any order, so long as there was enough information to match them up.
Since PowerShell is an object-based environment and can use .NET, we also gained the ability to specify the type of input we were expecting. This lets us catch errors much sooner (for example if we were expecting a number and someone tried to pass in “hello”) and gives the user immediate feedback that something is not right.
function Do-Something ()
{
param ([string]$FirstArgument, [int]$SecondArgument)
...
One of the final benefits that this syntax allows for is that we can specify a default value (or through some trickery create a mandatory parameter).
function Do-Something ()
{
param ([string]$FirstArgument="PowerShell Rocks",
[int]$SecondArgument = $(throw "I'm Required!!")
...
Version 2 of PowerShell kicks parameters into high gear. All the cool things that developers could do in managed code writing a PowerShell cmdlet were now in the hands of scripters.
By building on to the parameter declarations and adding one or more “attributes”, we can do all sorts of wonderful things. (This is by no means an exhaustive list. Check the about_Advanced_Parameters help topic for further information.)
We can
[parameter(mandatory=$true)][string]$FirstParameter
[parameter(mandatory=$true, position=0)][string]$FirstParameter
[parameter(mandatory=$true, ValueFromPipeline=$true)][string]$FirstParameter
[parameter(mandatory=$true, ValueFromPipelineByPropertyName=$true)][string]$Name
[parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[alias('Server','__Server', 'Name')]
[string]
$ComputerName
[parameter(ValueFromRemainingArguments=$true)][string]$Name
[parameter(HelpMessage="I want a name here")][string]$Name
[parameter(mandatory=$true, ValueFromPipelineByPropertyName=$true)] [ValidateNotNullOrEmpty()] [string] $Name
These different options throw open the doors for all sorts of cool scenarios and greatly reduce the amount of work we as scripters need to do to take input into our scripts and functions.
Once we’ve figured out how to make our parameters act like those of cmdlets, we can begin to leverage the same techniques we use with cmdlets (like scriptblock parameters).
Scriptblock parameters allow you to transform your pipeline input to any parameter that takes a value from the pipeline by property name.
For example, if I wanted to copy and rename a file based on the current file name, I could do something like
Get-childitem ~\documents\windowspowershell\*.ps1 | copy-item -destination {$_.fullname -replace 'windowspowershell','backup'}
Instead of having to transform the location inside of a foreach loop or create a mapping of the old location and new location, I can pass that transformation as an argument to the command and it will evaluate that for each item piped in.
My example uses cmdlets, but when I add the pipeline related parameter attributes to my parameters in my script or function, I gain that capability as well.
Apr 13th
I’ve seen a number of scripts that are using Write-Host to display output from their scripts.
For example:
Write-Host "SomeValue = $MyValue"
I’ll tell you what.. If I want to do anything with that output other than look at it in the console (or perhaps a shell transcript), it gets me nothing.
If you want to be able to do anything at all with your output (other than look at it, you need to output an object (remember, even text is treated as an object to PowerShell). Write-Host is quite inviting, but with a simple change to Write-Output, we’ll still see the text on our console, but the resulting output can be piped or redirected to a file.
Write-Output "SomeValue = $MyValue"
One of the common aliases from the DOS and CMD.exe world is "echo". That’s actually an alias for Write-Output!
After we get to writing output, it’ll only be a short time before you start moving to returning structured objects that the other built in commands can format, display, and output in any way necessary.
$MyReportingObject = New-Object PSObject -Property @{
Name="Scripting Games"
Value="Learning PowerShell"
Result="Being More Effective At Work"
}
Write-Output $MyReportingObject
Dec 8th
1. You Always Talk About Script club
2. You Always Talk About Script Club
3. If Someone asks for Help, And You Can Help, You Help
4. Two People Help One Person at One Time
5. One Module Per Person Per Night
6. All Scripts, All PowerShell
7. Scripts will be as short as they can be
8. If This is your First time at Script Club, You Have to Script
The first meeting will be on Tuesday, January 19th at 6:00 PM at the Greenfield Law Enforcement Center (in the Municipal Court Room), 5300 W Layton Ave, Greenfield, WI 53220. Register here.
All IT Professionals (sysadmins, network admins, developers, help desk, and all others) with any level of experience are welcome. If you DO NOT KNOW POWERSHELL, but you WANT TO – This is the place.
Pizza and soda will be provided, but please bring a laptop with PowerShell installed (version 1 or 2 is fine).
Andy Schneider describes a script club:
Script Clubs are like a hands on lab with no set topic or teacher. You bring an idea for a script, and ask your fellow PowerShell users for help getting the script written.
This is not a lecture or presentation based group (though we may have presentations from time to time). Script Club is focused on creating working scripts that will help you get your work done or just enjoy yourself.
Jul 21st
I started looking a little deeper at error handling in PowerShell after this StackOverflow question.
PowerShell has two kinds of errors – terminating errors and non-terminating errors.
Terminating errors are the errors that can stop command execution cold. Non-terminating errors provided an additional challenge, as you need to be notified of failed operations and continue with pipeline operations. To deal with this issue and to provide additional output options, PowerShell employs the concept of streams. There are three additional streams (other than the primary pipeline stream) available in PowerShell – Verbose, Warning, and Error (and . We’ll be concerning ourselves with the output from the Error stream.
There are some automatic variables in the shell that deal with errors as well. $ErrorActionPreference is a variable that describes how PowerShell will treat non-terminating errors. $ErrorActionPreference has four options: “Continue” (the default), “SilentlyContinue”, “Inquire”, and “Stop”. $error is an array of all the errors that have occurred in the current session up to the number specified in $MaximumErrorCount. $? is a boolean value that is $true if the previous operation succeeded and $false if it did not.
PowerShell does have some built in flexibility to turn non-terminating errors into terminating errors. Based on our setting of $ErrorActionPreference, PowerShell will treat a non-terminating error differently. If $ErrorActionPreference is set to ‘”Stop”, PowerShell will treat the errors from that scope and all sub scopes (unless explicitly overridden) as terminating errors. If “Inquire” is chosen as the $ErrorActionPreference, then PowerShell will prompt the user at the console upon each error to ask whether it should continue (treat as non-terminating) or halt (treat as terminating), as well as providing an option to drop into a nested prompt.
$ErrorActionPreference is a global preference that can be set, but the PowerShell runtime also allows more granular control by providing common parameters for -ErrorAction and -ErrorVariable (shorthand -EA and –EV) which allow you to determine per cmdlet (and in V2 per advanced function) how errors should be handled. The –ErrorAction parameter takes the same options as $ErrorActionPreference.
So what happens when you encounter an error?
If you hit a terminating error, your script, function, or command will stop. The error will be logged to the $error variable and written to the error stream. The $? will be set to false. You can use the trap construct (or the try/catch/finally construct in V2) to deal with terminating errors and we’ll cover that further in the fourth post in this series.
If you hit a non-terminating error, the result will depend on the $ErrorActionPreference in the scope or the –ErrorAction parameter. If the $ErrorActionPreference or –ErrorAction parameter is set to ‘Continue’, the error will be written to the error stream, added to the $error array, and the $? will be set to $false. ‘SilentlyContinue’ will not write an error to the Error stream, but $error and $? are updated. Finally, ‘Inquire’ provides you an option at each error as to whether to treat it as terminating or non-terminating. If treated as a non-terminating error, the error will be treated like the $ErrorActionPreference or –ErrorAction parameter is ‘Continue’.
Now, I’ve mentioned that some terminating and non-terminating errors both write to the Error stream, so where does that actually get reported back to the user? If an error has been written to the error stream, it will be written to the output stream (which may surface differently based on the PowerShell host) separately from the pipeline output unless it is redirected. This means that the exception objects, which are how the errors are represented, are not saved to a variable if you are assigning the pipeline output to a variable.
For example:
$results = Get-WMIObject Win32_Bios –ComputerName localhost, ComputerThatDoesNotExist
Get-WmiObject : The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)
At line:1 char:22
+ $results = Get-WmiObject <<<< Win32_Bios -ComputerName localhost, ComputerThatDoesNotExist
creates this output to the screen, but $results will only hold the results of the successful WMI query. This is where the –ErrorVariable comes in. You can specify a variable to hold the exceptions generated by a particular cmdlet (or in V2 advanced function). Specifying a variable does not remove an item from being written to the error stream and displayed as output.
In a console session, you can redirect the error stream with the redirection operators ( 2> or 2>>) or you can merge the error stream with the output stream (2>&1) and write the output to a file, the pipeline, or assign it to a variable.
Up next:
Other great PowerShell Error Handling posts: