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: 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:
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.
- 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
From outside PowerShell, use
pwsh -Command " ... "
(orpwsh -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 another shell means that its syntax requirements must be satisfied first, which can get tricky with
- 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.
- Calling from another shell, notably
- Outside can mean:
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).
- Behind the scenes, the code inside the script block is Base64-encoded and passed on the command line that is ultimately constructed via
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()
- 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
[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)"