Powershell returning boolean fails - powershell

Writing a powershell script with a function that invokes an executable, and then attempts to return a bool. Two errors occur:
The output from the .exe doesn't show in the console window while running
the returned value is contains the output + the return value..
I'm doing something stupid, but what?
Script is
function HelloWorld () {
$cmd = "./HelloWorld.exe"
& $cmd
return $true}
$ret = HelloWorld
The $ret = "Hello World
True"

UPDATED:
Ok, I found a script online to create the helloworld.exe
Add-Type -outputtype consoleapplication -outputassembly helloworld.exe 'public class helloworld{public static void Main(){System.Console.WriteLine("hello world");}}'
Now We can run this:
function HelloWorld {
$cmd = ".\HelloWorld.exe"
& $cmd | Write-Host
return $true
}
$ret = HelloWorld
After run:
hello world
PS> $ret
True
PS> $ret | gm
TypeName: System.Boolean
Seems to work here at least.
HTH

The output is being captured by the assignment to $ret. Important to understand PowerShell functions return all output. when you ran & $cmd the output is returned, then you ran an explicit albeit unnecessary Return $true. Both pieces of data get returned and you see no screen output because it was consumed by the assignment.
In order to get the output of HelloWorld.exe to the console while only returning the Boolean from the function you can use either Write-Host or Out-Host. The difference being the latter traverses PowerShell's for-display formatting system, which may not be necessary in this case.
function HelloWorld ()
{
$cmd = "./HelloWorld.exe"
& $cmd | Write-Host
return $true
}
$return = HelloWorld
In this case the screen should show the output from HellowWorld.exe, but $return should only contain $true.
Note: Because of the aforementioned behavior Return isn't technically necessary. The only necessary use case for Return is to explicitly exit a function usually early.
Also note: this assumes HelloWorld.exe is a typical console application for which PowerShell will capture output into the success stream.

So in the function, there is a call to an executable which had a console.writeline statement (console.writelne("Hello world"))
calling the function works fine, but there no output that appears. Instead it got returned to the caller along with the return true/false. How can I get just the bool return without disrupting the output from the exe which we need to see on the console real time...

Related

Send result from called powershell script to a calling powershell script

I'm trying to send just the result I want from a called PowerShell script back to the calling script.
So the script testcaller.ps1
$Result = Invoke-Expression "$PSScriptRoot\testcalled"
$LogMessage = "TestCalled ended with Result $Result"
Write-Output $LogMessage
Calls the script testcalled.ps1
$TestMessage = "this is a test"
Write-Output $TestMessage
$Level = 1
exit $Level
When run it produces this...
TestCalled ended with Result this is a test
0
I have two problems. I get the testmessage passed back to my calling script and the level past back is 0 when it should be 1. What I want to see is...
TestCalled ended with Result 1
Given testcalled.ps1
'hello world'
function Say-Hello { 'hello!' }
exit 1
If you want to run this external script from another script you can either dot source it:
The functions, variables, aliases, and drives
that the script creates are created in the scope in which you are
working. After the script runs, you can use the created items and
access their values in your session.
$Result = . "$PSScriptRoot\testcalled.ps1"
$Result # => hello world
Say-Hello # => 'hello!'
$LASTEXITCODE # => 1
Or use the call operator &:
The call operator executes in a child scope.
Meaning that, functions, variables, aliases, and drives will not be available on the current scope.
$Result = & "$PSScriptRoot\testcalled.ps1"
$Result # => hello world
Say-Hello # => SayHello: The term 'SayHello' is not recognized as a name of a cmdlet, function....
$LASTEXITCODE # => 1
As you can see, on both cases, you can use the automatic variable $LASTEXITCODE.
Last, but not least, it is not recommended to use Invoke-Expression. See https://devblogs.microsoft.com/powershell/invoke-expression-considered-harmful/

How to handle errors for the commands to run in Start-Job?

I am writing an automation script. I had a function which takes either a command or an executable. I had to wait until the command or executable has completed running and return if failed or passed. I also want to write the output to file. I am trying with the Start-Job cmdlet.
My current code:
$job = Start-Job -scriptblock {
Param($incmd)
$ret = Invoke-Expression $incmd -ErrorVariable e
if ($e) {
throw $e
} else {
return $ret
}
} -ArumentList $outcmd
Wait-Job $job.id
"write the output to file using receive-job and return if passed or failed"
This works perfectly fine for commands but for executables irrespective of errorcode the value of $e is null. This falsely shows as passed even though the errorcode is 0.
I tried with errorcode using $LASTEXISTCODE and $?. But $? is true for executables and $LASTEXISTCODE is either null or garbage value for commands. I am out of ideas and struck here.
When in doubt, read the documentation:
$?
Contains the execution status of the last operation. It contains TRUE if the last operation succeeded and FALSE if it failed.
[…]
$LASTEXITCODE
Contains the exit code of the last Windows-based program that was run.
Basically, you need to check both. $? indicates whether the last PowerShell command/cmdlet was run successfully, whereas $LASTEXITCODE contains the exit code of the external program that was last executed.
if (-not $? -or $LASTEXITCODE -ne 0) {
throw '... whatever ...'
} else {
return $ret
}
However, Invoke-Expression is not a very good approach to executing commands. Depending on what you actually want to execute there are probably better ways to do it, with better methods for error handling.

