1

I am trying to start a Powershell window that starts an ssh session with the following command:

pwsh.exe -noexit -Command {ssh <username>@<host>}

This works when executed from a pwsh.exe window itself: enter image description here but when executed from cmd, the run window (Win+R) or Task Scheduler (what I need it for), it just displays the command that should have been executed and starts pwsh: enter image description here

That is, the command that doesn't work outside of PowerShell is:

pwsh.exe -noexit -Command {ssh username@host}

The commands have of course been tested with actual websites where, again, 1 works and 2 doesn't.
Any help would be greatly appreciated! Thank you!

mklement0
  • 382,024
  • 64
  • 607
  • 775
MySurmise
  • 146
  • 11

3 Answers3

2

Note:

  • The answer below applies to the CLIs of both PowerShell editions, (powershell.exe for Windows PowerShell, pwsh for PowerShell (Core) 7+)

  • powershell -Command "& { ... }" / pwsh -Command "& { ... }" is an anti-pattern, and therefore to be avoided:

    • Use "...", not "& { ... }", i.e. specify all commands directly,[1] which avoids unnecessary syntactic ceremony and unnecessary processing (though the latter won't matter in practice).

    • This anti-pattern, which is unfortunately very common, presumably arose because older versions of the CLI documentation erroneously suggested that & { ... } is required, but this has since been corrected.


tl;dr

  • From inside PowerShell, use pwsh { ... } (-Command implied); in your case:

    pwsh.exe -noexit { ssh username@host }
    
    • However, note that it's rarely necessary to call the PowerShell CLI from inside PowerShell (which involves creating a child process). Here you could simply call ssh username@host directly.
  • From outside PowerShell, use pwsh -Command " ... " (or pwsh -c " ..." ); in your case (as in your own answer):

    pwsh.exe -noexit "ssh username@host"
    
    • Outside can mean:

      • Calling from another shell, notably cmd.exe
        • Calling from another shell means that its syntax requirements must be satisfied first, which can get tricky with " chars. that are escaped as \" - see this answer for robust workarounds.
      • Calling from a no-shell context such as the Windows Run dialog (WinKey+R) and commands launched by Task Scheduler.
      • All (native) outside contexts on Windows require double-quoting ("..."), but if you're calling from a Unix (POSIX-compatible) shell such as Bash, single-quoting is also an option.

For a comprehensive overview of the PowerShell CLI, see this post.


Background information:

{ ... } is the literal form of a PowerShell script block, loosely speaking a reusable piece of code that must be invoked on demand, either with & (in a child scope) or with . (directly in the caller's scope):

  • Only inside PowerShell can it be used with the PowerShell CLI to pass commands to execute, because PowerShell there offers { ... } as syntactic sugar:

    • Behind the scenes, the code inside the script block is Base64-encoded and passed on the command line that is ultimately constructed via -EncodedCommand; similarly, any arguments passed to -Args are Base64-encoded and passed via -EncodedArguments, and CLIXML (PowerShell's XML-based inter-process serialization format) is requested as the output format via -OutputFormat XML
    • This, in conjunction with automatic deserialization of the output received, ensures that the benefits of PowerShell's rich (.NET-based) type system and multiple output streams are preserves as much as possible (type fidelity has inherent limitations when serialization is involved - see this answer).
  • When the PowerShell CLI is called from the outside (or even via string from inside PowerShell), the syntactic sugar does not apply, and a script-block literal (invariably provided via a string argument) is parsed as just that: a piece of code to be called later.

    • Thus, if you try something like the following:

        # !! WRONG - simply *prints* what's between { and }
        # (Also applies if you don't use "..." from outside PowerShell.)
        pwsh -c "{ Get-Date }"
      
      • PowerShell constructs a script block, and, since it is not assigned to a variable, outputs it by default, which means a string representation of the (otherwise unused) script block prints, which is the verbatim content of the script block, sans { and }.
      • That is, verbatim  Get-Date  is printed, which you can also verify as follows from inside PowerShell: { Get-Date }.ToString()

[1] Technically, you could use "& { ... } ..." if you wanted to pass arguments from inside your command string to the embedded script block, though that level of encapsulation will rarely be necessary in practice; a contrived example (call from outside PowerShell):
pwsh -c "& { 'Time is now: ' + $args[0].TimeOfDay } (Get-Date)"

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

The problem was that I used curly braces that apparently don't work in cmd, but do work in pwsh. To make the command work in CMD, just use double quotes:
pwsh.exe -noexit -Command "ssh username@host"

MySurmise
  • 146
  • 11
0

See the -Command parameter in the pwsh documentation. cmd.exe doesn't know the ScriptBlock concept and interprets {ssh test@test} as a string.

When triggering pwsh.exe from cmd.exe, you should do something like this:

pwsh.exe -noexit -Command "& {ssh test@test}"
  • Thank you! I already figured it out (see answer below), but what is the "&" for here? Do we really need it here? – MySurmise Oct 27 '22 at 14:24
  • 1
    @MySurmise: No, there's no reason to use `"& { ... }"` in order to invoke code passed to PowerShell's CLI via the `-Command` (`-c`) parameter - just use `"..."` directly, as you did in your own answer. Older versions of the [CLI documentation](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pwsh) erroneously suggested that `& { ... }` is required, but this has since been corrected. – mklement0 Oct 27 '22 at 14:27
  • Also, `pwsh -Command` can be called with a script block as well (from inside PowerShell, where using a script block _implies_ `-Command`), so it isn't about whether `-Command` is used, it's about whether a true script block is passed (only possibly from inside PowerShell) or whether a _string_ representing a script block. When a _string_ is passed, a script block embedded in it _is_ parsed as such, but isn't _executed_. In other words: it is _not_ interpreted as a string; it does become a script block, but, in the absence of it being invoked or saved, is _output_, as its _verbatim content_. – mklement0 Oct 27 '22 at 21:02