52

I've learned from this Stack Overflow question, that PowerShell return semantics are different, let's say, from C#'s return semantics. Quote from the aforementioned question:

PowerShell has really wacky return semantics - at least when viewed from a more traditional programming perspective. There are two main ideas to wrap your head around: All output is captured, and returned. The return keyword really just indicates a logical exit point.

Let's look at this example:

function Calculate
{
   echo "Calculate"
   return 11
}

$result = Calculate

If you echo $result you will realise that something is not right. You'd expect this:

11

But the reality, what you actually see, is different:

Calculate
11

So instead of getting back only the intended return value, you actually get back an array.

You could get rid of the echo statements and not polluting the return value, but if you call another function from your function, which echoes something, then you're in trouble again.

How can I write a PowerShell function that only returns one thing? If the built-in PowerShell functions do return only one value, why can't I do that?

The workaround that I'm using now and I'd love to get rid of:

function Calculate
{
   # Every function that returns has to echo something
   echo ""
   return 11
}

# The return values is the last value from the returning array
$result = (Calculate)[-1]
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Balázs Szántó
  • 1,440
  • 3
  • 15
  • 29

11 Answers11

28

You can also just assign everything to null, and then return just the variable you want:

Function TestReturn {

    $Null = @(
        Write-Output "Hi there!"
        1 + 1
        $host
        $ReturnVar = 5
    )
    return $ReturnVar
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nate
  • 281
  • 1
  • 3
  • 2
  • 1
    Well, I can't pretend to know all of the consequences this may cause, but it *does* seem to work.. Especially with 3rd party libraries like the Azure-CLI that just insist on inject things into the function returns. – CarComp Feb 27 '18 at 20:37
  • One consequence is that output is not being output (the "Hi there!" part in the above example) – HVL71 Aug 02 '22 at 09:40
13

Don't use echo to write information, use Write-Host, Write-Verbose or Write-Debug depending on the message.

Echo is an alias for Write-Output which will send it as an Object through the pipeline. Write-Host/Verbose/Debug however is only console-messages.

PS P:\> alias echo

CommandType Name                 ModuleName
----------- ----                 ----------
Alias       echo -> Write-Output               

Sample:

function test {
    Write-Host calc
    return 11
}

$t = test
"Objects returned: $($t.count)"
$t

#Output
calc
Objects returned: 1
11

Also, if you return the value on the last line in your function, then you don't need to use return. Writing the Object/value by itself is enough.

function test {
    Write-Host calc
    11
}

The difference is that return 11 would skip the lines after it you use it in the middle of a function.

function test {
    return 11
    12
}

test

#Output
11
Frode F.
  • 52,376
  • 9
  • 98
  • 114
  • Hi, I tried this, but then, somewhere along the way I run into a problem, when I guess cli tool echoed something that ended up in the return value. Even though I could use Write-Host in my functions, but functions and tools by others can also echo. That's why I'm using this ```echo ""``` convetion instead. – Balázs Szántó Apr 10 '15 at 09:49
  • 1
    If you don't want to recieve the output from an external app or function/script, then you should either throw the output away `ipconfig | Out-Null` or save it to a variable `$ip = ipconfig`. If you need the output, save it to a variable, parse it and return the result when you want to (and in you format). Your sample doesn't show any external programs being called, it only says that you receive two objects, and that's because you're writing info. messages ("Calculating") to the user using `echo/Write-Output`. If that's not the answer you want then you need to edit your question. – Frode F. Apr 11 '15 at 22:14
  • In my test (I'm by no means a PS expert) `function f { & ipconfig | Write-Host ; return 123 }` does the trick so I suppose it's a matter of finding those pesky functions that use `echo` etc and apply the above treatment... – sxc731 Jul 10 '16 at 10:31
  • But isn't [`Write-Host` evil](https://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/)? – Peter Mortensen Jun 11 '19 at 21:46
13

Several answers already given do not fully answer your question because they do not account for other cmdlets you might call--as you point out--that might "pollute" your output. The answer from @Laezylion is essentially correct but I believe bears further elaboration.

It is clear that you appreciate that your current workaround--forcing every function to return an array then taking the last element--is not a very robust solution. (In fact, I think it is safe to call that a kludge. :-) The correct approach--and a direct answer to your question--is clear... though at first glance it sounds like a tautology:

Question: How do you ensure a function returns only one thing?
Answer: By ensuring that it returns only one thing.

Let me elucidate. There are really two kinds of functions/cmdlets in PowerShell: (A) those that return data and (B) those that do not return data but may instead report progress or diagnostic messages. Problems arise, as you have seen, when you try to mix the two. And this may easily happen inadvertently. Thus, it is your responsibility, as the function author, to understand each function/cmdlet call within your function: specifically, does it return anything, be it a true return value or just diagnostic output? You are expecting output from type A functions, but you need to be wary of any type B functions. For any one that does return something you must either assign it to a variable, feed it into a pipeline, or... make it go away. (There are several ways to make it go away: pipe it to Out-Null, cast it to void, redirect it to $null, or assign it to $null. See Jason Archer’s Stack Overflow post that evaluates the performance of each of these flavors, suggesting you shy away from Out-Null.)

Yes, this approach takes more effort, but you get a more robust solution by doing so. For a bit more discussion on this very issue, these A/B functions, etc. see Question 1 on A Plethora of PowerShell Pitfalls recently published on Simple-Talk.com.

Community
  • 1
  • 1
Michael Sorens
  • 35,361
  • 26
  • 116
  • 172
  • 8
    `+1` for really useful info. `-1` for not giving a maintainable solution, but instead implying the status quo is acceptable: *"Thus, it is your responsibility, as the function author, to understand each function/cmdlet call within your function: specifically, does it return anything, be it a true return value or just diagnostic output?"* - seriously people? How insane is that? I have to know whether anything I call *may* output diagnostic info at some point? Pfff. – Martin Ba Oct 27 '16 at 19:00
  • Oh and I might add, the comment wasn't to bash on you. Rather, I think I share the sentiment expressed by [poster Byron over there](http://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/#comment-112182) at http://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/ – Martin Ba Oct 27 '16 at 19:08
  • I appreciate your frustration, Martin. :-) However, I might suggest it is not quite as bad as it seems. Cmdlets from Microsoft, or other commercial source, will not scatter diagnostic outputs about willy-nilly; I added that part to be cognizant of your _own_ cmdlets. The part you really need to be mindful of is return values of cmdlets. But, as with any API, one should understand the cmdlets you're using, both what parameters they take and what values they return--if any. – Michael Sorens Oct 28 '16 at 00:34
  • 1
    Yeah, I'm OK with the CMdLets I write - however, half of them call into existing batch/exe/perl scripts (I can't reinvent the world), and *there* I do have to expect stout and stderr output and have to do something with it - piping it to null isn't a good idea. - Just as the other one said: Powershell doesn't play very nicely with the other kids. – Martin Ba Oct 28 '16 at 07:37
  • this is quite bad for me, for many reasons: first they take an (i dare to say) universal concept of returning one value and subvert it, then they don't do anything to warn you they have done so; as a programmer it took like half a day to figure out this in a script i'm doing, and i'm still not done understanding why it doesn't work... and where was this explained? in a rather unassuming paragraph in a secondary page... sorry but no, i sort of understand where ps is coming from since it's a script, but considering you are not given warnings about stuff you've done wrong it's just a nightmare – lunadir Jan 26 '23 at 22:00
