Comma separate input parameters? - powershell

I have a script foo.cmd:
echo %1 %2
In PowerShell I run:
foo.cmd "a,b" "c"
Expected output: a,b c
Actual output: a b
Why?

The double quotes are removed after PowerShell parsed the command line and passes it to CMD for execution, so CMD actually sees a statement
foo.cmd a,b c
Since the comma is one of CMD's delimiter characters that statement is equivalent to
foo.cmd a b c
To avoid this behavior you need to ensure that the double quotes are preserved when passing the arguments to CMD. There are several ways to achieve this. For instance, you could put the double quoted arguments in single qoutes:
foo.cmd '"a,b"' "c"
and change the positional parameters in the batch script to %~1, %~2 so that CMD removes the double quotes from the arguments.
If you have PowerShell v3 or newer you can use the "magic parameter" --% to avoid the nested quotes:
foo.cmd --% "a,b" "c"
You still need %~1 and %~2 in the batch script, though.

Related

Avoid Line break at end of cmd output?

when I use this command pwsh -c echo hello in cmd I get the following output:
C:\>pwsh -c echo hello
hello
C:\>
I do not get that line break at the end
when I run it on powershell:
PS C:\> pwsh -c echo hello
hello
PS C:\>
So I think the problem is in cmd. I know this is not such a problem and have an easy fix but I have some programs uses cmd to access powershell and removing that line break is not that fun.
So is there any fix to prevent cmd to add that line ?
Mofi has provided the crucial pointers in comments:
When executing a command interactively, cmd.exe unconditionally appends a a newline (line break) to the command's output, presumably for readability and perhaps also to ensure that the next prompt always starts on a new line.
This applies irrespective of what that command is. In other words: It doesn't matter that your command happens to be a PowerShell command.
However, that trailing newline does not become part of the command's output, therefore programmatic processing of a command's output is not affected, such as when you redirect > to a file or process the output lines one by one with for /f.
In other words: for programmatic processing you need not remove the trailing newline, because it isn't part of the actual command output.
Conversely, if you really need to in effect suppress the trailing newline for display, you'll have to modify the command's output - if that is even an option - so that the output itself doesn't end in a newline, as shown in this SuperUser answer for cmd.exe's own echo command; for PowerShell, you could do pwsh -c Write-Host -NoNewLine hello.
Edge case:
When capturing output from a batch file that is running without #echo off (or with echo on) - in which case the trailing newlines do become part of the output - you can filter out empty lines by piping to findstr /r /v /c:"^$" (as also shown in the linked answer); e.g.
foo.cmd | findstr /r /v /c:"^$"
However, note that all empty lines are filtered out this way - potentially including actual empty lines in the output from commands executed by the batch file.
If preventing that is required, a more sophisticated approach is required, which, however (a) relies on the standard prompt string (e.g., C:\>) being used and (b) can still yield false positives:
foo.cmd | powershell -nop -c "#($Input) -join \"`n\" -replace '\n(?=[a-z]:\\.*?>)'"
Finally note that if you execute the above commands without capturing or redirecting their output, their overall output in the cmd.exe console will again have a trailing newline.

What is the difference between the escape backtick "`" and backslash "\" in PowerShell?

