PowerShell has some very valuable features, but it is a mistake to believe that all those skills that you've derived from more conventional programming languages will transfer over to PowerShell. There are some pitfalls for the unwary. Michael Sorens warns abut the most common PowerShell pitfalls and explains how to avoid them.

Pesky Parameter Problems

Many articles out there sing the praises of PowerShell. In fact, I have had several of my own published here on Simple-Talk. And, indeed, PowerShell can provide a tremendous boost in productivity for administrative-type tasks. However, as with any complex technology there are a variety of ways-some a lot more obvious than others-that you can cause problems for yourself without even realizing it. This article presents some of my favorite pitfalls, culled from my own experiences as well as from monitoring user questions on StackOverflow.

This article is designed to truly engage your brain. To get the most out of it I recommend:

First, try to answer the short list of questions without executing the code. Exercise your mental muscles. Give it some thought. Try to figure out the what . Next, go ahead and execute the code to see if you were right. If not, now given the answers, see if you understand the why . Finally, continue reading through the answers and explanations to reveal the what and the why .

The Questions

Try to answer these questions before executing the code.

Question 1

This function should square a number and report some diagnostic output with it, i.e. write some text to the console.

1 2 3 4 function square ( $x ) { Write-Output "Squaring $x..." return $x * $x }

What is the result of executing this statement?

1 $y = 4 + ( square 5 )

Question 2

You want to generate a (very:-) terse report to give an indication of how many XML files you have. What are the likely outcomes from executing this statement?

1 $myList = Get-ChildItem * . xml ; if ( $myList . Count -gt 100 ) { "big" } else { "small" }

Question 3

Given that the left side is equivalent to the right side for both of these scenarios…

Multiple Assignments Individual Assignments $a = $b = $c = “thing” $a = “thing” $b = “thing” $c = “thing” $a, $b = 1, 2 $a = 1 $b = 2

What are the values for $a and $b upon executing this line?

1 $a , $b = 1 , 2 * 3

Question 4

Consider a function that takes in a number and a switch parameter that indicates whether to negate the given number or not:

1 2 3 4 function f ( [ int ] $num , [ switch ] $negate ) { if ( $negate ) { - $num } else { $num } }

What is the result of executing this statement?

1 f -num42 -Negate $false

Question 5

(a) Given that the if statement below displays “I am true” on the console, how many times will the while loop display “hello” ?

1 2 3 4 5 $ condition1 = $ false - eq '' $ condition2 = '' - eq $ false if ( $ condition1 ) { " I am true " } while ( $ condition1 - and $ condition2 ) { " hello " }

(b) Given that the third line below prints the result “3” (the number of items in the list), what is output on the fourth line?

1 2 3 4 5 $ myList = 'a' , 'b' , 'c' $ anotherItem = 'd' $ myList . Length ( $ anotherItem + $ myList ) . Length

Question 6

Given this function definition…

1 2 3 4 5 6 7 function f ( $a , $b , $c ) { Write-output "Processing..." if ( $b -eq $null -or $c . Length -lt 5 ) { Write-Output 'one' } elseif ( $a -is [ int ] ) { Write-Output 'two' } else { Write-Output 'three' } }

What is the output of this statement?

1 f 25 , true , 'some string'

The Answers

As with most endeavors, the more you put in the more you get out, so have a go at answering the above question before continuing on to the answers. Each exposition below repeats the question but this time with the answer. Challenge yourself yet a second time: see if you can figure out why you get the given answer before reading the subsequent explanation.

Question 1: Functions Return Unexpected Results

Question This function should square a number and report some diagnostic output with it, i.e. write some text to the console. 1 2 3 4 function square ( $x ) { Write-Output "Squaring $x..." return $x * $x } What is the result of executing this statement? 1 $y = 4 + ( square 5 ) Answer 1 Method invocation failed because [ System . Object [ ] ] does not contain a method named 'op_Addition' .

Because (a) the return keyword in a function is optional, and (b) both function return values and the Write-Output cmdlet send output to the stdout stream, and (c) the Write-Output cmdlet can be invoked without even using the cmdlet name, then one can easily get unexpected output when calling a function.

There are two types of functions in PowerShell:

A function that does work but returns no result; it serves as a container to logically group several other statements. A function that returns a result.

