Explaining Rationale for Function Output Behavior

Ok, so I discovered (the hard way) how functions sort of queue up any output from within the function and then return all of it as part of the return object.

function foo () {
  echo "SettingVar"
  $var = "varValue"
  echo "VarSet"
  return $var
}

> foo
SettingVar
varValue
VarSet

I initially found this to be idiotic. Why wouldn’t it behave the same as the rest of the code? Why wouldn’t it behave the same as most other languages that I’ve ever worked with? It made no sense to me.

This makes it much more difficult to take a block of code I suddenly find myself needing to reuse and just throw a function declaration and some brackets around it and reuse it. Suddenly the code has to be refactored to capture all output (or direct to something like Write-Host instead of standard output - which results in its own challenges) in order to avoid ruining the returning object.

But I tried to be open minded. Ok, maybe if they were trying to pseudo-recreate VBScript mechanics (function means return value;subroutine means no return) and this behavior was being used in a ‘subroutine’ to have it return all output at the end, then maybe I could understand it. But since they don’t distinguish between the two and, in fact, eliminated the distinction - only having functions (as far as I’m aware), that doesn’t apply here.

I’ve tried thinking of various ways in which this would be a technical requirement - something structural that prevents these from behaving as just another section in the main line of the code run - after all, it already shares variable scope with the rest of the code in a script, why would it then seem to “jump out” to execute as almost a separate run of code? But with my limited knowledge of the internals of Powershell, I couldn’t come up with anything.

I don’t understand it.

So I would appreciate it if anyone could help me understand why this is the behavior - is it a technical requirement? - and whether there is any possibility of this changing at any point - so that a function (or - to preserve legacy behavior - even create a new thing like a ‘runblock’ or ‘execblock’) would run within the current execution stream, output returns at exec time, not queued up until end of exec, and returning an obj only returns that obj.

Thank you

huh?

PS H:> get-help about_functions
TOPIC
about_Functions

SHORT DESCRIPTION
Describes how to create and use functions in Windows PowerShell.

LONG DESCRIPTION
A function is a list of Windows PowerShell statements that has a name
that you assign. When you run a function, you type the function name.
The statements in the list run as if you had typed them at the command
prompt.

Functions can be as simple as:

    function Get-PowerShellProcess {Get-Process PowerShell}

or as complex as a cmdlet or an application program.

Like cmdlets, functions can have parameters. The parameters can be named,
positional, switch, or dynamic parameters. Function parameters can be read
from the command line or from the pipeline.

Functions can return values that can be displayed, assigned to variables,
or passed to other functions or cmdlets.

The function's statement list can contain different types of statement
lists with the keywords Begin, Process, and End. These statement lists
handle input from the pipeline differently.

A filter is a special kind of function that uses the Filter keyword.

