It has been said that you can write BASIC code in any language. When I look at PowerShell code, I tend to see a lot of code that looks like transplanted C# code. It's easy to get confused sometimes, since C# and PowerShell syntax are similar, and when you are dealing with .NET framework objects the code is often nearly identical. Most of the time, though, the differences between the semantics are small and there aren't a lot of surprises.
I recently found one case, however, that stumped me for a while. What makes it more painful is that I found it while conducting a PowerShell training session and was at a loss to explain it at the time. Please read the following line and try to figure out what will happen without running the code in a PowerShell session.
$services=get-wmiobject -class Win32_Service -computername localhost,NOSUCHCOMPUTER -ErrorAction STOP
.
.
.
.
You're thinking about this, right?
.
.
.
.
.
.
Once you've thought about this for a few minutes, throw it in a command-line somewhere and see what it does.
The first thing (I think) that's important to notice is that the behavior is completely different from anything that you will see in any other language (at least in my experience).
In most languages, if you have an assignment statement and a function call one of three things will happen:
- The assignment statement is successful (i.e. the variable will be set to the result of the function call)
- The function call will fail (and throw an exception), leaving the variable unchanged
- The assignment could fail (due to type incompatibility), leaving the variable unchanged
In PowerShell, though, we see a 4th option.
- The function call succeeds for a while (generating output) and then fails, leaving the variable unchanged but sending output to the console (or to be captured by an enclosing scope).
Here's what the output looks like when it's run, note that I abbreviated some to make the command fit a line:
Not shown in the screenshot is that at the end of the list of localhost services is the expected exception.
How this makes sense is that an assignment statement in PowerShell assigns the final results of the pipeline on the RHS to the variable on the LHS. In this case, the pipeline started generating output when it used the localhost parameter value. As is generally the case with PowerShell cmdlets, that output was not batched. When the get-wmiobject cmdlet tried to use the NOSUCHCOMPUTER value for the ComputerName parameter, it obviously failed and since we specified -ErrorAction Stop, the pipeline execution immediately terminated by throwing an exception. Since we didn't reach the "end" of the pipeline, the assignment never happens, but there is already output in the output stream. The rule for PowerShell is that any data in the output stream that isn't captured (by piping it to a cmdlet, assigning it, or casting to [void]) is sent to the console, so the localhost services are sent to the console.
It all makes sense if you're wearing your PowerShell goggles (note to self---buy some PowerShell goggles), but if you're trying to interpret PowerShell as any other language this behavior is really unexpected.
Let me know what you think. Does this interpretation make sense or is there an easier way to see what's happening here?
-Mike