8

Worth mentioning:

Any unmanaged command output inside your function will end up as a return value.

I use to get this trouble with a function managing XML content:

Function Add-Node(NodeName){
    $NewXmlNode = $Xml.createElement("Item")
    $Xml.Items.appendChild($NewXmlNode) | out-null
}

If you remove the Out-Null, the return of your function will be the Node basic properties... (You can also manage the exit code of the appendChild command ... it depends on the motivation) :)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Laezylion
  • 143
  • 10
3

@Michael Sorens's answer is useful, but it suggests a design strategy that seems rather onerous. I don't want to have to separate out data processing functions from functions that report progress or diagnostic messages. Reading his blogpost, I found a less hacky work-around to the OP's question. Here's the key sentence from the blog post:

A PowerShell function returns all uncaptured output.

So what if we capture our output?

function Calculate
{
   $junkOutput = echo "Calculate"
   return 11
}

$result = Calculate

Now $result just contains 11, as intended.

BobbyA
  • 2,090
  • 23
  • 41
2

You could replace echo (alias of Write-Output) with Write-Host:

PS> function test{ Write-Host "foo";"bar"} 
PS> $a = test                                    
test                                          
PS >$a                                         
bar
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Loïc MICHEL
  • 24,935
  • 9
  • 74
  • 103
