Evaluate Weigh the pros and cons of technologies, products and projects you are considering.

Making Windows PowerShell scripts able to accept arguments

You script a task when you expect to be doing that task more than once. But you probably won't run exactly the same task each time. Rather than saving one script for every eventuality, we'll make the scripts able to accept arguments and assign them to variables, in order to give the scripts more flexibility.

When you script a task it's usually because you expect to be doing that task more than once. My previous Scripting School column showed you how to save Windows PowerShell scripts for later execution.

But you probably won't run exactly the same task each time. You may want to search for running services regularly, or else to sort files. But over time you'll need to search for different services or to sort files based on different criteria.

Therefore, rather than saving one script for every eventuality, we'll make the scripts able to accept arguments and assign them to variables, in order to give the scripts more flexibility.

All variables in PowerShell begin with a dollar sign ($). PowerShell has a bunch of special variables for its internal use. This month, we'll use two of these special variables:

  • $args, for storing arguments supplied to the script, and
  • $_, the current pipeline object used for conditional statements such as for…each.

Since $args is an array, the script identifies the variables in the array according to their index number, beginning with 0.

Introducing variables to a script

Let's start with a simple example from our first scripts for inventorying, stopping and starting services. Because Windows PowerShell takes wildcards, stopping or starting a service is easy. You connect to the service (identified below as "vm*") and then pipe that output to the stop-service cmdlet:
get-service vm* | stop-service

Enabling this script to take arguments is easy. You'll need to save it (I've saved it as stopsvc.ps1) and edit it to read:
get-service $args[0] | stop-service

Now run stopsvc.ps1 vm* and it will stop all the VMware-related services just as the original command did. Note: Do not run stopsvc.ps1 without arguments, as it will stop all the services on your computer. Get-service (as you may remember from the first column) enumerates all the services on your computer.

Assigning search results to a variable

You can make the process more efficient by inventorying the services in a slightly different way. Inventorying the services on a remote computer will take more time than doing it on a local computer. To plan for working on a remote computer, you could assign the list of services to a variable, then make queries against that variable in the rest of the script.

This is important when you'll be querying against the services more than once—perhaps if you wanted to check the status of more than one service. Rather than making a remote query against the list, you can get the list once, then query against the results. Since the list of services and the service status applies only as long as the script is running, you shouldn't have problems with the contents of the services variable being obsolete. To do this, you'll use a foreach command that works much like VBScript's For…Each conditional statement, enclosing the pieces to be worked on in brackets, as shown here.
$allservices = get-service $args | foreach-object { get-service -inputobject $allservices -include $_}

What we're doing is creating a function (the bit closed in curly brackets). This function gets the list of services from $allservices, then tells the script to run get-services against that variable for all the arguments. The $_ argument is a special variable (like $args) that stores the pipeline of input.

When you run this script, you will return the get-service information for all the services that can be identified by the arguments you provided. And because we're querying against the variable, we don't have to go back and get the list of services again for each instance. This code works well for both local and remote queries.

The scope of the above search will depend on the arguments you supply to the script. For example, if I supply vm* and co* to the script on this computer, I get the following output:

Status Name DisplayName
------ ---- -----------
Running VMAuthdService VMware Authorization Service
Running VMnetDHCP VMware DHCP Service
Running vmount2 VMware Virtual Mount Manager Extended
Running VMware NAT Service VMware NAT Service
Stopped COMSysApp COM+ System Application

Filtering search results by service status

Suppose you want to work with more than one service at a time, but not necessarily the entire list. How you do this depends on whether you want to filter according to service name (as we have already done) or on status. For example, when patching a server, it's useful to be able to inventory the running services before patching and then afterwards, in order to ensure that the patch didn't unexpectedly stop any services that should be running. The following command doesn't require additional input from you:
get-service | where-object {$_.Status -eq "Running"}

This command will return only the services that are currently running. It takes the results of get-service (these results are represented by $_) and filters them according to their property. You can combine this command with others to get only the commands with a certain name that are running by replacing get-service with $args.

Filling in the blanks Anyone who's worked with variables before knows that they can be combined with static text to clarify output or add information that may not be available to the person running the script. This is also true of PowerShell. To combine variables with hard-coded text, enclose the string in quotation marks, and use the + concatenation operator to combine the text and variables. For example, the following script will open the TechTarget site when you supply the argument "techtarget" to the script.
$args | foreach-object {
$iexp = new-object -comobject "InternetExplorer.Application"
$iexp.visible = $true
$iexp.navigate("www." + $_ + ".com")

As we did before, we're taking the arguments supplied to the script and passing them to the function enclosed within the curly brackets. Once in the function, we're using the foreach-object cmdlet to take all the arguments provided to the script, create an instance of Internet Explorer (identified as $iexp to make referring to it easier), make it visible, then navigate to the specified location. So that the script will accept multiple pieces of input without you having to identify them all by their index number, we're using the pipeline variable in the URL instead of the $args variable.

Now you're now ready to save scripts and pass information to them. You can treat this input either independently by referring to an argument's index number in the array, or use all of the input with the $_ variable. Finally, it's possible to combine variables with hard-coded text to make sure that it's formatted properly.

Christa Anderson
A Terminal Services MVP, Christa Anderson is the strategic technology manager for visionapp She formerly was program manager for the Microsoft Terminal Services team. She is an internationally known authority on scripting, the author of Windows Terminal Services, The Definitive Guide to MetaFrame XP, and co-author of the book Mastering Windows 2003 Server. If you have a scripting question for Christa, please e-mail her at [email protected] She often uses these emails as fodder for her scripting columns.

Dig Deeper on Windows Server troubleshooting