Functions can also act like cmdlets. You can create a function that works
just like a cmdlet without using C# programming. For more information,
see about_Functions_Advanced (http://go.microsoft.com/fwlink/?LinkID=144511).

Are you saying you don’t understand my issue?

Run this in ISE:

function Get-ItemIndexes ($haystack,$needle){ 

    echo "Called Get-ItemIndexes()"

    $returnObj = @()

    $count = 1
    foreach ($nextItem in $haystack) {

        echo "Item $($count): $nextItem"

        if ($nextItem -eq $needle) {

            echo "Found needle! Adding to return obj"
            $returnObj += $count
        }
        
        $count++
    }

    echo "Finished processing haystack. Returning $($returnObj.count) indexes found"
    return $returnObj
}


$array = @("a","b","c","b")

echo "Will call Get-ItemIndexes()"

$indexes = Get-ItemIndexes -haystack $array -needle "b"

echo "Get-ItemIndexes() completed"

Note the lack of output from inside the function. Now look at the contents of $indexes.

This apparently isn’t new/unknown (just to me until now):
https://technet.microsoft.com/en-us/library/hh847760.aspx
https://4sysops.com/archives/the-powershell-function-parameters-data-types-return-values/#returning-values
http://stackoverflow.com/questions/10286164/function-return-value-in-powershell

I can take a stab. PowerShell is designed to be friendly to admins, and attempts to abstract away some programming stuff like Return. Return isn’t really needed in a PowerShell function. Anything that goes out of stdout will go out of the function. That’s to be friendly to non-programmers.

Most people will say that you shouldn’t write powershell function with messages that display by default. The idea is that they shouldn’t display anything unless there is an error. Functions can be written to support verbose output, and that is where most people I’ve read suggest to put those type of messages. There is more than stdout and stderr in Powershell. There are streams for warnings and verbose messages.

This would be a more powershellish way to do that:

function Get-ItemIndexes{ 
    [cmdletbinding()]

    Param ($haystack,$needle) 

    Write-Verbose "Called Get-ItemIndexes()"

    $returnObj = @()

    $count = 1
    foreach ($nextItem in $haystack) {

        Write-Verbose "Item $($count): $nextItem"

        if ($nextItem -eq $needle) {

            Write-Verbose "Found needle! Adding to return obj"
            $returnObj += $count
        }
        
        $count++
    }

    Write-Verbose "Finished processing haystack. Returning $($returnObj.count) indexes found"
    return $returnObj
}


$array = @("a","b","c","b")

echo "Will call Get-ItemIndexes()"

$indexes = Get-ItemIndexes -haystack $array -needle "b" -Verbose

I think there’s two things going on.

One, “return” is really just an internal alias to Write-Output. It doesn’t “return and exit” as you would expect from all other programming languages, and was a bad choice on the PowerShell team’s part. They’ve owned up to this. In a Class construct, “return” behaves as you’d expect.

Two, you’re perhaps not entirely embracing where PowerShell is coming from. Functions aren’t meant to emit a whole bunch of accumulated objects; they’re expected to emit one, at a time, to the pipeline - so that the next command in the pipe can begin processing objects immediately. There are notable exceptions - like Sort-Object - which do block the pipeline and accumulate objects, due to the nature of what they’re doing. But in most cases, commands in the pipeline can run “kind of in parallel” and process one object at a time as they pass down the line.

PowerShell’s main ethos is as a shell, not a scripting language per se. The creation of functions - a kind of command - is mainly to enable composable pipelines. Thus the different approach. “Learn PowerShell Toolmaking in a Month of Lunches” kind of expounds on that ethos, and outlines the difference between a “tool” (command) and “controller script” in PowerShell’s perspective. Functions (commands) are meant to be fairly tightly scoped, atomic units of work that have a high expectation for reuse across business processes. A “controller script” is a less structured/formal procedural script which employs those commands to a specific use, implementing a business process of some kind.

“Echo” is similarly an alias (e.g., “Get-Alias echo” and see what you get). There’s also a strong difference between Write-Output and Write-Host, with only the former employing the pipeline, and the latter being restricted to screen display. Again, this is kind of a big part of PowerShell’s worldview that unfortunately isn’t well spelled-out in a lot of the docs (see, “Learn Windows PowerShell in a Month of Lunches,”) but playing along with PowerShell’s “way of doing things” is a lot easier and more productive than treating it like, say, VBScript.

(By the way, I find that a lot of people with a strong shell or scripting background from another OS or language run into exactly the same problems; PowerShell looks a lot more familiar than it is, and that facade obscures what it actually is doing and how it wants to live in the world. It’s worth spending some time learning it’s worldview, which is what I tried to do in the two books I referenced in the previous post.)

And happy to clarify/expand, if it helps. Just ask.

I understand your question but was confused as to why you would do that at all. I have hundreds of functions I’ve written since 2007 and none of them have write-host,echo,write-verbose etc. in them.

The way you’ve constructed it makes $indexes unusable, you want to return objects so you can reuse the output somewhere else in your script.

function get-p{get-process}
(get-p | select -First 1).processname

You may also want to review this discussion

I appreciate the feedback. I understand the purpose of functions and in most any other language, that made-up Get-ItemIndexes would only return a single object containing the indexes found. I also understand echo/Write-Output/Write-Host and their differences. And while some may never use Write-Output in their scripts, I do a great deal. It significantly helps in larger functions to be able to keep flow in log files of the output.

My major issue is that, beyond being counterintuitive vs most other scripting langs, when I take code that I wish to reuse, I am forced to refactor it instead of essentially tossing it, as is, into a function block. I have to either remove echo/Write-Output statements, change them to Write-Host/Write-Verbose (not to mention when I output objects for logging purposes), and alter the way the script itself is ran (-Verbose) in order to have everything function as expected. Plus, if I go this method, I have to make sure that my functions are prepared to utilize Write-Verbose.

Craig Duff: “PowerShell is designed to be friendly to admins, and attempts to abstract away some programming stuff like Return. Return isn’t really needed in a PowerShell function. Anything that goes out of stdout will go out of the function. That’s to be friendly to non-programmers.”

See, I don’t think it IS friendly to admins. Anyone that has used any other scripting lang, including other MS Windows lang like VBScript, would find this very counter-intuitive.

Dan Potters: saying ‘huh?’ and just listing the docs on functions made it seem like you didn’t know what I was talking about. And good for you. I’m glad this odd implementation of functions doesn’t affect you. For those of us that use output for logging purposes frequently, it does. But if you don’t use any output in your functions, then if PS functions were to be modified to behave more like functions in other languages, it shouldn’t significantly affect you. :slight_smile: I wrote the function (that would work perfectly well in most any other scripting language), specifically to demonstrate that it functions very unintuitively in PS.

It felt, as it appears Don Jones confirmed, like this implementation assumed a great deal about how functions would/should be used - which seems like a mistake.

I guess I’ll work through and replace all echo/Write-Output with Write-Verbose, and just deal with it.

Thanks for the feedback.