Hey, Scripting Guy! How Can I Supply More Than One Value from the Command Line?

Hey, Scripting Guy! How Can I Supply More Than One Value from the Command Line?

  • Comments 1
  • Likes

Hey, Scripting Guy! Question

Hey, Scripting Guy! I do not think I write very complicated scripts, but quite often I find that it would be nice to be able to supply more than one value from the command line. It seems that everything I see written for Windows PowerShell uses the $args variable to get command line input, and I cannot figure out how to make $args accept more than one value. Can you help me? I looked over all the submissions I saw for the 2009 Summer Scripting Games, and I did not see anything there either.

- KA

SpacerHey, Scripting Guy! Answer

Hi KA,

Your question reminds me of a time when I was scuba diving off the coast of the Big Island, in the state of Hawaii in the United States. It was a beautiful spring day, with high-level puffy white clouds and a sea state that is best described as glass. From the deck of side of the dive boat, I could look down and see the rock formations 100 feet below the surface of the boat. Schools of humuhumunukunukuapuaa were swimming around looking for food or scuba divers to harass. (The humuhumunukunukuapuaa is a brightly colored reef trigger fish. By the way, if you ever get a scuba certification in Hawaii, you have to be able to pronounce the name of that fish.) The water was a warm 82 degrees. The highlight of the dive was a 40-foot-long lava tube, which was about as wide as an elevator. After doing a rolling entry into the water and joining my dive buddy, we headed over to the entrance to the lava tube and began our descent. The further down we went, the darker the inside of the lava tube became. After one rather narrow passage, we came upon a shelf, and lying there was this shark:

Image of a shark 

When your flashlight uncovers a smiling nine-foot shark inside a darkened space about the size of an elevatorlet's just say that you begin to have multiple sources of input.

KA, handling multiple input parameters from the command line of your script will not require you to wrestle elevator sharks. But if you need to supply multiple values via the command line, and you attempt to do so via the $args automatic variable, you will be greeted with the following error message that warns of a type mismatch. The error does not use the term “type mismatch,” but this is what is meant by the error. It states you are attempting to supply an object array to a string. The computername parameter, it states, requires a string for its input:

Get-WmiObject : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'ComputerName'. Specified method is not supported.
At C:\Users\edwils.NORTHAMERICA\AppData\Local\Temp\tmp774.tmp.ps1:18 char:47
+  Get-WmiObject -Class win32_bios -computername <<<<  $args
    + CategoryInfo          : InvalidArgument: (:) [Get-WmiObject], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.GetWmiObjectCommand

The error is not caused by the array. The error is caused because the $args automatic variable arrives as a System.Object array. The Get-WmiObject cmdlet will accept an array of computer names to the computername parameter. This is seen here where an array of computer names is supplied directly to the computername parameter and BIOS information is retrieved via the WIN32_Bios WMI class.

PS C:\> Get-WmiObject -Class Win32_Bios -ComputerName localhost,loopback


SMBIOSBIOSVersion : 7LETB7WW (2.17 )
Manufacturer      : LENOVO
Name              : Ver 1.00PARTTBLx
SerialNumber      : L3L4518
Version           : LENOVO - 2170

SMBIOSBIOSVersion : 7LETB7WW (2.17 )
Manufacturer      : LENOVO
Name              : Ver 1.00PARTTBLx
SerialNumber      : L3L4518
Version           : LENOVO - 2170


There are a couple of ways to solve this issue. The first way is to index into the array and force the retrieval of the computer names. This is shown in the Get-BiosArray1.ps1 script:

Get-BiosArray1.ps1

Get-WmiObject -Class win32_bios -computername $args[0]


The technique of indexing directly into the $args automatic variable works well. The problem is it looks like it would only retrieve the first item in the array, but it in fact retrieves both items. Because Windows PowerShell automatically handles the transition between a single item and multiple items in an array, the technique of indexing into element 0 of the array works whether one or more items are supplied. This issue of the way the Windows PowerShell $args automatic variable handles an array of information can be seen in the StringArgs.ps1 script:

StringArgs.ps1