Choose from multiple return values

When I have a function, which uses several Write-Output commands and returns single number, how can I get number value in function caller code?
As far as I got, line
[int] $var = Get-MyNumber(...)
gets me the error
Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.Int32"".
Probably because PowerShell returns an array of objects (containing Write-Output messages) back to caller code, where the assignment to the [int]-typed variable fails. Got that.
Now, how can I tell PowerShell that I'm interested in only a single value from those returned from the function, which is typed as [int].
BTW, I DON'T want to choose output by indexing the return array as I could mess up the indexes in the return array simply by adding another Write-Output line. (Which will happen sooner or later due to code maintenance).
Code
function f1() {
Write-Output "Lala"
return 5
}
[int] $x = f1
Write-Output $x
Results in the same error.
I see from your edit that you are using Write-Output to display a status message.
You should use Write-Host for that, or if you were using an advanced function, I would recommend using Write-Verbose and calling the function with -Verbose when you want to see the messages (see about_CommonParameters).
Updated code:
function f1() {
Write-Host "Lala"
return 5
}
[int] $x = f1
Write-Host $x
Advanced Function Example
function f1 {
[CmdletBinding()]
param()
Write-Verbose "Lala"
return 5
}
$x = f1
# The "Lala" message will not be seen.
$x = f1 -Verbose
# The "Lala" message will be seen.
Why Write-Output seems to work outside of a function:
Write-Output passes the input object to the caller. In the case of code that is executed directly in the host, and not in a function or cmdlet, the caller is the host, and the host decides what to do with it. In the case of powershell.exe (or ISE), it displays it.
Write-Host on the other hand, always writes to the host; it doesn't pass anything back to the caller.
Also note that Write-Output is, basically, optional. The following lines are equivalent:
Write-Output $x
$x

Capturing verbose output from -Verbose switch in PowerShell v2

I ran into an interesting issue today that has me puzzled. I need to capture output from the verbose stream and write it to stdout.
This can be accomplished with this:
# Create a PowerShell Command
$pscmd = [PowerShell]::Create()
# Add a Script to change the verbose preference.
# Since I want to make sure this change stays around after I run the command I set UseLocalScope to $false.
# Also note that since AddScript returns the PowerShell command, I can simply call Invoke on what came back.
# I set the return value to $null to suppress any output
$null = $psCmd.AddScript({$VerbosePreference = "Continue"},$false).Invoke()
# If I added more commands, I'd be adding them to the pipeline, so I want to clear the pipeline
$psCmd.Commands.Clear()
# Now that I've cleared the pipeline, I'll add another script that writes out 100 messages to the Verbose stream
$null = $psCmd.AddScript({Write-verbose "hello World" }).Invoke()
# Finally, I'll output the stream
$psCmd.Streams.Verbose
Now the interesting part is if I were to create a function called Hello-World and use [CmdletBinding()] to inherit the -verbose switch, I can no longer capture output:
Function Hello-World {
[CmdletBinding()]
Param()
Write-Verbose "hello world"
}
...
$null = $psCmd.AddScript({Hello-World -Verbose }).Invoke()
...
I am assuming that the function is given its own verbose stream and that visibility to the stream is lost, but I am not positive. Does this have to do with [CmdletBinding()]?
Avoiding transcripts, is there a way to accomplish this?
Thanks!
Thank you #JasonMorgan, below is the solution that appears to be working. I needed to declare the function in the pscmd I made:
$pscmd = [PowerShell]::Create()
$null = $psCmd.AddScript({$VerbosePreference = "Continue"},$false).Invoke()
$null = $psCmd.AddScript({
function Hello-World {
[CmdletBinding()]
Param()
Write-Verbose "hello world"
}
}, $false).Invoke()
$psCmd.Commands.Clear()
$null = $psCmd.AddScript({Hello-World -Verbose }).Invoke()
$psCmd.Streams.Verbose

Function return value in PowerShell

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?
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.
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.
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.
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]
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.
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.
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
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"
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
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.