248

I have developed a PowerShell function that performs a number of actions involving provisioning SharePoint Team sites. Ultimately, I want the function to return the URL of the provisioned site as a String so at the end of my function I have the following code:

$rs = $url.ToString();
return $rs;

The code that calls this function looks like:

$returnURL = MyFunction -param 1 ...

So I am expecting a String, however it's not. Instead, it is an object of type System.Management.Automation.PSMethod. Why is it returning that type instead of a String type?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ChiliYago
  • 11,341
  • 23
  • 79
  • 126
  • 3
    Can we see the function declaration? – zdan Apr 23 '12 at 19:17
  • 1
    No, not the function invocation, show us how/where you declared "MyFunction". What happens when you do: Get-Item function:\MyFunction – zdan Apr 23 '12 at 21:09
  • 1
    Suggested an alternate way to address this : http://stackoverflow.com/a/42842865/992301 – Feru Mar 16 '17 at 20:28
  • 1
    I think this is one of the most important PowerShell questions on stack overflow relating to functions/methods. If you're planning on learning PowerShell scripting you should read this entire page and understand it thoroughly. You'll hate yourself later if you don't. – Birdman Jun 27 '19 at 05:45
  • using static class-functions seems to me the proper clean solution as in this answer: https://stackoverflow.com/a/42743143/1915920 – Andreas Covidiot Feb 25 '21 at 22:41

10 Answers10

334

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

Thus, the following two script blocks will do effectively the exact same thing:

$a = "Hello, World"
return $a

 

$a = "Hello, World"
$a
return

The $a variable in the second example is left as output on the pipeline and, as mentioned, all output is returned. In fact, in the second example you could omit the return entirely and you would get the same behavior (the return would be implied as the function naturally completes and exits).

Without more of your function definition I can't say why you are getting a PSMethod object. My guess is that you probably have something a few lines up that is not being captured and is being placed on the output pipeline.

It is also worth noting that you probably don't need those semicolons - unless you are nesting multiple expressions on a single line.

You can read more about the return semantics on the about_Return page on TechNet, or by invoking the help return command from PowerShell itself.

Goyuix
  • 23,614
  • 14
  • 84
  • 128
  • 21
    so is there any way to return a scaler value from a function? – ChiliYago Apr 23 '12 at 22:04
  • I am not entirely sure what you mean by scalar value. For example, the following code returns a scalar value: `function Return-One { 1 }` - you could do similar things with any "scalar" values: strings, numbers, booleans, etc... – Goyuix Apr 23 '12 at 22:11
  • 13
    If your function returns a hashtable, then it will treat the result as such, but if you "echo" anything within the function body, including the output of other commands, and suddenly the result is mixed. – Luke Puplett Jan 28 '13 at 23:01
  • 7
    If you want to output stuff to the screen from within a function without returning that text as part of the function result, use the command `write-debug` after setting the variable `$DebugPreference = "Continue"` instead of `"SilentlyContinue"` – BeowulfNode42 Nov 05 '14 at 03:55
  • 10
    This helped, but this http://stackoverflow.com/questions/22663848/powershell-function-doesnt-have-proper-return-value helped even more. Pipe unwanted results of commands/function calls in the called function via " ... | Out-Null" and then just return the thing you want. – Straff Mar 21 '15 at 23:42
  • I'm starting to see why PS scripts are so incredibly slow at run-time! (Not really a criticism; I suspect we use PS for things that would be far better solved in C#, largely because folks *love* PS, even if they can't give (m)any good reasons why we "have to" use it. Our context is SharePoint....) – The Dag Jul 02 '15 at 14:25
  • 2
    @LukePuplett `echo` was the issue for me! That little side point is very very important. I was returning a hashtable, following everything else, but still the return value was not what I expected. Removed the `echo` and worked like a charm. – Ayo I May 27 '16 at 18:40
  • 1
    "PowerShell has really wacky return semantics - at least when viewed from a more traditional programming perspective." I find this wording is a tinybit inaccurate or is mixing terminologies. Actually, Powershell has the *traditional* shell semantic, i.e. standard unix shell mostly works the same way, except that in standard shell, the return statement is not trying to lie to you because it's well-specified that it just sets the exit status code. – Johan Boulé May 17 '18 at 17:20
  • Using the TechNet URLs for PowerShell documentation now redirects (so it is not really on TechNet). – Peter Mortensen Oct 07 '19 at 11:28
  • @Ayo Quick note that 'echo' is simply a PowerShell predefined alias for the Write-Output cmdlet – BentChainRing Oct 24 '19 at 18:39
  • The return semantics are weird for one used to traditional full-fledged imperative languages and it sure isn't helped by letting `return` take an argument. It should honestly just have been a standalone statement. That would force the coder (and reader) to learn about how standalone variables and side-effect outputs work in Powershell. And make the return itself very simple indeed and not strange at all. – Jonas Dec 31 '19 at 21:06
  • Is there a good source of information about why these semantics are as they are? – DWR Apr 09 '20 at 17:27
  • This works best `Write-Information "Your comments go here..." -InformationAction Continue` instead of simply writing statements on console. – Shrikant Prabhu Jul 01 '20 at 02:19
  • using static class-functions seems to me the proper clean solution as in this answer: https://stackoverflow.com/a/42743143/1915920 – Andreas Covidiot Feb 25 '21 at 22:40
  • Wished I've seen this before as wasted a long time trying to figure out why data return preceded with Boolean. In order to avoid this, be wary of returns of other functions from within a function. Assign their value to a variable to "contain" it, if this makes sense. – Roger Apr 30 '21 at 07:23
  • `| Out-Null` the "unwacker" has some use – Mark Schultheiss Aug 23 '23 at 22:31
