21

I am using "invoke-command" to execute a script on a remote machine.

invoke-command -computername <server_name> -scriptblock {command to execute the script}

My script returns "-1" when there are any errors. So, I wanted to ensure that script has executed successfully by checking the return code.

I tried as follows.

$code = invoke-command -computername ....

But it returns nothing.

Doesn't Invoke-command catch return code of script block?

Is there other way to resolve this?

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249

8 Answers8

13

I think if you run a command that way on another server there is no way you can get at the return code of your script there. This is because Invoke-Command simply runs one command on the remote machine, probably within a single temporary session and you can't connect to that session again.

What you can do, however, is create a session on the remote computer and invoke your script within that session. Afterwards you can just check for the return value in that session again. So something along the lines of:

$s = New-PSSession -ComputerName <server_name>
Invoke-Command -Session $s -ScriptBlock { ... }
Invoke-Command -Session $s -ScriptBlock { $? }

might work. This way you get access to the same state and variables as the first Invoke-Command on the remote machine.

Also Invoke-Command is very unlikely to pass through the remote command's return value. How would you figure out then that Invoke-Command itself failed?

ETA: Ok, I misread you with regard to "return code". I was assuming you meant $?. Anyway, according to the documentation you can run a script on a remote computer as follows:

To run a local script on remote computers, use the FilePath parameter of Invoke-Command.

For example, the following command runs the Sample.ps1 script on the S1 and S2 computers:

invoke-command -computername S1, S2 -filepath C:\Test\Sample.ps1

The results of the script are returned to the local computer. You do not need to copy any files.

Joey
  • 344,408
  • 85
  • 689
  • 683
  • odd thing in Invoke-command when running within the Jenkins context, the return code like this $returnCode = Invoke-command -file somescript.ps1 would return every from somescript.ps1. if ran within a command windows, the return value is returned properly. anyone has any idea? – koo9 Apr 17 '17 at 21:46
  • @koo9: `$returnCode` will receive _pipeline output_ of the script file. Not an exit code. – Joey Apr 18 '17 at 05:57
  • supposedly but when running the script within the Jenkins job, somehow $returncode catch all output from the ps1 script – koo9 Apr 18 '17 at 13:35
3

If a remote scriptblock returns an exit code, the psession will be closed. I am just checking the state of the psession. If it's closed I assume an error. Kind of hacky, but it works for my purposes.

$session = new-pssession $env:COMPUTERNAME
Invoke-Command -ScriptBlock {try { ErrorHere } Catch [system.exception] {exit 1}} -Session $session

if ($session.State -eq "Closed")
{
    "Remote Script Errored out"
}

Remove-PSSession $session

$session = new-pssession $env:COMPUTERNAME
Invoke-Command -ScriptBlock {"no exitcodes here"} -Session $session

if ($session.State -ne "Closed")
{
    "Remote Script ran succesfully"
}

Remove-PSSession $session
Cirem
  • 840
  • 1
  • 11
  • 15
3

Here's an example...

Remote script:

try {
    ... go do stuff ...
} catch {
    return 1
    exit
}
return 2
exit

Local script:

function RunRemote {
    param ([string]$remoteIp, [string]$localScriptName)
    $res = Invoke-Command -computername $remoteIp -UseSSL -Credential $mycreds -SessionOption $so -FilePath $localScriptName
    return $res
}

$status = RunRemote $ip_name ".\Scripts\myRemoteScript.ps1"
echo "Return status was $status"

$so, -UseSSL and $mycreds aren't needed if you're fully inside a trust group. This seems to work for me... good luck!

Iain Ballard
  • 4,433
  • 34
  • 39
3

If you need the output from the script block and the exit code, you can throw the exit code from the block and convert if back into an integer on the calling side...

$session = new-pssession $env:COMPUTERNAME
try {
    Invoke-Command -ScriptBlock {Write-Host "This is output that should not be lost"; $exitCode = 99; throw $exitCode} -Session $session
} catch {
    $exceptionExit = echo $_.tostring()
    [int]$exceptionExit = [convert]::ToInt32($exceptionExit)
    Write-Host "`$exceptionExit = $exceptionExit"
}

This returns the standard out and the last exit code. This does require however that within your script block, any exceptions are caught and re-thrown as exit codes. Output from example ...

