18

Is there a way to clear the return values inside of a function so I can guarantee that the return value is what I intend? Or maybe turn off this behaviour for the function?

In this example, I expect the return value to be an ArrayList containing ("Hello", "World")

function GetArray() 
{
    # $AutoOutputCapture = "Off" ?
    $myArray = New-Object System.Collections.ArrayList
    $myArray.Add("Hello")
    $myArray.Add("World")

    # Clear return value before returning exactly what I want?
    return $myArray
}

$x = GetArray
$x

However, the output contains the captured value from the Add operations.

0
1
Hello
World

I find this "feature" of Powershell very annoying because it makes it really easy to break your function just by calling another function, which you didn't know returned a value.

Update

I understand there are ways to prevent the output from being captured (as described in one of the answers below), but that requires you to know that a function actually returns a value. If you're calling another Powershell function, it can be, in the future, someone changes this function to return a value, which will then break your code.

Mas
  • 4,546
  • 5
  • 39
  • 56
  • 2
    How did you end up handling this issue. This is very annoying feature of powershell not allowing the control of the return value. – Feru Mar 15 '17 at 21:26
  • 1
    I just ended up using [void] everywhere. I also decided it was easier to do complex things in C#, then use powershell to glue them together. – Mas Mar 27 '17 at 09:17

9 Answers9

9

I ran into the same issue. In PS a function would return all output pipe information. It gets tricky to | Out-Null rest of the code in the function. I worked around this by passing return variable as a reference parameter [ref] . This works consistently. Check the sample code below. Note: some of the syntax is important to avoid errors. e.g. parameter has to be passed in brackets ([ref]$result)

function DoSomethingWithFile( [ref]$result, [string]$file )
{
    # Other code writing to output
    # ....

    # Initialize result
    if (Test-Path $file){
        $result.value = $true
    } else {
        $result.value = $false
    }
}
$result = $null
DoSomethingWithFile ([ref]$result) "C:\test.txt" 
Feru
  • 1,151
  • 2
  • 12
  • 23
  • I think this is the solution that is clearest to anyone reading the code. It also avoids cluttering your code with re-directions ( eg ```| Out-Null```) – JonoB Jan 02 '19 at 14:11
  • Wish there was a better way... but this is the most like "programming". Unfortunately, I have to use Write-Output to get logs to appear... – jlo-gmail Dec 11 '19 at 23:26
8

Pipe output to Out-Null, redirect output to $null, or prefix the function calls with [void]:

$myArray.Add("Hello") | Out-Null

or

$myArray.Add("Hello") >$null

or

[void]$myArray.Add("Hello")
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
Raf
  • 9,681
  • 1
  • 29
  • 41
  • Yes, I know this, but that means I have to pipe the output of every function call to null to guarantee my output isn't corrupted, which is a huge pain. And if I forget to do it, and the code works, it can be in the future, someone else changes a Powershell function and breaks another function. – Mas Jul 03 '14 at 16:36
  • 4
    It seems the answer is that there is no solution to this problem, other than voiding every function call, which is very annoying. – Mas Apr 10 '15 at 08:11
  • Unfortunately this works for Write-Output, except no output comes out. Runbooks wont write portal logs to anything but Write-Output, so I am buggered. – jlo-gmail Dec 11 '19 at 23:18
8

Using the information from user1654962, here is the way that I worked around this PowerShell issue. Since, if there is output information, PowerShell will return it as an array, I decided to make sure the function output was always an array. Then the calling line can use [-1] to get the last element of the array and we'll get a consistent return value.

function DoSomething()
{
  # Code that could create output
  Write-Output "Random output that we don't need to return"
  Write-Output "More random output"

  # Create the return value. It can be a string, object, etc.
  $MyReturnString = "ImportantValue"

  # Other code

  # Make sure the return value is an array by preceding it with a comma
  return ,$MyReturnString
}

$CleanReturnValue = ( DoSomething() )[-1]
Piper
  • 149
  • 2
  • 1
6

I too am having this exact same frustration! I had a function where a variable is supposed to count up $<variablename>++

Except. I had forgotten the "++" at the end in one iteration. Which was causing the variable's value to be sent to the output buffer. It was very aggravating and time consuming to find! MS should have provided, at minimum, an ability to flush the output buffer before the user gets to assign their desired return value. Anywhew... I believe I have a work around.

Since I assign a return value at the end of the function, my desired value will always be the last entry of the output buffer array. So I've changed the way I call my functions from:

If (FunctionName (parameters)) {}

To:

If ((FunctionName (parameters))[-1]) {}

My particular scenario bit me when I was trying to capture a function $False return. because of my typo above, my IF statement saw it as true because of the garbage in the output, even though my last function output was "Return $False". The last output assigned from the function was the $False value due to the problem resulting from the typo causing mis-calculation. So changing my Function Return Evaluation to:

If (!((FunctionName (parameters))[-1])) {#something went wrong!}

Did the trick by allowing me to evaluate only the last element of the output array. I hope this helps others.

user1654962
  • 91
  • 1
  • 5
  • 1
    This is a nice trick if you're expecting a single return value. I'll keep this in mind. – Mas Apr 10 '15 at 08:09
  • 1
    Note this does not work when you have a single return value which is a hashtable - you'd try to index into that. Solution: prefix the function call with `array` like `[array](FunctionName(parameters))` to force the result being an array of return values. This would then contain your hashtable at the expected index. – Heinrich Ulbricht Apr 21 '16 at 07:27
3

This is one that caused me some trouble with much larger functions.

One can take the first answer "Pipe output to Out-Null" a bit further. We take the code in our function and embed this in a script block, which we then pipe its output to Out-Null.

Now we don't need to trawl through all the code in the function...like so:

function GetArray() 
{
    .{
        # $AutoOutputCapture = "Off" ?
        $myArray = New-Object System.Collections.ArrayList
        $myArray.Add("Hello")
        $myArray.Add("World")

    } | Out-Null

    # Clear return value before returning exactly what I want?
    return $myArray
}

$x = GetArray
$x
GJ-Hardie
  • 31
  • 2
2

Just wanted to post this in case it helps. I had multiple functions I was calling. Each function returned a 0 or a 1. I used that to see if everything worked.

So basically...

function Funct1
{
   # Do some things
   return 1
}

$myResult += Funct1
$myResult += Funct2
$myResult += Funct3
if ($myResult -eq 3) { "All is good" }

Everything went along fine until I added a function that had some Copy-Item and such commands.

Long story short and an hour or two I'll never get back, I discovered this same issue with this darn pipeline thing. Anyway, with the help of sites like this, I found the cleanest way to assure you get your "Return" and nothing else.

$myResult += Funct1 -split " " | Select-Object -Last 1
$myResult += Funct2 -split " " | Select-Object -Last 1
$myResult += Funct3 -split " " | Select-Object -Last 1
da_jokker
  • 954
  • 2
  • 12
  • 16
0

Why not using the Powershell Array object, instead of the .Net arrayList ?

You could do something like :

function GetArray() {
  # set $myArray as a powershell array   
  $myArray = @()
  $myArray+="Hello"
  $myArray+="World"
  # $myArray=@("Hello", "World") would work as well

  return $myArray
}

This will return a "clean" array, as expected.

If you really need to get a System.collections.ArrayList, you could do it in 2 steps :

  • create a powershell array as explained above
  • Convert your powershell array in a .Net arrayList this way :

    # Get the powershellArray 
    $x = getArray
    # Create a .Net ArrayList
    $myArrayList = New-Object System.Collections.ArrayList
    # Fill your arraylist with the powershell array
    $myArrayList.AddRange($x)
    

Hoping this helps a bit.

Gruntzy
  • 423
  • 2
  • 7
  • 3
    The code in the question was just an example of the undesirable behaviour. I'm asking, in general, how to handle this situation. – Mas Jul 03 '14 at 16:30
0

Set variable scope to script or global.

function Get-Greeting {
    'Hello Everyone'
    $greeting = 'Hello World'
    'Ladies and Gentlemen'
    $script:greeting = $greeting
}

Get-Greeting
$greeting <== 'Hello World'
NOYB
  • 625
  • 8
  • 14
0

There is a way to return a 100% clean object from a method and you don't have to do any null assignments or output anything (i.e. Write-Information)! The only catch is that you have to define a class. In PowerShell 5.0 they introduced formal classes. If you have multiple methods you want to call statically without having to create an instance of the class then you can use the static keyword and call the methods statically and wrap them in a "UtilityMethods" class; otherwise it would make sense to create class instances.

Here is a class that defines a static method and how to call it.

class UtilityMethods {
        static [HashSet[int]] GetCleanHashSet(){
        $hs = New-Object System.Collections.Generic.HashSet[int];
        $null = $hs.Add(1);
        $null = $hs.Add(2);

        #Here is some code doing something completely unrelated to $hs. 
        #Notice how the return object and output are unnaffected by this and I don't need to do null assignments or Write-Information or anything like that.
        $hs2 = New-Object System.Collections.Generic.HashSet[int];
        $hs2.Add(99);

        return $hs; #This will return a clean HashSet, as you would expect.
    }
}

$cleanHS = [UtilityMethods]::GetCleanHashSet();
Write-Host "cleanHS = $cleanHS" #output: cleanHS = 1 2

You can also add into the method whatever you want to output to the console or logging, it won't affect the return object. For me this is the best solution in most cases to write functions that will return exactly what you're expecting without piping in junk. If you choose to use null assignments then you will have to do that on every line of code in the function that would pipe output, even if it's completely unrelated to the object you will return.

SendETHToThisAddress
  • 2,756
  • 7
  • 29
  • 54