'The value of arg0 ' + $args[0] + ' the value of arg1 ' + $args[1]


When the StringArgs1.ps1 script is run with the array "string1","String2" supplied from the command line, the entire array is displayed in $args[0] and nothing is displayed for $args[1]. This is shown here:

PS C:\> StingArgs.ps1 "string1","String2"
The value of arg0 string1 String2 the value of arg1
PS C:\>


A better way to handle an array that is supplied to the $args automatic variable is to use the ForEach-Object cmdlet and pipeline the array to the Get-WmiObject cmdlet. This is seen in Get-BiosArray2.ps1:

Get-BiosArray2.ps1

$args | Foreach-Object {
Get-WmiObject -Class win32_bios -computername $_
} 

When the Get-BiosArray2.ps1 script is started with an array of computer names from the Windows PowerShell prompt, the following output is displayed:

PS C:\> Get-BiosArray2.ps1 localhost,loopback
SMBIOSBIOSVersion : 7LETB7WW (2.17 )
Manufacturer      : LENOVO
Name              : Ver 1.00PARTTBLx
SerialNumber      : L3L4518
Version           : LENOVO - 2170

SMBIOSBIOSVersion : 7LETB7WW (2.17 )
Manufacturer      : LENOVO
Name              : Ver 1.00PARTTBLx
SerialNumber      : L3L4518
Version           : LENOVO – 2170


There are two advantages to using the ForEach-Object cmdlet. The first is readability of the code. It meets the principle of “least shock.” When someone reads the code and they see that the script accepts an array for input via the $args variable, they are not surprised when they see the script using the ForEach-Object cmdlet to walk through the array. The other advantage is that the script will work when a single value is supplied for the input.

Unfortunately, if the same approach is tried with the StringArgsArray.ps1 script, the value of the $args array is repeated twice. The StringArgsArray1.ps1 script is seen here:

StringArgsArray1.ps1

$args | ForEach-Object {
'The value of arg0 ' + $_ + ' the value of arg1 ' + $_
}


When the StringArgsArray1.ps1 script is started, the results seen here are displayed:

PS C:\> StingArgsArray1.ps1 "string1","String2"
The value of arg0 string1 String2 the value of arg1 string1 String2
PS C:\>

If you examine the output from the StringArgsArray1.ps1 script, you will see both elements of the $args array displayed. If you modify the StringArgsArray1.ps1 script so that you index into the array that is contained in the $_ automatic variable (which represents the current item on the pipeline), you will be able to retrieve both items from the array. The revised script is called StringArgsArray2.ps1, and it is shown here:

StringArgsArray2.ps1

$args | Foreach-Object {
'The value of arg0 ' + $_[0] + ' the value of arg1 ' + $_[1]
}

When the script is run, the correct information is displayed. This is seen here:

PS C:\> StingArgsArray1.ps1 "string1","String2
The value of arg0 string1 the value of arg1 String2
PS C:\>


Now that is pretty cool huh? Well KA, we have reached the end of another exciting Hey, Scripting Guy! article. Join us tomorrow as we continue looking at handling input to a Windows PowerShell script. If you want a sneak peek of what is coming up, you can always follow us on Twitter or join our Facebook group. If you get stuck while you are working on a script, don't forget the Official Scripting Guys Forum, which we link to from a tab on our home page. If you are having problems finding things on the new Script Center, don’t feel like the Lone Ranger. Keep your eyes out for Script Center 101 in which we will unravel the mysteries of the new site.

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • sorry guys, but in StringArgsArray1.ps1 you don't get any argument twice, as long as you don't repeat it in your output. Consider the following revised script:

    <b>arg_test.ps1</b>

    $args | % { $_.GetType().FullName; $_ }

    when called with two strings, the output is as follows:

    PS > .\arg_test.ps1 a b

    System.String

    a

    System.String

    b

    it even works with more complex input:

    PS > .\arg_test.ps1 a b 1 [math]::pow(2,20)

    System.String

    a

    System.String

    b

    System.Int32

    1

    System.String

    [math]::pow

    System.Object[]

    2

    20

    PS >

    Please keep us posted about your shark adventures.