This is output that should not be lost
$exceptionExit = 99
Jules Clements
  • 418
  • 5
  • 8
  • 1
    Nicely done. Use `throw $LASTEXITCODE` to report the exit code of the most recently executed script / external program. Quibbles: I suggest not using `Write-Host` to demonstrate _data_ output. `echo` is an alias of `Write-Output`, which _is_ the right tool for emitting data, i.e. writing to the _success output stream_ (PowerShell's analog to stdout), but its explicit use is rarely needed. Therefore: `$exceptionExit = echo $_.tostring()` -> `$exceptionExit = $_.tostring()`. No need for `[convert]::ToInt32()`, an `[int]` cast / type constraint will do: `[int] $exceptionExit = $_.ToString()` – mklement0 Apr 19 '23 at 11:00
1

The last Powershell Invoke-Command below returns boolean True if the error code is zero from the script executed and boolean False if the return code is non-zero. Works great for detecting error codes returned from remote executions.

function executeScriptBlock($scriptString) {

    Write-Host "executeScriptBlock($scriptString) - ENTRY"

    $scriptBlock = [scriptblock]::Create($scriptString + " `n " + "return `$LASTEXITCODE")

    Invoke-Command -Session $session -ScriptBlock $scriptBlock

    $rtnCode = Invoke-Command -Session $session -ScriptBlock { $? }

    if (!$rtnCode) {
        Write-Host "executeScriptBlock - FAILED"
    }
    Else {
        Write-Host "executeScriptBlock - SUCCESSFUL"
    }
}
Freddie
  • 908
  • 1
  • 12
  • 24
1

The chosen answer from Joey is correct, but I think the OP was asking for the exit code. Here's what works for me, PS5 and PS7. The test command robocopy /help has exit code 16.

$remoteCommand = "robocopy /help" # has exit code 16
$scriptBlock = [Scriptblock]::Create($remoteCommand)
$session = New-PSSession -Computername $computerName -Credential $cred
Invoke-Command -Session $session -ScriptBlock $scriptBlock
Invoke-Command -Session $session -ScriptBlock { $LASTEXITCODE } -OutVariable exitCodeArray > $null
$exitCode = $exitCodeArray[0] # Be careful!
"Exit code: $exitCode"
exit $exitCode
Paul Williams
  • 3,099
  • 38
  • 34
  • Nicely done. Note that it's simpler to simply create a script-block _literal_ (`{ robocopy /help }`) than to create one from a string with `[scriptblock]::Create()`. Unless you want to both capture _and_ print the value, `$exitCodeArray = Invoke-Command -Session $session -ScriptBlock { $LASTEXITCODE }` is a simpler way to capture the output, which also avoids the pitfall of `-OutVariable` _always_ returning the captured output as an array (list). (This surprising behavior is discussed in [GitHub issue #3154](https://github.com/PowerShell/PowerShell/issues/3154).) – mklement0 Apr 19 '23 at 13:08
1

Some background information, an overview of the existing, helpful answers, plus an additional solution:

The automatic $LASTEXITCODE variable, which reflects the exit code reported by the most recently executed external program or *.ps1 script (assuming it uses exit), is only available in a given session, and therefore not directly available to the caller in the context of executing code with Invoke-Command in a remote session.

  • As an aside: The same problem applies to background jobs, which also don't offer a direct way to query the background session's $LASTEXITCODE from the outside.
    GitHub issue #5422 suggests adding a .LastExitProperty to job objects to address this limitation, but such a solution wouldn't work directly with Invoke-Command, which only returns command output by default; it does, however, have an -AsJob switch that returns a job object too, so Invoke-Command could indirectly benefit from this improvement (requiring a change in invocation logic and output collection, however).

It is possible to make a remote session output its $LASTEXITCODE value as data, but that "pollutes" the stream of output objects from that session.

The existing answers work around that as follows:

  • Jules Clements' helpful answer offers a pragmatic, single-call solution that uses a throw statement to convey the exit code via an error that must be caught.

  • Paul Williams' helpful answer, which builds on Joey's, offers a workaround based on explicitly creating a remote session and then making two Invoke-Command calls in that session: one to retrieve the command output, and the second to query the $LASTEXITCODE value.

Another single-call option, which deals with the "pollution" problem differently:

  • Output the remote session's $LASTEXITCODE value at the very end, as an additional output object following the actual command output.

  • On the caller side, split the collected output into the last object and all objects before it, which gives you the exit code as well as the "unpolluted" command output.

# Invoke a remote command and capture all its output.
# (This example uses "loopback" remoting, targeting the local machine (".")
#  The local machine must be set up for remoting AND you must run from an
#  ELEVATED session.)
$output = Invoke-Command -ComputerName . { 

  # Sample commands that produce output and 
  # also cause $LASTEXITCODE to be set.
  Get-Date 
  Get-Item / 
  cmd /c 'exit 5' # Sets $LASTEXITCODE to 5
  
  # Finally, also output the $LASTEXITCODE value.
  $LASTEXITCODE 
}

# Now split the collection of output objects into the actual command
# output and the added $LASTEXITCODE value.
# Afterwards $output contains the actual output,
# and $exitCode the exit code.
$output, $exitCode = $output[0..($output.Count-2)], $output[-1]

Note the relatively cumbersome collection-splitting code - $output, $exitCode = $output[0..($output.Count-2)], $output[-1], because, as of PowerShell 7.3.4, there's no array-slicing support for all-but-the-last-N logic.

  • GitHub issue #7940 proposes adding such support, which would simply the expression to something like:

    $output, $exitCode = $output[0..@-2], $output[-1]
    
mklement0
  • 382,024
  • 64
  • 607
  • 775
0

I was looking to catch the error code return by a batch script called through invoke-command. Not an easy task but I found a nice and easy way to do it.

You PowerShell script is $res=invoke-command -Computername %computer% {C:\TEST\BATCH.CMD} write-host $res You'll find out the $res is the batch output, knowing this by using @echo off, >nul and echo "what you want to return" is possible!

You batch script

@echo off
echo "Start Script, do not return" > nul
echo 2

Now $res will be = 2! Then you can apply any logic in your batch.

You can even use invoke-command -AsJob Use Receive-job to get the output! In this case you don't need to use $res.

Cheers, Julien

Julien
  • 1