Search code examples
powershellsshopenssh

How to get return code of program executed on remote server from powershell?


Very important, this is powershell specific.

There are many answers to variations of this question whose answer is to use single quotes instead of double quotes

e.g. ssh me@server "invalid_program; echo $?" -> ssh me@server 'invalid_program; echo $?'

This is not possible on windows in powershell with openssh. https://stackoverflow.com/a/75740191/8652920

Given there is a restriction that I have to ssh with double quotes only, is there any way to still force the environment variable expansion to happen on remote?

You can try this on your powershell.

PS C:\...> ssh me@remote "beep; echo $?"
Warning: Permanently added 'remote' (ECDSA) to the list of known hosts.
bash: beep: command not found
True

The stdout should be 127, not True.

% ssh me@remote 'beep; echo $?'
Warning: Permanently added 'remote' (ED25519) to the list of known hosts.
127
bash: beep: command not found

Solution

  • Preface:

    This [using single quotes] is not possible on windows in powershell with openssh.

    • In PowerShell (as opposed to in cmd.exe / batch files / no-shell invocations) using '...' when invoking external programs such as ssh does work even on Windows, because PowerShell translates such arguments into "..."-enclosed (double-quoted) arguments behind the scenes, assuming they contain spaces.[1]

    • Indeed, using '...' (a single-quoted string) is the simplest solution in your case - see the next section.


    $ is a metacharacter in both PowerShell and POSIX-compatible shells such as bash, and in both shells "..." is an expandable i.e. interpolating string.

    Therefore, in ssh me@remote "beep; echo $?", it is PowerShell that expands $?, up front,[2] using the then-current value of its definition of $?, which is a Boolean value.

    To prevent unwanted up-front interpolation:

    • Either: Use ssh me@remote "beep; echo `$?", i.e. escape the embedded $ using PowerShell's escape character, ` (the so-called backtick)

    • Or: Use a verbatim string ('...'), to which no interpolation is applied by design:
      ssh me@remote 'beep; echo $?'


    [1] That is, PowerShell passes arguments that do not contain spaces unquoted on the process command line constructed behind the scenes. Unfortunately, this can break invocation of batch files (*.cmd, *.bat), because cmd.exe, the batch-file interpreter - inappropriately - parses its command line as if it had been passed from inside a cmd.exe session. Thus, an invocation from PowerShell such as .\foo.cmd 'a&b' breaks and requires awkward workarounds. GitHub issue #15143 is a proposal to prevent such problems, but, regrettably, it died from neglect. Note that in Unix-like environments this point is moot, because there are no process command lines - argument are invariably passed as an array of verbatim strings.

    [2] It follows from the above that if you were to run this command from a POSIX-compatible shell (i.e. from a Unix environment), undesired up-front interpolation would occur too, except that a number would be interpolated, namely the most recently executed command's exit code, which is what $? reflects in those shells.