Being a C# developer, I recently found some use for Microsoft’s PowerShell (the cmd
replacement). What’s nice about PowerShell is that it has full access to the .NET framework.
However, there are also some very pit falls when coming from C# (or any related programming language).
There’s one very mean pit fall when it comes to functions and their return values that - if you don’t exactly know how PowerShell works - makes you pull out your hair.
Assume this PowerShell code:
function PrintAndReturnSomething {
echo "Hello, World"
return 42
}
$result = PrintAndReturnSomething
What do you expect $result
to be? An integer with value 42
, right? Let’s check it out.
We’re adding the following line to the end of the script:
echo ("Return type: " + $result.GetType().FullName)
Now, when you run the script, you’ll get:
Return type: System.Object[]
Wait, what? Where’s the “Hello, World”? And why the heck is $return
an object array?
The first problem (pit fall) is that PowerShell treats every non-captured object (i.e. one that isn’t assigned to a variable) as return value.
Functions Return Everything That Isn’t Captured
Let’s have a look at a different script for a moment:
function LongNumericString {
$strBld = new-object System.Text.StringBuilder
for ($i=0; $i -lt 20; $i++) {
$strBld.Append($i)
}
return $strBld.ToString()
}
One would expect that LongNumericString
returns just a string; but it doesn’t. Instead it returns an object[]
with this contents:
Capacity MaxCapacity Length
-------- ----------- ------
16 2147483647 1
16 2147483647 2
16 2147483647 3
16 2147483647 4
16 2147483647 5
16 2147483647 6
16 2147483647 7
16 2147483647 8
16 2147483647 9
16 2147483647 10
16 2147483647 12
16 2147483647 14
16 2147483647 16
32 2147483647 18
32 2147483647 20
32 2147483647 22
32 2147483647 24
32 2147483647 26
32 2147483647 28
32 2147483647 30
012345678910111213141516171819
The problem here is that $strBld.Append
returns a StringBuilder
object. And since this return value isn’t assigned to a variable, PowerShell considers it part of the return value.
To resolve this problem, add | Out-Null
to the $strBld.Append
line:
function LongNumericString {
$strBld = new-object System.Text.StringBuilder
for ($i=0; $i -lt 20; $i++) {
$strBld.Append($i) | Out-Null
}
return $strBld.ToString()
}
The keyword return
is completely optional. The expression return $strBld.ToString()
is equivalent to $strBld.ToString()
, and even to $strBld.ToString(); return
.
Function Return Values = Function Output
Back to our initial script. Now that we know how PowerShell composes return values for functions, why didn’t PrintAndReturnSomething
return just 42
?
One needs to understand that PowerShell doesn’t actually care about return value but rather about output of functions. The return value is just considered to be a part of the output. This way, PowerShell doesn’t just allow you to pipe text (like ls -l | sort
in bash), but to pipe actual objects.
For example, Get-ChildItem C:\ | Format-Table Name, Length
is the same as:
$child_items = Get-ChildItem C:\
Format-Table -InputObject $child_items Name, Length
PrintAndReturnSomething
uses echo
(which is an alias for Write-Output
) to print “Hello, World”. This is output and thus part of the function’s return value.
To force PowerShell to write something to the console (rather than including it in the return value), use Write-Host
instead of echo
.
The corrected code is:
function PrintAndReturnSomething {
Write-Host "Hello, World"
return 42
}
$result = PrintAndReturnSomething
# Return is now a "System.Int32" with value 42.
Summary
To sum things up:
- PowerShell functions will always return (as
object[]
, if there’s more than one return value):- all non-captured objects (i.e. objects that haven’t been assigned to variables)
- as well as all output (from
echo
/Write-Output
)
- Exclude from return value:
- Non-captured objects: prefix with
[void]
- Console output: use
Write-Host
instead ofecho
/Write-Output
- Non-captured objects: prefix with