Here is a function of type A that performs several long-running steps and writes messages to the stdout stream to let you know what step it is on.

1 2 3 4 5 6 7 8 function ProcessAll ( ) { Write-Output "Processing A." DoProcessA Write-Output "Processing B." DoProcessB Write-Output "Done." }

That works just as you would expect. In your PowerShell window, you will see this:

1 2 3 4 PS > ProcessAll Processing A . Processing B . Done .

Here is a function of type B, a pure mathematical function:

1 2 3 function square ( $ x ) { return $ x * $ x }

This also works just as you would expect. Here we store the result of the calculation in a variable that displays the contents of that variable. I have added one more line to show that it is, in fact, an integer as expected.

1 2 3 4 5 PS > $result = square 3 PS > $result 9 PS > $result . GetType ( ) . Name Int32

Problems arise when you attempt to create a function that is an A/B hybrid. Pretend that squaring takes a very long time so you want to report progress inside the square function. Just as before add one or more Write-Output statements:

1 2 3 4 function square ( $x ) { Write-Output "Squaring $x..." return $x * $x }

Now we have a rather unexpected result:

1 2 3 4 5 6 7 8 9 PS [ 1 ] > $result = square 3 PS [ 2 ] > $result Squaring 3 . . . 9 PS [ 3 ] > $result . GetType ( ) . Name Object [ ] PS [ 4 ] > $y = 4 + ( square 5 ) Method invocation failed because [ System . Object [ ] ] does not contain a method named 'op_Addition'

It seemingly does everything as expected until you reveal the type-an array of objects instead of an integer-and the execution, which completely fails! As to why this happens, look closer at the execution above… shouldn’t the “Squaring 3…” message show up after statement [1], when you execute the function, rather than after statement [2], when you display the contents of the variable?

The issue emanates from not understanding an important rule: A PowerShell function returns all uncaptured output.

“ A PowerShell function

returns all uncaptured output. “

That is, the PowerShell return statement is a red herring. Yes, a function will return the value sent from a return statement but that is because it is uncaptured output, which just means output that is not stuffed into a variable or fed to a pipe. But, by that definition, the Write-Output statement also generates uncaptured output! So the value passed to Write-Output is also returned by the function. Thus, this function returns two items: the string “Squaring 3…” and the integer 9; in other words a 2-element array of objects.

The Fix

The principle fix for this is to not use Write-Output inside of type B functions. (Recall that the Write-Output keyword is itself optional: "hello" is the same as Write-Output "hello" , so you cannot use either form!) You could use Write-Host instead because its output stream is separate from stdout, but don’t! See Jeffrey Snover’s Write-Host Considered Harmful. Instead use Write-Verbose, which writes to PowerShell’s separate verbose stream.

But there is more to the story… You must be ever-diligent when writing type B functions, because some standard cmdlets or .NET method calls send output to the stdout stream. You must, by the above rule, capture their output to prevent interference with your function’s return value. Do this by using the PowerShell equivalent of /dev/null, which can be done in a variety of ways:

1 2 3 4 $any | Out-Null [ void ] $any $any > $null $null = $any

Advanced note: See Jason Archer’s Stack Overflow post that evaluates the performance of each of these flavors, suggesting you shy away from Out-Null .

Question 2: The Conundrum of None, One, or Many

Question You want to generate a (very:-) terse report to give an indication of how many XML files you have. What are the likely outcomes from executing this statement? 1 $myList = Get-ChildItem * . xml ; if ( $myList . Count -gt 100 ) { "big" } else { "small" } Answer 1) “big”

2) “small”

3) The property ‘Count’ cannot be found on this object. Verify that the property exists. you will only see this error if you have strict mode enabled, which is recommended (see Set-StrictMode).

The code in this question is a ticking time bomb …

1 $myList = Get-ChildItem * . xml ; if ( $myList . Count -gt 100 ) { "big" } else { "small" }

…that is to say that code will blow up at some point, the only question is when. If Get-ChildItem returns more than one item then the type of $myList is an array, which has a Count property, and the conditional statement will then report big or small as appropriate. But there are two other significant possibilities: if Get-ChildItem finds only one XML file, then $myList will not be an array, and not have a Count property. Worse, if there are no XML files $myList will be null, which does not have any properties. Thus, there are three rather than two likely outcomes from executing the above statement:

big small Error: The property ‘Count’ cannot be found on this object. Verify that the property exists.

Note that you will only see this error if you have strict mode enabled, which is recommended (see Set-StrictMode).

Typically Get-ChildItem returns an array of objects: the return type is Object[], which has a Count property that you can access. But if Get-ChildItem has only one item to return then it returns a single FileInfo (or DirectoryInfo ) object, so the type is, of course FileInfo (or DirectoryInfo ). Neither of these types has a Count property, so it will cause an error. Finally, if Get-ChildItem has no items to return, it returns null, which of course has neither a type nor any properties, so you will also get an error.

The Fix

“ A function’s return type depends on

the number of items returned. “

There is a variety of ways to deal with this pervasive issue. Often the best course of action is to just to make sure that, whenever you can get an array, you do get an array. Here is one simple way to do that:

1 $myList = @ ( Get-ChildItem * . ps1 ) ; if ( $myList . Count -gt 100 ) { "big" } else { "small" }

Here, the Count property will always exist and it will return 0, 1, or more as appropriate, thereby eliminating the opportunity for the above error.

Question 3: Multiple Assignment Not Delivering Expected Values

Question What are the values for $a and $b upon executing this line? 1 $a , $b = 1 , 2 * 3 Answer $a is 1 $b is @(2, 1, 2, 1, 2)

PowerShell provides a handy multiple-assignment mechanism that feels like it has an “array nature” to it, given that this statement assigns 1 to $a and 2 to $b simultaneously:

There are two common expectations as to what assignments that statement makes-and both are wrong!

It seems reasonable to assume that we can apply other operators that maintain the array-like feel in this multiple assignment statement, i.e. $a should be 3 and $b should be 6:

Alternatively, it seems reasonable that the multiply operator is just applied very locally, so that $a is assigned 1 while $b is assigned 6.

But both of those are incorrect. Here is the actual result:

1 2 3 4 5 6 7 8 9 10 11 PS [ 1 ] > $a , $b = 1 , 2 * 3 PS [ 2 ] > $a 1 PS [ 3 ] > $b 2 1 2 1 2

The reason is operator precedence: the comma operator has higher precedence than the multiply operator. In other words:

1 2 3 4 $a , $b = 1 , 2 * 3 = > $a , $b = ( 1 , 2 ) * 3 = > $a , $b = 1 , 2 , 1 , 2 , 1 , 2 = > $a = 1 ; $b = 2 , 1 , 2 , 1 , 2

To understand how to arrive at that last line, there is one more thing to know about multiple assignment. Each variable on the left is assigned exactly one value from the right-except when there are more values on the right, then all the left overs are also given to the last variable on the left! (See about_Operator_Precedence and Assigning Multiple Variables on PowerShell’s about_Assignment_Operators page for more.)

The Fix

“The comma has a higher

precedence than arithmetic

operators

The comma has a higher precedence than arithmetic operators. Work with operator precedence rather than against it:

If you want $a to be 1 and $b to be 6 you need only add parentheses to force it to comply:

1 $a , $b = 1 , ( 2 * 3 )

If, however, you do want the multiplication to apply to each element, you must be explicit:

1 $a , $b = ( 1 * 3 ) , ( 2 * 3 )

Advanced note: For those thinking that, well, that’s not very PowerShell-like, there is a way, of course, that does leverage the array-nature of PowerShell: 1 $a , $b = 1 , 2 | % { $_ * 3 } I leave it as an exercise to play with that to understand the details.

Question 4: When a Boolean is Not Just a Boolean

Question Consider a function that takes in a number and a switch parameter that indicates whether to negate the given number or not: 1 2 3 4 function f ( [ int ] $num , [ switch ] $negate ) { if ( $negate ) { - $num } else { $num } } What is the result of executing this statement? 1 f -num 42 -Negate $false Answer 1 - 42

PowerShell has two types of parameters: SwitchParameters and everything else. A SwitchParameter is typically used to specify a condition that is true or is false. For example, normally Get-ChildItem returns both files and directories, but if you use the -Directory switch, then it will return only directories:

1 Get-ChildItem -Directory

The -Directory switch when present says to restrict output to directories and when absent says to not restrict the output. So it looks and acts just like a Boolean-but it is not quite the same because it is a SwitchParameter rather than a regular parameter. The difference is crucial: when you specify a regular parameter you provide a value (Get-Process -Id 25 or Set-Location -Path c:\temp ). With a SwitchParameter, its presence or absence deterÂ­mines its Boolean state, rather than any value being passed to it. Because, in fact, you cannot pass a value to it! So the code in the question…

1 f -num42 -Negate $false

…says to pass 42 to the $num parameter (-num 42), set the $negate SwitchParameter to true (-Negate), and, since the function f takes no more inputs, ignore anything else on the line ($false). Thus, all of these produce exactly the same result:

1 2 3 f -num 42 -Negate $false f -num 42 -Negate $true f -num 42 -Negate

The Fix

“A switch parameter

does not take a value“

Fairly obvious for this one: do not attempt to provide a value to a switch parameter.

Advanced note: As stated above, you cannot technically pass a value to a SwitchParameter because it does not have a value. But you can specify its state of existence. There are a couple ways to do that, the simplest being, for example: 1 f -num 42 -Negate : $false You could also set the existence of the switch via command-line splatting, which is a more useful thing to do. See my StackOverflow post for more details.

Question 5: A Question of Argument Placement

(a) Given that the if statement below displays “I am true” on the console, how many times will the while loop display “hello” ? 1 2 3 4 $ condition1 = $ false - eq '' $ condition2 = '' - eq $ false if ( $ condition1 ) { " I am true " } while ( $ condition1 - and $ condition2 ) { " hello " } (b) Given that the third line below prints the result “3” (the number of items in the list), what is output on the fourth line? 1 2 3 4 $ myList = 'a' , 'b' , 'c' $ anotherItem = 'd' $ myList . Length ( $ anotherItem + $ myList ) . Length Answer 0 6

Both (a) and (b) in this question may produce seemingly unexpected results and they do so for exactly the same reason: whether PowerShell operators are commutative largely depends on the operands. In pure mathematics, addition is always commutative regardless of the arguments: 234.212 + 5 is the same as 5 + 234.212. Fear not: the same is true regarding addition of numbers in PowerShell. When it comes to comparison, evaluating “does 5 equal 3?” is the same as evaluating “does 3 equal 5?” in algebra, and again in PowerShell, the testing of equality of integers is commutative ( ‘3 -eq 5’ yields the same result as ‘5 -eq 3’ ).

But the plus sign is overloaded in PowerShell: you could, for example, add strings and obviously “a” + “b” is not the same as “b” + “a” (“ab” vs “ba”). Thus, commutativity of the plus sign operator depends on the type of the arguments. On the other hand, the PowerShell equality operator “-eq” is always commutative, whether you are comparing integer with integer, string with string, etc.

Where it gets interesting is when you apply operators to different types. Consider part (a) of the question. The answer is that the loop outputs “hello” zero times because $condition1 is true but $condition2 is false! How can that be? With dissimilar operand types, PowerShell attempts to coerce the right-hand operand to the type of the left-hand operand. For $condition1 , the empty string is cast to a Boolean, and it evaluates to $false , so $condition1 is really comparing $false to $false , so the result is true. For $condition2 , $false is cast to the string “false” and thus it is really comparing the empty string to a 5-character string, with the result being false. (With thanks to mjolinor’s StackOverflow post for the gist of this.)

As to part (b) of the question, the answer is six rather than four as you might expect. First, consider if you reverse the arguments, $myList + $anotherItem adds one item to the 3-element array, yielding a 4-element array, so its length would be 4. But as stated in the question, $anotherItem + $myList , is not adding an element to an array. In fact, PowerShell applies the same automatic casting as with comparison operators: Here the left-hand side is a string, so it converts the right-hand side to a string, getting “a b c”. That code reduces to adding a string to a string, yielding “da b c”, and the length of that is 6. Thus in attempting to add an element to an array, the order of operands affects not just the order of the result, but whether the result even remains an array!

The Fix

“… the order of dissimilar

operands matters“

For testing truth, always put the Boolean first, because the order of dissimilar operands matters. e.g. ( $true -eq $x ) rather than ( $x -eq $true ): But you can also avoid the whole issue when it comes to Booleans by simply testing the truth of an expression directly. That is, line 1 is equivalent to line 2, and line 3 is equivalent to line 4:

1 2 PS [ 1 ] > if ( $ true - eq $ x ) { " true " } PS [ 2 ] > if ( $ x ) { " true " }

1 2 PS [ 3 ] > if ( $ false - eq $ x ) { " false " } PS [ 4 ] > if ( ! $ x ) { " false " }

For the case of adding an element to an array, always put the array on the left when using a plus sign. Alternately, use the assignment-by-addition operator; these two lines are equivalent:

1 $ myList = $ myList + " new item "

1 $ myList += " new item "

Advanced note: PowerShell lets you strongly type data but-in a similar way to the above discussion-where you put the typing matters. Both of these yield exactly the same, strongly-typed array of integers (without the type specification you would just have an array of objects):

1 2 [ Int [ ] ] $array1 = ( 1 , 2 , 3 , 4 , 5 ) $array2 = [ Int [ ] ] ( 1 , 2 , 3 , 4 , 5 )

If you display the type (e.g. $array1.GetType().FullName), the result is an array of integers ( System.Int32[] ) in both cases. However, if you then add an element to each array (e.g. $array1 += 6) then $array1 remains an array of integers but $array2 reverts to an array of objects! (With thanks to this post on PowerShell.com.)

Question 6: Three-Card Monte

Question Given this function definition… 1 2 3 4 5 6 7 function f ( $a , $b , $c ) { Write-output "Processing..." if ( $b -eq $null -or $c . Length -lt 5 ) { Write-Output 'one' } elseif ( $a -is [ int ] ) { Write-Output 'two' } else { Write-Output 'three' } } What is the output of this statement? 1 f 25 , true , 'some string' Answer 1 2 Processing . . . one

The difficulty of this question depends on how much you automatically think in a conventional proÂ­gramming language. The function in question accepts three parameters, and we pass in three arguments, so clearly you should have

1 2 3 $a <= 25 $b <= true $c <= 'some string'

So for this statement…

1 f 25 , true , 'some string'

…the function should output “Processing” followed by “two”. That is, the first conditional should fail ( $b is not null, the length of $c is not less than 5), and the second conditional should succeed ( $a is an integer): And yet, the actual answer is “Processing” followed by “one”.

Consider this follow-up question: for the same function f , would this statement produce the same result or not?

1 f ( 25 , true , 'some string' )

That second question hints at what is relevant in both questions: how do you specify parameters to pass to a function? For this second question, you do not get the same result. You do not get “Processing” followed by “two”. Nor do you get “Processing” followed by “one”. In fact, you do not even get “Processing” followed by “three”. Because you never get “Processing” output at all! Instead, you get a runtime error.

As the follow-up question illustrates, you can exacerbate your problem from the first question: here, your function never gets called at all; rather, you get a multitude of errors. The reason is that, unlike conventional languages, parentheses have nothing to do with calling a function! In PowerShell, parentheses are used to execute a command and then use the result of that command in place: But you have not given any valid PowerShell command to execute. Try just this-without the function f on the front-and you will see you get the same errors:

1 ( 25 , true , 'some string' )

The Fix

The reason both of these function calls produce unexpected results is that you are not calling the function correctly. To associate the parameters as indicated…

1 2 3 $a <= 25 $b <= true $c <= 'some string'

The function must be called with spaces rather than commas between arguments:

1 f 25 true 'some string'

“Pass parameters the PowerShell

way: separate with spaces“

When you use commas, you are creating a list of 3 elements that is then passed to parameter $a . You are passing nothing to parameters $b and $c , so they are both $null. Since $b is $null, the first conditional passes and you get “Processing” then “one”. Pass parameters the PowerShell way: separate with spaces

Advanced note: There are two right ways to call a PowerShell function and several wrong ways; you have seen two of the latter here. For an in-depth look at these, see Down the Rabbit Hole: A Study in PowerShell Pipelines, Functions, and Parameters.

Conclusion

I hope on your way to the end here you had a few “Aha!” moments and gained a better understanding of assignments in PowerShell, both direct and across the function-calling boundary. Keep the highlighted principles in mind as you work in PowerShell and you will increase your productivity and reduce your head-scratching. Of course, there are other areas of PowerShell use that could also cause consternation or frustration: But those are stories for another time…