Bookmark and Share

 

Hey, Scripting Guy! Question

Hey, Scripting Guy! I have started using functions in Windows PowerShell to encapsulate complex commands, and it works pretty well. However, I would like to be able to change the way the functions work on the fly. By this I do not mean that I will throw the computer across the room and see how the function works, but rather I want to modify what the function does based upon information I give it. Can I do this?

-- LK

Hey, Scripting Guy! AnswerHello LK,

Microsoft Scripting Guy Ed Wilson here, I can sympathize with the desire to encourage one’s computer to take flight. Keep in mind that it is generally not the computer’s fault—it is the software. In the message/messenger dynamic, hardware is almost always the messenger hoping not to be shot. The good thing about Windows PowerShell is that it is highly configurable. If you do not like the way a particular cmdlet works, you can create a function and modify it. Let us look at how to accomplish this task.

Note: Portions of today's Hey, Scripting Guy! article are excerpted from the Microsoft Press book, Windows PowerShell 2.0 Best Practices by Ed Wilson. The book is available for pre-order.

When using a function it is quite common to want to accept two or more parameters for input. This adds flexibility and usefulness to the function. The first method of passing parameters in Windows PowerShell is to use the $args automatic variable as seen in yesterday’s Hey, Scripting Guy! post.

Another way to pass parameters is to use named parameters. When using named parameters with a script, the Param statement precedes them. To use a named parameter within a function, you do not need to use the Param statement. You simply supply variables in each position for which you wish to have a parameter. The name of the variable becomes the name of the parameter. There are a few tricks to keep in mind when using both methods of passing multiple parameters. To that end, let us first examine the $args variable in a bit more detail.


Multiple parameters with $args

One way to pass two parameters is to use the $args automatic variable, and when passing two values to the function, you index into the array to retrieve a specific value. In the Get-WmiClass function, two values are passed when calling the function. The first value is used to hold the WMI namespace to search for WMI class names, and the second value is the type of WMI class for which to search. This function is useful for locating WMI classes. Use of the Get-WmiClass function is seen here:

Image of using the Get-WmiClass function


The Get-WmiClass function begins by retrieving two values from the $args variable. The $args variable is an automatic variable and is populated with whatever is fed to the function. The element from the first position is stored in the $ns variable, and the second element is kept in the $class variable. This is seen here:

$ns = $args[0]
$class = $args[1]

The Get-WmiObject cmdlet has a list switched parameter that will produce a list of all the WMI classes in the namespace. The namespace used is the one specified in the first position of the command used to call the function. The resulting list of all the WMI classes in the particular namespace is shunted to the pipeline. This line of code is seen here:

Get-WmiObject -List -Namespace $ns |

To make the list of WMI classes useful, the Where-Object cmdlet is used to filter out the unwanted WMI class names. Inside the code block for the Where-Object cmdlet, the automatic variable $_ is used to refer to the current item on the pipeline. The –match operator allows you to use a regular expression if desired to filter out the list of WMI class names. This line of code is shown here:

Where-Object { $_.name -match $class }

The complete Get-WmiClass.ps1 script is shown here.

Get-WmiClass.ps1

Function Get-WmiClass()
{
 #.Help Get-WmiClass "root\cimv2" "Processor"
 $ns = $args[0]
 $class = $args[1]
 Get-WmiObject -List -Namespace $ns |
 Where-Object { $_.name -match $class }
} #end Get-WmiClass


Multiple named parameters

When you have more than two parameters to supply to a function, it can get really confusing to keep track of both the position and the meaning of the parameters. In addition, when using named parameters, you can apply type constraints to prevent basic types of errors that could occur when supplying values from the command line.

In Get-WmiClass2.ps1, the Get-WmiClass function has been rewritten to take advantage of command-line arguments. The primary change was moving the $ns and the $class variables inside the parentheses following the name of the function. In addition, because both the namespace and the class names should be strings, we use the [string] type constraint to prevent the inadvertent entry of an illegal value such as an integer. Because the revised function is using named parameters, the two lines that parsed the $args variable have also been removed. The Get-WmiClass2.ps1 script file is therefore shorter than the Get-WmiClass.ps1 script, and it has more capability. The first line of the Get-WmiClass function is seen here:

Function Get-WmiClass([string]$ns, [string]$class)

An example of value of the type constraints is seen here:

Image of type constraints

 

In the first example shown in the previous image, the Get-WmiClass function is called with the value of 5 for the –ns parameter. This violates the type constraint of [string] for the ns parameter. The resulting error is “Invalid parameter.”

In the second example shown in the previous image, the Get-WmiClass function is called with the value of root/cimv3 for the –ns parameter. Because there is no root/cimv3 namespace in the WMI hierarchy (at least not yet), the function actually executes. The resulting error comes from WMI, which states the problem is an “Invalid namespace.” As a best practice, you should always apply type constraints to your function parameters. The rudimentary protection afforded by them easily justifies the minimal effort required to type them.

To call the Get-WmiClass function, you can use the entire parameter name, a shortened unique version of the parameter name, or no parameter at all. Examples of each way to call the function are shown in the following code. When supplying a parameter, you only need to type enough of the parameter name to ensure that it is unique. As a best practice, you should take this feature into account when naming parameters. If each parameter begins with a unique letter, users of the function can supply single-letter parameter names, and still maintain a rudimentary level of readability. As an example, in the Get-WmiClass function, if we had named the namespace namespace and the class name simply name, we would have been required to type the entire word name for the name parameter and names for the namespace. That does not shorten very well.

Get-WmiClass -ns "root\cimv2" -class "disk"
Get-WmiClass -n root\cimv2 -c disk
Get-WmiClass root\cimv2 disk

When using named parameters with functions, you do not need to include a string inside quotation marks unless it contains a comma, semicolon, or other special character that could be misinterpreted by the runtime engine. When working from the command line, I often take advantage of this technique to reduce typing. However, when working in a script, I like to include the quotation marks to improve readability and understandability of the code.

The complete Get-WmiClass2.ps1 script is seen here.

Get-WmiClass2.ps1

Function Get-WmiClass([string]$ns, [string]$class)
{
 #.Help Get-WmiClass -ns "root\cimv2" -class "Processor"
 
 Get-WmiObject -List -Namespace $ns |
 Where-Object { $_.name -match $class }
} #end Get-WmiClass

You can also create an alias for the function at the same time you define the function. Because this was for the Get-WmiClass function, we used the Get-Alias cmdlet to check for the existence of the chosen alias letter combination of gwc (selecting the first letter of each main word in the function name. You can use this command to see if the gwc alias is available:

Get-Alias -Name gwc

This is one of those times when you hope to receive an error because it means your chosen alias can be used. The error is seen in the following image:

Image of the error we were hoping to see


The completed Get-WmiClassWithAlias.ps1 script is seen here.

Get-WmiClassWithAlias.ps1

Function Get-WmiClass([string]$ns, [string]$class)
{
 #.Help Get-WmiClass -ns "root\cimv2" -class "Processor"
 
 Get-WmiObject -List -Namespace $ns |
 Where-Object { $_.name -match $class }
} #end Get-WmiClass
New-Alias -Name gwc -Value Get-WmiClass -Description "Mred Alias" `
-Option readonly,allscope

 

Well, LK, this should get you started with passing parameters to functions. Join us tomorrow as we continue examining ways to customize our Windows PowerShell environment.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, keep on scripting!

 

Ed Wilson and Craig Liebendorfer, Scripting Guys