2

echo is an alias for Write-Output which sends the object to the next command.

Using Write-Host should help:

PS:> function Calculate
>> {
>>    # Every function that returns has to echo something
>>    Write-Host "test"
>>    return 11
>> }
>>
PS:> $A = Calculate
test
PS:> $A
11
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
hysh_00
  • 819
  • 6
  • 12
0

The use of parenthesis seems critical when calling a function that accepts parameters. This example fills a DataTable from SQL Server, and returns an [int] from the first row, fourth column. Note the parenthesis around the actual function call assigned to the variable [int]$tt1

    function InvokeSQL-GetOneCount { param( [string]$cn, [string]$prj )
        $sql = "SELECT * FROM [dbo].[dummytable] WHERE ProjectNumber = '$prj' ORDER BY FormID, PartID;"

        $handler = [System.Data.SqlClient.SqlInfoMessageEventHandler] { param($sender, $event) Handle-Message -message $event.Message -fcolor "yellow" -bcolor "black"; };
        $conn = new-Object System.Data.SqlClient.SqlConnection($cn)
        $conn.add_InfoMessage($handler); 
        $conn.FireInfoMessageEventOnUserErrors = $true;

        $cmd = New-Object System.Data.SqlClient.SqlCommand;
        $cmd.Connection = $conn;
        $cmd.CommandType = [System.Data.CommandType]::Text;
        $cmd.CommandText = $sql;
        $cmd.CommandTimeout = 0;

        $sa = New-Object System.Data.SqlClient.SqlDataAdapter($cmd);
        $dt = New-Object System.Data.DataTable("AllCounts");
        $sa.Fill($dt) | Out-Null;

        [int]$rval = $dt.Rows[0][3];

        $conn.Close();
        return $rval;
    }
    clear-host;
    [int]$tt1 = (InvokeSQL-GetOneCount -cn "Server=[your server];DataBase=[your catalog];Integrated Security=SSPI" -prj "MS1904");
    write-host $tt1;
red5.c9
  • 3
  • 1
  • 3
0

I ended up using the following to make sure only one value is returned:

  • write-host 'some console message'
  • invoke-some-utility arg1 arg2 | write-host
  • functionCall arg1 arg2 | out-null # suppress any output
  • return theDesiredValue
Peter L
  • 2,921
  • 1
  • 29
  • 31
0

PowerShell does pipes awkwardkly. To work around the return value problem and save the pipeline for real data, pass the function a value by reference, that is, an array. The array can be loaded with the value you wish to return. Here's a simple example:

# tp.ps1
#   test passed parameters
#   show that arrays are passed by reference, and can be used to provide
#   return values outside the pipeline
function doprint
{
    process { write-output "value now: $psitem" }
}

function recurse($thing,     $rtx)
{
    $thing++
    if($thing -lt 20) {
        if($thing -eq 15) { $rtx[0] = $thing }
        write-output $thing | doprint
        recurse $thing     $rtx
    }
}

j=0
$rtv=@(4)
recurse $j     $rtv
write-output $rtv[0]
dan
  • 1
0

I was surprised how powershell return works. Because I never had to write a function which returns a value. It returns not the variable itself, but all output stream of the function. To not pass output, you would have to use Out-Null. So your example would be:

function Calculate
{
   . {
       echo "Calculate"
       $result = 11
       return
   } | Out-Null
   
   return $result
}

$result = Calculate

And result is:

11

Credits to https://riptutorial.com/powershell/example/27037/how-to-work-with-functions-returns

3lvinaz
  • 113
  • 3
  • 12