68

This part of PowerShell is probably the most stupid aspect. Any extraneous output generated during a function will pollute the result. Sometimes there isn't any output, and then under some conditions there is some other unplanned output, in addition to your planned return value.

So, I remove the assignment from the original function call, so the output ends up on the screen, and then step through until something I didn't plan for pops out in the debugger window (using the PowerShell ISE).

Even things like reserving variables in outer scopes cause output, like [boolean]$isEnabled which will annoyingly spit a False out unless you make it [boolean]$isEnabled = $false.

Another good one is $someCollection.Add("thing") which spits out the new collection count.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Luke Puplett
  • 42,091
  • 47
  • 181
  • 266
  • 2
    Why would you just reserve a name instead of doing the best practice of properly initializing the variable with a value explicitly defined. – BeowulfNode42 Oct 30 '14 at 03:49
  • It was just an example of an operation that generates unexpected output. But personally, I'm a C# developer, so its just habitual. – Luke Puplett Oct 30 '14 at 08:41
  • 2
    I take it you mean that you have a block of variable declarations at the start of each scope for every variable used in that scope. You should still, or may already, be initializing all variables before using them in any language including C#. Also importantly doing `[boolean]$a` in powershell does not actually declare the variable $a. You can confirm this by following that line with the line `$a.gettype()` and compare that output to when you declare and initialize with the string `$a = [boolean]$false`. – BeowulfNode42 Nov 05 '14 at 03:47
  • In C#, value types are initialized to their default values, a boolean being false. It's normal to write `bool isEnabled;` but you need to assign a value before reading, the compiler will warn you. For class fields, this is not necessary, you can read without assigning a value. – Luke Puplett Nov 06 '14 at 05:34
  • Regarding, PS not actually assigning/allocating a variable, that is well worth noting! Another difference to C# that's caught me out. I tend to get in trouble with dynamic languages. – Luke Puplett Nov 06 '14 at 05:47
  • 1
    `I tend to get in trouble with dynamic languages` This is why it is good practice to initialise your variables in any language even when the compiler takes care of things for you, as it makes your skills/habits more portable, and the code slightly less ambiguous when someone else is reading it. – BeowulfNode42 Nov 07 '14 at 00:19
  • 1
    It's not stupid - it was by design...it's done to accommodate the pipeline, which has unique benefits and allows you to capture output easily, such as command line utilities. Other languages require some gymnastic coding to capture the stdout where PoSH does it with one character. – KoZm0kNoT Mar 30 '15 at 13:42
  • 3
    You're probably right, I'd just prefer a more explicit design to prevent accidental writes. – Luke Puplett Mar 31 '15 at 21:44
  • 8
    Coming from a programmer's perspective, although I'm a PS-n00b, I am not so sure I find this the very worst of the many annoying aspects of PS syntax. How on earth did anyone think it preferable to write "x -gt y" rather than "x > y"?!? Surely at least the built-in operators could have used a more human-friendly syntax? – The Dag Jul 02 '15 at 14:27
  • 10
    @TheDag because it's a shell first, programming language second; `>` and `<` are already used for I/O stream redirection to/from files. `>=` writes stdout to a file called `=`. – TessellatingHeckler Oct 11 '15 at 02:51
  • Yup. Instead of calling "myCollection.Add($thing)", I called "$result=myCollection.Add($thing)", and that got rid of all the crap in my return value. – Jay Godse Nov 03 '16 at 21:05
  • @TessellatingHeckler. A better parser would be able to distinguish whether the context is a logical test or I/O for most cases. – crokusek Feb 28 '17 at 19:10
  • @crokusek but then you break the pattern of operators being `-and, -match, -split, -gt, -replace, -le, -or, -gt, -ne` and have some as dash-name and some as *character-which-already-has-a-use* (assuming you're right, which I'm not sure of - How would the compiler know if `$x = invoke-webrequest google.com; $x.StatusCode > $y` should write to a file or output a bool? The operators act as filters on an array, should `1..10 > 5` filter or redirect IO? What if it's text - `get-content names.txt > "eve"` is valid PS to drop names alphabetically before 'eve'... it is with `-gt` at least). – TessellatingHeckler Feb 28 '17 at 19:33
  • I am but a lowly dissenter however I would nominate you on the PS 6 committee. Mainly support within if(...) statements has got to be -gt 95% of the cases, Could require "->" for redirects for ambiguous cases. – crokusek Feb 28 '17 at 23:37
  • using static class-functions seems to me the proper clean solution as in this answer: https://stackoverflow.com/a/42743143/1915920 – Andreas Covidiot Feb 25 '21 at 22:42
60

With PowerShell 5 we now have the ability to create classes. Change your function into a class, and return will only return the object immediately preceding it. Here is a real simple example.

class test_class {
    [int]return_what() {
        Write-Output "Hello, World!"
        return 808979
    }
}
$tc = New-Object -TypeName test_class
$tc.return_what()

If this was a function the expected output would be

Hello World
808979

but as a class the only thing returned is the integer 808979. A class is sort of like a guarantee that it will only return the type declared or void.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
John Chalker
  • 613
  • 5
  • 7
  • 12
    Note. It also supports `static` methods. Leading to the syntax `[test_class]::return_what()` – Sancarn Apr 29 '18 at 22:24
  • 1
    "If this was a function the expected output would be 'Hello World\n808979" -- I don't think that's correct? The output value is always only the value of the last statement, in your case `return 808979`, function or not. – Armen Michaeli Feb 20 '19 at 12:12
  • 2
    @amn Thank you! You are very correct the example would only return that if it created output within the function. Which is what annoys me. Sometimes your function may generate output and that output plus any value you add in the return statement gets returned. Classes as you know will only return the type declared or void. If you rather use functions then one easy method is to assign the function to a variable and if more than the return value is returned the var is an array and the return value will be the last item in the array. – John Chalker Feb 21 '19 at 19:08
  • 3
    Well, what do you expect from a command called `Write-Output`? It's not the "console" output that is referred to here, it's the output for the function/cmdlet. If you want to dump stuff on the console there are `Write-Verbose`, `Write-Host` and `Write-Error` functions, among others. Basically, `Write-Output` is closer to the `return` semantics, than to a console output procedure. Don't abuse it, use `Write-Verbose` for verbosity, that's what it's for. – Armen Michaeli Feb 23 '19 at 14:27
  • 2
    @amn I am very aware this is not the console. It was mealy a quick way to show the way a return deals with unexpected output inside a function vs a class. in a function all output + the value you return are all passed back. HOWEVER only the value specified in the return of a class is passed pack. Try running them for yourself. – John Chalker Feb 24 '19 at 19:14
43

As a workaround I've been returning the last object in the array that you get back from the function... It is not a great solution, but it's better than nothing:

someFunction {
   $a = "hello"
   "Function is running"
   return $a
}

$b = someFunction
$b = $b[($b.count - 1)]  # Or
$b = $b[-1]              # Simpler

All in all, a more one-lineish way of writing the same thing could be:

$b = (someFunction $someParameter $andAnotherOne)[-1]
XDS
  • 3,786
  • 2
  • 36
  • 56
Jonesy
  • 569
  • 4
  • 6
  • 8
    `$b = $b[-1]` would be a simpler way of getting the last element or the array, but even simpler would be to just not output values you don't want. – Duncan Nov 28 '14 at 18:53
  • 1
    @Duncan And what if I do want to output the stuff in the function, while at the same time I also want a return value? – Utkan Gezer Jun 15 '19 at 13:58
  • @ThoAppelsin if you mean you want to write stuff to the terminal then use `write-host` to output it directly. – Duncan Jun 17 '19 at 10:09
10

The existing answers are correct, but sometimes you aren't actually returning something explicitly with a Write-Output or a return, yet there is some mystery value in the function results. This could be the output of a builtin function like New-Item

PS C:\temp> function ContrivedFolderMakerFunction {
>>    $folderName = [DateTime]::Now.ToFileTime()
>>    $folderPath = Join-Path -Path . -ChildPath $folderName
>>    New-Item -Path $folderPath -ItemType Directory
>>    return $true
>> }
PS C:\temp> $result = ContrivedFolderMakerFunction
PS C:\temp> $result


    Directory: C:\temp


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----         2/9/2020   4:32 PM                132257575335253136
True

All that extra noise of the directory creation is being collected and emitted in the output. The easy way to mitigate this is to add | Out-Null to the end of the New-Item statement, or you can assign the result to a variable and just not use that variable. It would look like this...

PS C:\temp> function ContrivedFolderMakerFunction {
>>    $folderName = [DateTime]::Now.ToFileTime()
>>    $folderPath = Join-Path -Path . -ChildPath $folderName
>>    New-Item -Path $folderPath -ItemType Directory | Out-Null
>>    # -or-
>>    $throwaway = New-Item -Path $folderPath -ItemType Directory 
>>    return $true
>> }
PS C:\temp> $result = ContrivedFolderMakerFunction
PS C:\temp> $result
True

New-Item is probably the more famous of these, but others include all of the StringBuilder.Append*() methods, as well as the SqlDataAdapter.Fill() method.

StingyJack
  • 19,041
  • 10
  • 63
  • 122
8

I pass around a simple Hashtable object with a single result member to avoid the return craziness as I also want to output to the console. It acts through pass by reference.

function sample-loop($returnObj) {
  for($i = 0; $i -lt 10; $i++) {
    Write-Host "loop counter: $i"
    $returnObj.result++
  }
}

function main-sample() {
  $countObj = @{ result = 0 }
  sample-loop -returnObj $countObj
  Write-Host "_____________"
  Write-Host "Total = " ($countObj.result)
}

main-sample

You can see real example usage at my GitHub project unpackTunes.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
What Would Be Cool
  • 6,204
  • 5
  • 45
  • 42
8

You need to clear output before returning. Try using Out-Null. That's how powershell return works. It returns not the variable you wanted, but output of your whole function. So your example would be:

function Return-Url
{
   param([string] $url)

   . {
       $rs = $url.ToString();
       return
   } | Out-Null
   
   return $rs
}

$result = Return-Url -url "https://stackoverflow.com/questions/10286164/function-return-value-in-powershell"

Write-Host $result
Write-Host $result.GetType()

And result is:

https://stackoverflow.com/questions/10286164/function-return-value-in-powershell
System.String

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

3lvinaz
  • 113
  • 3
  • 12
5

It's hard to say without looking at at code. Make sure your function doesn't return more than one object and that you capture any results made from other calls. What do you get for:

@($returnURL).count

Anyway, two suggestions:

Cast the object to string:

...
return [string]$rs

Or just enclose it in double quotes, same as above but shorter to type:

...
return "$rs"
Shay Levy
  • 121,444
  • 32
  • 184
  • 206
  • 1
    Or even just `"$rs"` - the `return` is only required when returning early from the function. Leaving out the return is better PowerShell idiom. – David Clarke Jul 29 '13 at 22:19
5

Luke's description of the function results in these scenarios seems to be right on. I only wish to understand the root cause and the PowerShell product team would do something about the behavior. It seems to be quite common and has cost me too much debugging time.

To get around this issue I've been using global variables rather than returning and using the value from the function call.

Here's another question on the use of global variables: Setting a global PowerShell variable from a function where the global variable name is a variable passed to the function

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ted B.
  • 51
  • 1
  • 1
3

The following simply returns 4 as an answer. When you replace the add expressions for strings it returns the first string.

Function StartingMain {
  $a = 1 + 3
  $b = 2 + 5
  $c = 3 + 7
  Return $a
}

Function StartingEnd($b) {
  Write-Host $b
}

StartingEnd(StartingMain)

This can also be done for an array. The example below will return "Text 2"

Function StartingMain {
  $a = ,@("Text 1","Text 2","Text 3")
  Return $a
}

Function StartingEnd($b) {
  Write-Host $b[1]
}

StartingEnd(StartingMain)

Note that you have to call the function below the function itself. Otherwise, the first time it runs it will return an error that it doesn't know what "StartingMain" is.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Edwin
  • 31
  • 2