Note:
-
JamesQMurphy’s helpful answer is the best solution in this case.
-
This answer generally discusses translating command lines written for
cmd.exe
to PowerShell.
Translating cmd.exe
(batch-file) command lines to PowerShell is tricky – so tricky, that in PSv3 pseudo-parameter
--%
, the stop-parsing token, was introduced:
Its purpose is to allow you to pass everything that comes after --%
as-is through to the target program, so as the control the exact quoting – except that cmd.exe
-style %...%
-style environment-variable references are still expanded by PowerShell[1].
However, --%
comes with many severe limitations – discussed below – and its usefulness is limited to Windows, so it should be considered a last resort;
An alternative is to use the PSv3+ Native
module (install with Install-Module Native
from the PowerShell Gallery in PSv5+), which offers two commands that internally compensate for all of PowerShell’s argument-passing and cmd.exe
‘s argument-parsing quirks:
-
Function
ie
, which you prepend to any call to an external program, includingcmd.exe
, allows you to use only PowerShell‘s syntax_, without having to worry about argument-passing problems; e.g.:# Without `ie`, this command would malfunction. 'a"b' | ie findstr 'a"b'
-
Function
ins
(Invoke-NativeShell
) function allows you to reuse command lines written forcmd.exe
as-is, passed as a single string; unlike--%
, this also allows you to embed PowerShell variables and expressions in the command line, via a PowerShell expandable string ("..."
):# Simple, verbatim cmd.exe command line. ins 'ver & whoami' # Multi-line, via a here-string. ins @' dir /x ^ c:\ '@ # With up-front string interpolation to embed a PowerShell var. $var="c:\windows"; ins "dir /x `"$var`""
Limitations and pitfalls of --%
Note: Many of these limitations can be overcome if you combine --%
with splatting, which requires more work, however, and requires formulating the arguments as quoted strings – see this answer.
-
--%
must follow the name/path of the external utility to invoke (it can’t be the first token on the command line), so that the utility executable (path) itself, if passed by [environment] variable, must be defined and referenced using PowerShell syntax. -
--%
supports only one command, which an unquoted|
,||
or&&
on the same line, if present, implicitly ends; that allows you to pipe / chain such a command to / with other commands.-
However, using
;
in order to unconditionally place another command on the same line is not supported; the;
is passed through verbatim. -
--%
reads (at most) to the end of the line so spreading a command across multiple lines with line-continuation chars. is NOT supported.[2]
-
-
Other than
%...%
environment-variable references, you cannot embed any other dynamic elements in the command; that is, you cannot use regular PowerShell variable references or expressions.- Escaping
%
characters as%%
(the way you can do inside batch files) is not supported;%<name>%
tokens are invariably expanded, if<name>
refers to a defined environment variable (if not, the token is passed through as-is).
- Escaping
-
Other than
%...%
environment-variable references, you cannot embed any other dynamic elements in the command; that is, you cannot embed regular PowerShell variable references or expressions. -
You cannot use stream redirections (e.g.,
>file.txt
), because they are passed verbatim, as arguments to the target command.- For stdout output you can work around that by appending
| Set-Content file.txt
instead, but there is no direct PowerShell workaround for stderr output. - However, if you invoke your command via
cmd
, you can letcmd
handle the (stderr) redirection (e.g.,cmd --% /c nosuch 2>file.txt
)
- For stdout output you can work around that by appending
Applied to your case, this means:
%winscp%
must be translated to its PowerShell equivalent,$env:winscp
, and the latter must be prefixed with&
, PowerShell’s call operator, which is required when invoking external commands that are specified by variable or quoted string.& $env:winscp
must be followed by--%
to ensure that all remaining arguments are passed through unmodified (except for expansion of%...%
variable references).- The list of arguments from the original command can be pasted as-is after
--%
, but must be on a single line.
Therefore, the simplest approach in your case – albeit at the expense of having to use a single line – is:
# Invoke the command line with --%
# All arguments after --% are used as-is from the original command.
& $env:winscp --% /ini=nul /log=C:\TEMP\winscplog.txt /command "open scp://goofy:changeme@10.61.10.225/ -hostkey=""ssh-rsa 2048 d4:1c:1a:4c:c3:60:d5:05:12:02:d2:d8:d6:ae:6c:5d""" "put ""%outfile%"" /home/public/somedir/somesubdir/%basename%" "exit"
[1] Note that, despite the cmd.exe
-like syntax, --%
also works on Unix-like platforms in PowerShell Core (macOS, Linux), but is of very limited use there: unlike with native shells such as bash
there, --%
only works with double-quoted strings ("..."
); e.g., bash --% -c "hello world"
works, but bash --% -c 'hello world'
doesn’t – and the usual shell expansions, notably globbing, aren’t supported – see this GitHub issue.
[2] Even `
, PowerShell’s own line-continuation character, is treated as a pass-through literal. cmd.exe
isn’t even involved when you use --%
(unless you explicitly use cmd --% /c ...
), so its line-continuation character, ^
, cannot be used either.