I know that both are used in PowerShell but for different contexts.
On the internet there is a paucity of information on this topic and the only site that talks about it (without making me understand the concept) is:
https://www.rlmueller.net/PowerShellEscape.htm
I am a beginner with PowerShell, I am recently approaching it.
A use case of the \ escape came up in the answer to this other topic of mine:
PowerShell removes multiple consecutive whitespaces when I pass arguments to a nested Start-Process command
Is there anyone who can explain to me in detail the difference between the escape backtick ` and backslash \ in PowerShell, with examples and use cases?
At least one source is welcome, but it is not mandatory.
vonPryz's helpful answer covers the PowerShell-internal angle well; let me attempt a systematic summary that includes the PowerShell CLI angle, as well as passing arguments with embedded " to external programs:
Inside a PowerShell session, the only escape character is ` (the so-called backtick), in the following contexts:
Inside an expandable string ("...", double quoted), but not inside a verbatim string ('...', single-quoted); for the supported escape sequences, see the conceptual about_Special_Characters help topic:
# " must be escaped; escape sequence `n expands to a newline.
"3`" of`nrain"
In unquoted command arguments:
# > must be escaped to write verbatim 'a>b',
# since outside a quoted string an unescaped > is a redirection.
Write-Output a`>b
For line-continuations:
# Spread the Get-Date call across two lines.
# Important: ` must be the *very last char* on the line.
Get-Date `
-Year 2525
Note: Various subsystems, whether PowerShell-specific or not, may have their own escaping rules, such as \ in regular expressions and ` in wildcard expressions. Since arguments to those subsystems are delivered via PowerShell strings, it's best to use verbatim string literals, so as to avoid confusion between PowerShell's own string interpolation and what the target subsystem ends up seeing; e.g. 'A $50 fine.' -match '\$50' (\ is needed to treat regex metacharacter $ literally).
When PowerShell is called from the outside, via its CLI, different rules apply, possibly in addition:
In order to adhere to widely used convention for CLIs (command-line interfaces, programs that accept arguments via a command line) on Windows:
In calls to powershell.exe, the Windows PowerShell CLI, " characters must be escaped with a backslash - i.e. as \" - in order to be preserved during parsing of the original command line.
pwsh.exe, the CLI of the cross-platform, install-on-demand PowerShell (Core) 7+ edition, now commendably alternatively accepts ""[1] in lieu of \", which makes calls from cmd.exe more robust. To get the same robustness in Windows PowerShell - from cmd.exe only - use "^"" (sic).
Note that - unlike \" - these escape sequences only work inside an (unescaped) "..." string (e.g., pwsh.exe -c " ""you & I"" " or powershell.exe -c " "^""you & I"^"" "
By contrast, unescaped " have syntactic function on the command line and tell PowerShell where the boundaries between arguments are; these " instances are removed during command-line parsing.
This ensures that outside callers that merely want to invoke a PowerShell script file (.ps1) with arguments, using the -File parameter, can use the conventional syntax and needn't special-case calls to PowerShell's CLI.
However, if you pass a string containing PowerShell code to the CLI, using the -Command parameter, what PowerShell ends up interpreting obviously must be syntactically valid PowerShell code.
Caveat: If you specify neither -Command nor -File:
powershell.exe defaults to -Command
pwsh.exe now defaults to -File
For the differences between -File and -Command calls and when to use which, see this answer.
If you use -Command, there are two, sequential parsing stages:
The command-line parsing stage, where syntactic (unescaped) " are removed, and escaped \" (or "") turn into literal ".
The result of this stage is then parsed as PowerShell code, as it would be from inside a PowerShell session.
Therefore, you may situationally have to combine \ and `-escaping; e.g. (call from cmd.exe):
C:>powershell.exe -Command " \"3`\" of snow as of $(Get-Date)\" "
3" of snow as of 11/04/2021 14:13:41
Note the use of `\" in order to make PowerShell see `", i.e. a properly escaped " inside a "..." string, after command-line parsing.
Alternatively, depending on the specifics of the command(s) you pass to -Command, using embedded '...' quoting may be an option, which simplifies matters, because ' chars. don't require escaping:
C:>powershell.exe -Command " '3\" of snow as of today.' "
3" of snow as of today.
Given that '...' strings in PowerShell are verbatim strings, use of '...' is only an option if you don't require string interpolation (such as the $(Get-Date) subexpression in the prior example).
Escaping " when calling external programs from PowerShell:
As a shell, it is PowerShell's job to pass the arguments that were passed based on its syntax rules to a target executable, so that the verbatim values that result from PowerShell's parsing are passed in a way that makes the target executable see them as such. In other words: PowerShell should perform any required escaping automatically, behind the scenes. (Unlike cmd.exe, PowerShell cannot just pass its own argument syntax through as-is, because external CLIs cannot be expected to understand '...' strings (single-quoting) or `-escaping).
To use a simply example: Passing '3" of snow' should be passed as "3\" of snow" behind the scenes, based on the most widely used escaping convention.
Sadly, up to PowerShell 7.2.x, this is not the case, and embedded " characters in arguments for external programs must additionally, manually be \-escaped in order to be passed through properly.
This has been fixed in PowerShell 7.3, with selective exceptions on Windows - see the new $PSNativeCommandArgumentPassing preference variable
# Broken behavior up to PS v7.2.x
PS> cmd /c echo '{ "foo": "bar" }'
"{ "foo": "bar" }" # !! Embedded " aren't escaped.
PS> choice.exe /d Y /t 0 /m '{ "foo": "bar" }'
{ foo: bar } [Y,N]?Y # !! When parsed by an executable,
# !! embedded " are effectively LOST.
# Manual escaping required.
PS> choice.exe /d Y /t 0 /m '{ \"foo\": \"bar\" }'
{ "foo": "bar" } [Y,N]?Y # OK
This bug has existed since v1, and has never been fixed so as to avoid breaking existing workarounds.
[1] Inside PowerShell, in "..." strings only, you may also use "" to escape an embedded ", as an alternative to `"
The backtick ` is used as the escape character in Powershell. That is, escaping quotes, tabs and whatnot. Unlike in many other environments, Powershell's newline is `n, not \n. This has the benefit of simplifying paths, since those use backslash in Windows.
As for a practical example, in many programming languages one needs to escape the backslashes in paths. If you have an application located at
c:\program files\newApplication\myApp.exe
Its path must be written as
c:\\program files\\newApplication\\myApp.exe
The double-backslash notation means that there is actaully a backslash, not a metacharacter such as tab or newline. Note that the path contains \newApplication. Now, \n usually means a newline, but it clearly isn't the case. The file does not reside in
c:\program files\
ewApplication\myApp.exe
after all, doesn't it?
The escaping is not needed in Powershell, as the backslash doesn't have special meaning by itself. When Powershell sees c:\program files\newApplication\myApp.exe, it doesn't assign any special meaning to the forementioned \n part, it is just a string literal.
The backslash \ is used as the escape character in regular expressions and to signify metacharacters. That is, to match a literal * in regex, it must be escaped, lest it means the Kleene star (zero or more matches).

Passing a semicolon as a parameter to a .bat file from both cmd and PowerShell

This question seems to be rather simple, but even after searching the web for a couple of hours, I was not able to find a solution...
I have a batch file test.bat
set MY_VARIABLE=%~1
echo %MY_VARIABLE%
The point is that I want to call this programm with a semicolon as input parameter, i.e.,
.\test.bat ";",
from both cmd and Windows PowerShell. While this works fine from cmd, PowerShell does not seem to get anything as an input. Is there any way to make this work for both simultaneously?
This is because of command line syntax. The semicolon is one of multiple delimiters, that split the command line into words: a;b would be interpreted as two separate arguments a (%1) and b (%2).
Therefore, quotes are required. Since Powershell uses quotes for string literals (Powershell does its own re-quoting behind the scenes when passing arguments), you need to include them in the string:
.\test.bat '";"'
# or
.\test.bat "`";`""
Or as #mklement0 pointed out, the stop-parsing symbol --% would also be an option:
.\test.bat --% ";"
Note that this is specific to Powershell syntax.
In CMD, this will suffice:
test.bat ";"

Is there a way to escape quotes in ripgrep for MS Windows (Powershell or CMD)?

I want to find a string "Hello (Hello starts with double quote) in text files using ripgrep.
Normally, in Bash or ZSH, this would work by escaping with backslash or surrounding with single quote:
rg \"Hello
rg '"Hello'
However, in MS Windows (Powershell and CMD), I've tried these but none of these worked:
rg \"Hello
rg '"Hello'
rg `"Hello
rg '`"Hello'
Is there any way to escape single or double quotes using ripgrep in MS Windows?
Verbatim string "Hello must ultimately be passed as \"Hello to rg ("\"Hello" would work too). That is, the verbatim " char. must be \-escaped:
From cmd.exe:
rg \^"Hello
^, cmd.exe's escape character, ensures that the " is treated verbatim and is removed by cmd.exe before calling rg.
Note that ^ isn't strictly necessary here, but it prevents the " from being considered the start of a double-quoted argument, which could make a difference if there were additional arguments.
From PowerShell:
rg \`"Hello
`, PowerShell's escape character, ensures that the " is treated verbatim and is removed by PowerShell before calling rg.
Arguably, the explicit \-escaping shouldn't be necessary, because it is the duty of a shell to properly pass arguments to target executables after the user has satisfied the shell's own escaping requirements (escaping the verbatim " with ^ in cmd.exe, and with ` in PowerShell).
In the context of PowerShell, this problematic behavior is summarized in this answer.
Note that in PowerShell this extra escaping is only needed if you call external programs; it isn't needed PowerShell-internally - such as when you call Select-String, as shown in js2010's answer.
You can use rg -F \"Hello
-F, --fixed-strings
Treat the pattern as a literal string instead of a regular expression. When this flag is used, special regular expression meta characters such as .(){}*+ do not
need to be escaped.
This flag can be disabled with --no-fixed-strings.
If you're in powershell you might as well do:
get-childitem file | select-string '"hello'
file:1:"hello

tcl exec to open a program with agruments

I want to open a text file in notepad++ in a particular line number. If I do this in cmdline the command should be:
start notepad++ "F:\Path\test.txt" -n100
And it is working fine from command line. Now I have to do this from tcl. But I can't make this command work with exec. When I try to execute this:
exec "start notepad++ \"F:\Path\test.txt\" -n100"
I am getting this error:
couldn't execute "start notepad++ "F:\Path\test.txt" -n100": no such file or directory.
What am I missing. Please guide.
Similar to this question:
exec {*}[auto_execok start] notepad++ F:/Path/test.txt -n10
First, you need to supply each argument of the command as separate values, instead of a single string/list. Next, to mimic the start command, you would need to use {*}[auto_execok start].
I also used forward slashes instead of backslashes, since you would get a first level substitution and get F:Path est.txt.
EDIT: It escaped me that you could keep the backslashes if you used braces to prevent substitution:
exec {*}[auto_execok start] notepad++ {F:\Path\test.txt} -n10
You can simply surround the entire exec statement in curly braces. Like this:
catch {exec start notepad++.exe f:\Path\test.txt -n10}
I haven't found a perfect solution to this yet. All my execs seem to be different from each other. On windows there are various issues.
Preserving double quotes around filename (or other) arguments.
e.g. in tasklist /fi "pid eq 2060" /nh the quotes are required.
Preserving spaces in filename arguments.
Preserving backslash characters in filename arguments.
[Internally, Windows doesn't care whether pathnames have / or \, but some programs will parse the filename arguments and expect the backslash character].
The following will handle the backslashes and preserve spaces, but will not handle double-quoted arguments. This method is easy to use. You can build up the command line using list and lappend.
set cmd [list notepad]
set fn "C:\\test 1.txt"
lappend cmd $fn
exec {*}$cmd
Using a string variable rather than a list allows preservation of quoted arguments:
set cmd [auto_execok start]
append cmd " notepad"
append cmd " \"C:\\test 1.txt\""
exec {*}$cmd
Note that if you need to supply the full path to the command to be executed, it often needs to be quoted also due to spaces in the pathname:
set cmd "\"C:\\Program Files\\mystuff\\my stuff.exe\" "