Summary: Microsoft Scripting Guy Ed Wilson teaches you how to create and to use intelligent defaults for your Windows PowerShell scripts or functions.
Microsoft Scripting Guy, Ed Wilson, is here. TechEd 2011 in Atlanta, Georgia was a lot of fun. TechEd is always a busy time because of the number of people one wants to see, and because of the number of great sessions one would like to attend. In addition, the Scripting Guy’s booth at TechEd seems to become like a magnet drawing scripters from the world over. I could spend an entire week just talking to people about Windows PowerShell—oh, wait a second, I did spend an entire week talking to people about Windows PowerShell. Cool!
One thing I saw in the 2011 Scripting Games, especially in the beginner division (but also in the advanced division), were scripts that did not handle the default condition very well. What do I mean when I say handle the default condition? I am talking about what happens—by default—when I run the script or the function.
Consider the Get-Service cmdlet. As shown in the following figure, this cmdlet dispays the Status, Name, and DisplayName of each service on the local machine.
Of course, what the Get-Service cmdlet actually does is return System.ServiceProcess.ServiceController objects, but this behavior is obscured under the covers of the cmdlet. The Windows PowerShell team could have just as easily caused the cmdlet to prompt for the name of a service, for an array of properties, for the formatting of the output, for running services, stopped services, paused services, or all services, and who knows what other parameters they could have devised to expose for the cmdlet. With each additional switch, parameter, or prompt, the cmdlet takes one further step away from usability as it slouches toward a morass of arcane obscurity.
In short, the Windows PowerShell team did the right thing. They asked the question, “When someone uses the Get-Service cmdlet, what are they more than likely seeking?” The answer is that someone who uses the Get-Service cmdlet is probably seeking basic status information about services on the local computer. By handling the default condition, the cmdlet becomes easier to use.
I am now going to review some of the ways I saw people not handle the default condition. Probably the worst way of not handling the default condition is to use the automatic variable $args. The $args variable contains unnamed arguments that are supplied to a Windows PowerShell script when the script runs. The reason this is the worst way, is that it forces me to run the Windows PowerShell script from the Windows PowerShell console prompt.
I cannot supply a value for $args very easily from the Windows PowerShell ISE. To actually do this from inside the Windows PowerShell ISE, you have to pass the path to the script and the command-line arguments from the Windows PowerShell ISE immediate (or command) pane. To me it is easier at that point to use the Windows PowerShell console instead. I generally have both open anyway. The following script fails when it is run:
Get-WmiObject -Class win32_bios -ComputerName $args
When I call the GetBiosArgs.ps1 script and do not pass any arguments, an error returns. The command and its associated error are shown in the following image.
The error, is a result of calling the script without passing any command-line arguments. If the script is called and passed the name of a computer, the script completes and returns basic BIOS information. This is shown in the following image.
One step up from using $args is to use Read-Host as shown here:
$computerName = Read-Host -Prompt "enter computer name"
Get-WmiObject -Class win32_bios -ComputerName $computerName
This methodology at least has the benefit of prompting the user to enter a ComputerName; however, it does not require a user to supply a ComputerName, and therefore errors are still possible. One problem is that often people do not know their computer name. In addition, Read-Host stops the script execution. This means that it is impossible to use the Task Scheduler to run the script. It will also cause an extremely problematic execution in a logon script. Not only this, but it is just plain annoying. When run from the Windows PowerShell ISE, a nice graphical input box displays, but that is small consolation for having to do the extra work of typing a computer name.
A better way to handle the default case of the local computer is to use the Param statement. The Param statement works the same way whether it is added to the top of a script or the top of a function. Here is an example of adding the Param statement to the top of a Get-Bios script:
$computerName = $env:COMPUTERNAME
Get-WmiObject -Class win32_bios -ComputerName $computerName
By using the Param statement and assigning a default value, I gain the ability to run the script without the need to supply the name of the local computer. It is also possible to use a period (.) to indicate the local computer, but there are at least two problems with this approach. The first is that there are some cases where a period does not work for the local computer. The second problem is a bit more esoteric, and that is, in cases where you want to perform logging, the $computername variable does not contain the actual computer name—instead, it simply contains a period.
This means that you will need to perform additional work to obtain the actual computer name for your log. Because you will type the ComputerName environmental variable only one time, it makes sense to plan ahead and use $env:ComputerName instead of using a period (“.”)…or worse yet, the literal LocalHost, which has an even more limited applicability than the period.
As illustrated in the following image, a script that utilizes the Param statement is usable with no parameters to run the script with the defaults. Additionally, if the parameter is used, the behavior of the script changes.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at firstname.lastname@example.org, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy