PowerShell version 1 comes with a lot of operators, and the list becomes even longer in version 2 with cool new operators like -split and -join. Whether you’re writing scripts or using PowerShell interactively, dealing with multiple operators in an expression that possibly contains different enclosures (brackets, quotation marks, etc.) as well can be very tricky. It is very important to know how the PowerShell interpreter processes the expression so that you can get your expressions right the first time or, if you’re not so lucky, so that you can identify the problem in your expressions later and fix them.
Recently there have been several posts on the forums where the problem has been a lack of understanding of the operator and enclosure precedence in PowerShell. That’s not too surprising because the precedence order used by the PowerShell interpreter doesn’t seem to be documented at this time. You can find the precedence order of arithmetic operators through the about_arithmetic_operators help file, you can find out some precedence details for specific operators in various operator help files, and you can find out the precedence of command types through the about_command_precedence help file, but that’s about it. There is no single help file that documents the overall operator and enclosure precedence. It doesn’t seem to be listed in any of the PowerShell books I have read either.
Fortunately through some ad hoc experimentation and through some reading of the help documentation that does exist it is possible to figure out how all of this works. I’ve gone through that exercise recently and the resulting table of operator and enclosure precedence is below. Before getting to the table though there are a few important things I should mention, as follows:
- Any items that share the same row in the table have the same precedence and are evaluated from left to right when adjacent within an expression unless otherwise indicated.
- The intent of this table is to identify a precedence that can be used to create or troubleshoot more complicated expressions without a lot of guesswork. It is not intended to explain what each of the operators are and how you can use them (although to help understand expressions you might have to deal with I do mention a few details about some operators that function differently than the majority of operators in PowerShell).
- If you want to learn about the individual operators and see examples showing how they can be used I recommend you consult the about_operators help file and all of its related files.
With that out of the way, here is the operator and enclosure precedence table for PowerShell:
|
|
Any character placed inside of these enclosures is treated as part of a literal type name. The contents are not evaluated like an expression would be. |
| ” “ |
Double-quoted string enclosures |
| ‘ ‘ |
Single-quoted string enclosures |
| @” “@ |
Double-quoted here-string enclosures |
| @’ ‘@ |
Single-quoted here-string enclosures |
|
|
| {} |
Script block enclosures |
|
|
| () |
Nested expression enclosures |
| @() |
Array subexpression enclosures |
| $() |
Subexpression enclosures |
|
|
| . |
Property dereference operator |
| :: |
Static member operator |
|
|
|
|
|
[int]
[string[]]
etc. |
Cast operators |
|
Multiple adjacent operators in this row have a right-to-left evaluation. |
| -split (unary) |
Split operator (unary) |
| -join (unary) |
Join operator (unary) |
|
These operators can be used as unary or binary operators. Their precedence varies depending on how they are used. |
|
|
This operator is the array element separator. It can be used as an unary or binary operator. |
| ++ |
Increment operator |
| - - |
Decrement operator |
|
These unary operators can be used before or after a variable or property. When used before the variable or property (as a prefix operator), the value is incremented or decremented first and then the result is passed into the expression in which it is contained. When used after the variable or property (as a postfix operator), the value is passed into the expression in which it is contained and then the variable or property is immediately incremented or decremented. |
| - |
Negate operator |
| -not |
Not operator |
| ! |
Not operator |
| -bnot |
Bitwise not operator |
|
Multiple adjacent operators in this row have a right-to-left evaluation. |
|
|
|
|
|
|
| * |
Multiplication operator |
| / |
Division operator |
| % |
Modulus operator |
|
|
| + |
Addition operator |
| - |
Subtraction operator |
|
|
-split
-isplit
-csplit (binary) |
Split operator (binary) |
| -join (binary) |
Join operator (binary) |
| -is |
Type is operator |
| -isnot |
Type is not operator |
| -as |
Type as operator |
-eq
-ieq
-ceq |
Equal to operator |
-ne
-ine
-cne |
Not equal to operator |
-gt
-igt
-cgt |
Greater than operator |
-ge
-ige
-cge |
Greater than or equal to operator |
-lt
-ilt
-clt |
Less than operator |
-le
-ile
-cle |
Less than or equal to operator |
-like
-ilike
-clike |
Like operator |
-notlike
-inotlike
-cnotlike |
Not like operator |
-match
-imatch
-cmatch |
Match operator |
-notmatch
-inotmatch
-cnotmatch |
Not match operator |
-contains
-icontains
-ccontains |
Contains operator |
-notcontains
-inotcontains
-cnotcontains |
Does not contain operator |
-replace
-ireplace
-creplace |
Replace operator |
|
With the exception of the join operator and the type operators (-is, -isnot, and –as), each of the operators in this row has a case-sensitive and an explicit case-insensitive variant. Case-sensitive variants are prefixed with c (e.g. -ceq) and case-insensitive variants are prefixed with i (e.g. –ireplace). |
| -band |
Bitwise and operator |
| -bor |
Bitwise or operator |
| -bxor |
Bitwise exclusive or operator |
|
|
| -and |
Logical and operator |
| -or |
Logical or operator |
| -xor |
Logical exclusive or operator |
|
|
| . |
Dot-sourcing operator |
| & |
Call operator |
|
Unary operators that are only valid at the beginning of an expression, a nested expression, or a subexpression. |
| = |
Assignment operator |
| += |
Assignment by addition operator |
| -= |
Assignment by subtraction operator |
| *= |
Assignment by multiplication operator |
| /= |
Assignment by division operator |
| %= |
Assignment by modulus operator |
|
Multiple adjacent operators in this row have a right-to-left evaluation. |
Since this table is created through experimentation and through snippets of information about precedence that I was able to find in the help files, it may not be entirely accurate. If you find any problems with the precedence information provided here, please let me know and I’ll update this table accordingly.
[Update 09-July-2009: Fixed table formatting, added an index operator, added all case-sensitive and case-insensitive variants and adjusted the precedence for the property dereference and static member operators.]
Thanks,
Kirk out.
It is not uncommon to want to store the results of a command in an array so that you can refer to the contents of that array later in your script. This is often done by simply assigning the results of a command to a variable. For example, if you were importing the contents of a csv file into an array you might do it like this:
$collection = Import-Csv C:\newusers.csv
Commands like this will work forever as long as you have a csv file in the appropriate location. Occasionally though you may find that a script containing this command just stops working. It might not make sense to you at the time because the script hasn’t changed, so what’s the problem?
The problem is that in the above command there is no guarantee that $collection will actually store an array of objects because there is no guarantee that Import-Csv will return more than one object. In fact, there is no guarantee that Import-Csv will return any objects at all. If the C:\newusers.csv file is generated by some other tool, it is possible that it might contain 0, 1 or more entries. When it contains 0 or 1 entries, the $collection variable will either be $null or it will contain one object representing the single entry in the C:\newusers.csv file, respectively. If further down in your script you’re always treating $collection as if it is an array and using indices to access elements in the array, your script will suddenly start failing with errors.
Fortunately there is a way to ensure that the results of any command are stored in an array. All you need to do is wrap the command that returns the object(s) in array enclosures, like this:
$collection = @(Import-Csv C:\newusers.csv)
If you take this approach in your script and then later when you run your script C:\newusers.csv only contains one entry or perhaps doesn’t contain any entries at all, your $collection variable will still be an array so that you can check the size using the Count property ($collection.Count), access individual values using indices (e.g. $collection[0]), etc. This is a much safer practice to take when writing your scripts so that you can make sure that you have an array when you store an array.
Of course if you are dealing with very large collections of objects that aren’t already loaded in memory an even better practice is to avoid storing the entire collection in memory altogether by using pipelines and cmdlets like ForEach-Object to process the items in the collection, but I wanted to make sure you were aware of how you should properly handle and store arrays in variables in your scripts so that you’re not facing a script long after it has been written and asking yourself why it suddenly stopped working. There are plenty of other reasons why a script could stop working at some point but at least now you’re armed with the knowledge required to avoid one of those possibilities.
Kirk out.