I’m very happy to see more and more people getting their hands dirty with Windows PowerShell. A common challenge I see across different support forums is getting information from one part of a script to another. Very often the user has created a function and is attempting to use its output elsewhere in their script. Often I can tell the scripter is coming from a VBScript background or at least thinking that way. There’s nothing necessarily wrong with writing a PowerShell script in a VBScript style, but these scripts are missing out. Let me walk you through several iterations of a function to demonstrate.
Here’s a variation on a function I saw posted recently.
In VBScript this would probably be a subroutine and is an acceptable way to modularize a repeatable block of code. However the first problem is one of scope.
The variables in the Get-Simple function only exist for the function scope. Once the function ends, the scope evaporates and so do all of the variables. Normally the best practice is to not access out of scope variables and to define any variable in a given scope before you use it. However if you know what you’re doing and plan carefully you can access out of scope variables. Here’s a revised version of the Get-Simple function.
Now this function is a little more usable in my PowerShell script, but there’s still a potential issue.
If you run this code and entered a number like 5 when prompted for a size, PowerShell will set$i to a value of 5555 because it will treat 5 as a string. Read-Host returns strings and this trips many PowerShell beginners. The solution is to cast variables to the correct type. I’ll show you an example in a moment.
Again, there’s nothing necessarily wrong with this function, but it doesn’t take advantage of PowerShell’s object nature. Consider this improved version.
The function uses essentially the same Read-Host expressions. Notice that I’m casting $size as a [int]. Even though Read-Host will output a string, as long as I enter something that looks like a number PowerShell will attempt to convert it to the correct type. By the way, depending on your needs you might also have used types like [int64] or [double]. The main change to this function is that it will write an object to the pipeline. I create an empty generic object using New-Object and then pipe it to Add-Member to define properties. The property values come from the Read-Host commands. A function like this is much more PowerShell-like.
PowerShell no longer has to rely on simple values. I have a rich object with properties of varying types that I can easily manipulate. I hope you can see how this is an improvement over the original function. But wait…there’s more.
When I write a function or even a script, I want it to be as flexible and easy to use as possible. Here’s one more improvement.
This function accepts parameters. I’ve cast each parameter to the appropriate type. This forces PowerShell to verify that the value is of the correct type. If not an exception will be raised and PowerShell will terminate the command. This is preferable to getting unexpected results from your function because an object is not the type you were expecting. Notice I’m also providing default values for each variable. In keeping with the original function I’ll prompt for a value if none is specified. The $size parameter has a default value of 1. If you run this code, the experience and results are essentially the same.
However, if I already know what value I want to specify for a given parameter, all I need to do is specify it.
This final version of my function has everything I need to fully take advantage of PowerShell. It writes an object to the pipeline that is easy to use and can be customized via input parameters, many with default values.
I hope you found this “object” lesson helpful. PowerShell v2.0 takes us even further but I’ll save that discussion for another day.
If you’d like to try out these functions, download a script file here.