Home > Software engineering >  Open pwsh with command from cmd without exiting
Open pwsh with command from cmd without exiting

Time:10-28

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!

CodePudding user response:

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"

CodePudding user response:

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}"

CodePudding user response:

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 (see links above) 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).
  • 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)"

  • Related