powershellreturnstdoutreturn-value

Powershell function returns stdout? (Old title: Powershell append to array of arrays appends stdout?)


I've learnt from this thread how to append to an array of arrays. However, I've observed the weirdest behaviour ever. It seems to append stdout too! Suppose I want to append to an array of arrays, but I want to echo debug messages in the loop. Consider the following function.

function WTF {
  $Result = @()
  $A = 1,2
  $B = 11,22
  $A,$B | % {
    Write-Output "Appending..."
    $Result += , $_
  }
  return $Result
}

Now if you do $Thing = WTF, you might think you get a $Thing that is an array of two arrays: $(1,2) and $(11,22). But that's not the case. If you type $Thing in the terminal, you actually get:

Appending...
Appending...
1
2
11
22

That is, $Thing[0] is the string "Appending...", $Thing[1] is the string "Appending...", $Thing[2] is the array @(1,2), and $Thing[3] is the array @(11,22). Nevermind that they seem to be in a weird order, which is a whole other can of worms I don't want to get into, but why does the echoed string "Appending..." get appended to the result??? This is so extremely weird. How do I stop it from doing that?

EDIT

Oh wait, upon further experimenting, the following simpler function might be more revealing:

function WTF {
  $Result = @()
  1,2 | % {
    Write-Output "LOL"
  }
  return $Result

Now if you do $Thing = WTF, then $Thing becomes an array of length 2, containing the string "LOL" twice. Clearly there is something fundamental about Powershell loops and or Write-Output that I'm not understanding.

ANOTHER EDIT!!!

In fact, the following even simpler function does the same thing:

function WTF {
  1,2 | % {
    Write-Output "LOL"
  }
}

Maybe I just shouldn't be using Write-Output, but should use Write-Information or Write-Debug instead.


Solution


  • Therefore, the PowerShell idiomatic reformulation of your function is:

    function WTF {
      
      # Even though no parameters are declared,
      # these two lines are needed to activate support for the -Verbose switch,
      # which implicitly make the function an *advanced* one.
      # Even without it, you could still control verbose output via
      # the $VerbosePreference preference variable.
      [CmdletBinding()]
      param()
    
      $A = 1,2
      $B = 11,22
      
      # Use Write-Verbose for status information.
      # It is silent by default, but if you pass -Verbose
      # on invocation or set $VerbosePreference = 'Continue', you'll 
      # see the message.
      Write-Verbose 'Appending...'
    
      # Construct an array containing the input arrays as elements
      # and output it, which *enumerates* it, meaning that each
      # input array is output by itself, as a whole.
      # If you need to output the nested array *as a whole*, use
      #    , ($A, $B)
      $A, $B
    }
    

    Sample invocation:

    PS> $nestedArray = WTF -Verbose
    VERBOSE: Appending...
    

    Note:


    As for your specific observations and questions:

    I've learnt from this thread how to append to an array of arrays

    While using += in order to incrementally build an array is convenient, it is also inefficient, because a new array must be constructed every time - see this answer for how to construct arrays efficiently, and this answer for how to use an efficiently extensible list type as an alternative.

    Nevermind that they seem to be in a weird order, which is a whole other can of worms I don't want to get into

    Output to the pipeline - whether implicit or explicit with Write-Output is instantly sent to the success output stream, notably while a script or function is still running.

    Thus, your Write-Output output came first, given that you didn't output the $Result array until later, via return.

    How do I stop it from doing that?

    As discussed, don't use Write-Output for status messages.