Launching powershell script from cmd without using double quotes (") - windows

I need to launch a powershell command from cmd without using double quotes "
My current command:
powershell.exe -noexit Start-BitsTransfer -Source 'download link' -Destination 'C:\test.txt'
The error:
The string is missing the terminator: '.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : TerminatorExpectedAtEndOfString
The reason I cannot use double quotes is because I'm using the post-install-command= parameter from vboxmanage which already uses double quotes.

It's best to enclose everything starting with Start-BitsTransfer in (escaped) "...", i.e., to pass a single, double-quoted string to the (positionally implied) -Command parameter of powershell.exe), which prevents cmd.exe from interpreting the string's content (except, potentially, cmd.exe-format environment-variable references such as %OS%).
Without "...", as in your case, a & outside a "..." string is then interpreted as a cmd.exe metacharacter (statement separator), and the call breaks ('...' strings are only recognized by PowerShell, not by cmd.exe).
While you could ^-escape all cmd.exe metacharacters individually, enclosing the entire -Command argument in "..." is the simpler solution. (Also, even though it is rarely a problem in practice, whitespace normalization occurs in the absence of "..." enclosure, i.e. runs of multiple spaces are folded into one space per run).
Since your entire powershell.exe command is itself embedded in a "..." string passed as part of an option to vboxmanage, you must escape the " chars. that are part of the powershell.exe call, namely as \".
Therefore, in the context of your vboxmanage call, use something like the following - note the outer "..." and embedded inner \"...\":
vboxmanage ... post-install-command="powershell.exe -noexit \"Start-BitsTransfer -Source 'download link' -Destination 'C:\test.txt'\""

Related

How to include double quote in Matlab's system() command

I need to pass filename as input to a powershell command which includes spaces. Hence I am hoping to include double quotes.
However, Matlab "eats" all double quotes in the input of system() when passing arguments to powershell.
For example, note how the examples below all the the same output.
>> system('powershell.exe echo a c','-echo')
a
c
ans =
0
>> system('powershell.exe echo "a c"','-echo')
a
c
ans =
0
>> system('powershell.exe echo ""a c""','-echo')
a
c
ans =
0
>> system(['powershell.exe echo ',char(34),'a c',char(34)],'-echo')
a
c
ans =
0
The actual output for echo "a c" in powershell is a c in a single line. The change of line only occurs without double quotes.
Just for experiment, I also tried ""a c"" and the expected input is the same as change line, a, change line, c. With the return, it seems that any and all double quotes are "eaten" alive by Matlab.
How do I bring the double quotes back when using system()?
It is PowerShell that is eating your double quotes:
You're passing a command (piece of PowerShell code) to the PowerShell CLI, via the -Command (-c) parameter (which is positionally implied in your case).
" characters that should be considered part of the command must be escaped as \" (sic)
The reason that unescaped " don't work is that PowerShell considers them to have syntactic function on the command line only - they are simply stripped after all arguments have been parsed; the resulting tokens are then joined with spaces, and the resulting string finally interpreted as PowerShell code.
While using just \" in your command would fix the problem, it is advisable to also enclose the entire command being passed in "...", because that prevents potentially unwanted whitespace normalization.
system('powershell.exe " echo \"a c\" "', '-echo')
Caveat:
Since MatLab's system() function executes the given command line via cmd.exe (which is inefficient in your case, since you don't need shell functionality), use of \" can break the invocation, due to how cmd.exe's parses command lines.
To avoid edge cases when cmd.exe is involved, enclose the overall command in "...." and escape pass-through " as follows:
Use "^"" (sic) when calling powershell.exe (the Windows PowerShell CLI)
Use "" when calling pwsh.exe (the PowerShell (Core) 7+ CLI).
See this answer for more information.

Pass argument from Powershell to Batch script

I need to pass a password with special characters from powershell script automation.ps1 to batch script batch_script.bat which pipes it to main.py. Piping from batch_script.bat to main.py works fine, that is authentication succeeds. However, when I run the entire procedure described above, authentication fails, but echoing the password shows the correct password string.
My guess is that there are issues with special characters. What is a safe way to pass these strings?
Background
I want to automate the daily download from some external source via a Python script main.py. This process requires a password. So I wrote a batch_script.bat which pipes the password to the Python script when prompted for it. However, I don't want to store the password as plain text in the batch script, so I encrypted the password and wrote another layer automation.ps1 which decrypts the password and passes it as plain text to batch_script.bat.
automation.ps1
# get password
$securePassword = Get-Content "<file_path>" | ConvertTo-SecureString
$credentials = New-Object System.Management.Automation.PsCredential("<user_name>",$securePassword)
$unsecurePassword = ($credentials).GetNetworkCredential().Password
# call script
$script = "<path>\batch_script.bat"
$args = #("`"<user name>`"","`"$unsecurePassword`"")
start-process $script $args
batch_script.bat
(I am aware that in this example I discard the passed username, just wanted to preserve the fact that I pass multiple arguments in case there is any relevance to it)
#echo off
SET username=%~1
SET password=%~2
echo(%password%|python main.py
With following, all special characters should be handled very well. If any character required to be escaped, check this
$pass could be any string but check for special characters of powershell
$pass="%^&<>|'\``,;=(\)![]\/";
# Wait till it ends with -Wait when using -NoNewWindow.
# It may be comprehensible to use `" instead of "" to denote we are enclosing string in quotes.
(thanks #mklement0 for elaboration).
start-process -Wait -NoNewWindow .\script.cmd "`"$pass`""
script.cmd
setlocal
rem Remove Double quotes
set "arg=%~1"
rem Test result with base64 encoding
echo|set/p="%arg%"|openssl base64
rem echo is used with set/p to prevent trailing new line.
echo|set/p="%arg%"|python main.py
rem Test with following, argument is in double quotes
rem script "%^&<>|'\`,;=(\)![]\/"
rem Expected result
rem %^&<>|'\`,;=(\)![]\/
tl;dr:
Unless you specifically need the batch file to run in a new window, avoid Start-Process (whose built-in aliases are start and saps), and invoke the batch file directly.
To avoid problems with special characters in $unsecurePassword, do not pass it as an argument, pass it via stdin (the standard input stream), which your batch file will pass through to your python script:
automation.ps1:
# ...
$script = "<path>\batch_script.bat"
# Pass the password via *stdin*
$unsecurePassword | & $script 'userName'
Note: It is the $OutputEncoding preference variable that controls what character encoding PowerShell uses for sending text to an external program's stdin. In Windows PowerShell, that variable defaults to ASCII(!) encoding, meaning that any characters outside the 7-bit ASCII-range of Unicode characters, such as accented characters, are unsupported (they turn to literal ?); fortunately, PowerShell [Core] v6+ now defaults to UTF-8. Assign the required encoding to $OutputEncoding as needed.
batch_script.bat:
#echo off
SET username=%~1
REM The batch file will pass its own stdin input through to Python.
python main.py
Read on for background information.
Invoking a batch file from PowerShell:
Unless you truly need to launch a batch file in a new window, the best approach is to invoke it directly from PowerShell; that way, it runs:
in the same console window, synchronously.
with its output streams connected to PowerShell's (which allows you to capture or redirect the output).
Because your batch-file path is stored in a variable, direct invocation requires use of &, the call operator:
# Note: The " chars. around $unsecurePassword are only needed if the variable
# value contains cmd.exe metacharacters - see next section.
& $script 'userA' `"$unsecurePassword`"
Start-Process is usually the wrong tool for invoking console applications, batch files, and other console-based scripts; see this answer for more information.
If you do need the batch file to run in a new window (which is only an option on Windows), use Start-Process as follows (the command will execute asynchronously, unless you also pass -Wait):
# The string with the arguments to pass is implicitly bound
# to the -ArgumentList parameter. Use only " for embedded quoting.
Start-Process $script "userA `"$unsecurePassword`""
Note: While the (implied) -ArgumentList (-Args) parameter is array-valued ([string[]]) and passing the arguments individually is arguably the cleaner approach, this generally does not work properly, due to a longstanding bug that probably won't get fixed; for instance,
Start-Process foo.exe -Args 'one', 'two (2)' passes 3 arguments rather than 2; that is, it passes single string 'two (2)' as two arguments - see this GitHub issue.
Therefore, it is ultimately simpler and more predictable to pass a single argument with embedded quoting to -ArgumentList, but be sure to use only " (not ') for embedded quoting:
Start-Process foo.exe -Args "one `"two (2)`""
Passing arguments robustly to cmd.exe / batch files:
Note:
The limitations of cmd.exe (the legacy command processor that interprets batch files) prevent fully robust solutions; notably, you cannot prevent the interpretation of tokens such as %foo% as environment-variable references when you call a batch file from PowerShell (at least not without altering the argument to %foo^%, which will retain the ^).
In your specific case, since you're trying to echo an argument unquoted, embedded double quotes (") in such an argument - which need to be escaped as "" - aren't properly supported: they are passed through as "".
Passing an unquoted argument to cmd.exe / a batch file breaks, if that argument contains one of cmd.exe's metacharacters, i.e., characters with special syntactic meaning; in this context, they are: & | < > ^ "
The solution is to enclose the argument in double quotes ("..."), with the added need to double " chars. that are embedded (a part of the value).
PowerShell, after performing its own parsing of the command line (notably evaluating variable references and expressions), constructs the command line that is ultimately used to invoke the external target program, behind the scenes.
However, it only automatically double-quotes an argument if it contains spaces, not if it only contains cmd.exe metacharacters; e.g., a variable with verbatim string content two (2) is passed double-quoted - $val = 'two 2'; .\foo.bat $val results in command line .\foo.bat "two 2" - whereas string content a&b is not - $val = 'a&b'.\foo.bat $val results in .\foo.bat a&b - which breaks.
The solution - as shown in your question - is to enclose the variable reference in literal, embedded " characters, because such a "pre-quoted" value instructs PowerShell to pass the value as-is:
$val = 'a&b'; .\foo.bat `"$val`" results in .\foo.bat "a&b"
Note: .\foo.bat "`"$val`"" has the same effect; I'm taking advantage of the fact that PowerShell in argument (parsing) mode (generally) implicitly treats arguments as if they were double-quoted; in expression (parsing) mode, such as in the array-construction statement in the question (#(..., ...)), you do need the "`"$val`"" form.
The problem with your specific batch file:
A properly "..."-enclosed argument (with any embedded " chars. escaped as "") is properly seen as a parameter (e.g., %1) inside a batch file.
However, it is seen with the enclosing double quotes and with any doubled embedded " chars.
If you were to pass this parameter to the target program (python in this case) as an argument, everything would work as expected.
However, since you're passing the value via stdin using echo, you need to strip the enclosing double quotes so that they're not passed as part of the value, which is what your batch file attempts (e.g., %~2)
However, passing the stripped value causes the echo command to break.
There is no good solution to this problem with echo, short of performing cumbersome explicit ^-escaping (^ being cmd.exe's escape character):
$escapedUnsecurePassword = $unsecurePassword -replace '[&|<>^]' -replace '"', '""'
& $script 'userA' `"$escapedUnsecurePassword`"
That alone still isn't enough, however - your batch_script.bat file needs a modification too:
Because the assignment itself in your SET password=%~2 command isn't protected with double quotes, it breaks with values that contain metacharacters; somewhat paradoxically, you must use the form SET "password=%~2" in order to safely strip the embedded enclosing " chars.:
#echo off
REM Strip the enclosing "..." from the arguments (%~<n>)
REM !! The *assignment itself* must be in "..." so that
REM !! it does not break if the value has cmd.exe metacharacters.
set "username=%~1"
set "password=%~2"
echo(%password%|python main.py
Note that that will work as intended for all metacharacters except the - of necessity doubled - embedded ", which are passed through as "".
However, there is a workaround for echoing a string with metacharacters unquoted, as also demonstrated in subcoder's helpful answer:
If you define batch_script.bat as follows:
#echo off
set "username=%~1"
REM Do NOT strip quotes from the password argument
set password=%2
REM Using a trick with set /p, echo the password unquoted.
REM Note: Put the "|" directly after the ":" to avoid trailing spaces.
<NUL set /p=%password% & echo:|python main.py
The workaround repurposes set's /p option, which accepts a prompt message to print when interactively prompting the user for a value and prints the message without quotes; the actual interactive prompt is suppressed via <NUL, so that only the message is printed.
echo: prints a newline (line break), which is necessary, because the set /p command prints its message without a trailing newline (if you don't want to send a newline, simply omit & echo:).
Caveat: In addition to the problem with embedded "" applying here too, this workaround has a side effect: it trims leading whitespace; e.g., " foo " results in output foo   (only trailing whitespace is preserved); however, given that arguments with leading whitespace are rare, this may not matter in practice.
Given how cumbersome / obscure the above approaches are, the stdin-based approach shown at the top is preferable.
You pass arguments to batch files in powershell using the -argumentlist switch of start/saps. For you you could use:
saps "$script" -argumentlist $args
But I would suggest first breaking $args up as it may not work since to pass arguments, you usually want to pass the arguments one at a time like:
saps "$script" -argumentlist "1","2","3"
Passing $args will work most of the time, but there are some cases where where it won't work. Most of the time you are fine though

How to escape spaces the parameters to a PS script running in VS post-build event

Update 1:
After looking carefully at the output again, I sort of figured it out.
By adding a trailing space between the closing parenthesis and the quotes it works:
powershell -ExecutionPolicy ByPass -File "$(SolutionDir)\BuildScripts\InjectGitVersion.ps1" "$(ProjectDir) " "$(TargetDir) "
I am suspecting that PowerShell somehow interprets the )".
Is there a more elegant way around this issue?
Update 2:
This is weird. I have another script that does the clean up and works with out the space between ) and " like this:
powershell -ExecutionPolicy ByPass -File "$(SolutionDir)\BuildScripts\InjectGitVersionCleanup.ps1" "$(ProjectDir)"
However adding trailing spaces will cause it to fail, because internally it appends a file-name to the path, so that the path will be incorrect.
If any one understand this, I would be happy to accept the explanation as the correct answer!
Original:
I have the following prebuild-command in VisualStudio, which I want to use to inject the version from a Git-tag:
powershell -ExecutionPolicy ByPass -File "$(SolutionDir)\BuildScripts\InjectGitVersion.ps1" $(ProjectDir) $(TargetDir)
This works fine, if the path $(SolutionDir) does not contain spaces (which will then also present in $(ProjectDir) or $(TargetDir)).
When the path $(SolutionDir) does contain spaces, it appears the script starts as expected, but the arguments are not passed correctly and I am unable to figure out how to escape them in the arguments to the PS-script.
I have tried adding sing ", triple """ and also ', which gives the following (each PS-command tries a different method for escaping the spaces):
powershell -ExecutionPolicy ByPass -File "$(SolutionDir)\BuildScripts\InjectGitVersion.ps1" $(ProjectDir) $(TargetDir)
args[0]:
D:\VisualStudio
args[1]:
Projects\software_git_repo\ProgramEditor\
powershell -ExecutionPolicy ByPass -File "$(SolutionDir)\BuildScripts\InjectGitVersion.ps1" "$(ProjectDir)" "$(TargetDir)"
BS: args[0]:
D:\VisualStudio Projects\software_git_repo\ProgramEditor" D:\VisualStudio
BS: args[1]:
Projects\software_git_repo\ProgramEditor\bin\Debug"
powershell -ExecutionPolicy ByPass -File "$(SolutionDir)\BuildScripts\InjectGitVersion.ps1" """$(ProjectDir)""" """$(TargetDir)"""
BS: args[0]:
"D:\VisualStudio
BS: args[1]:
Projects\software_git_repo\ProgramEditor"
powershell -ExecutionPolicy ByPass -File "$(SolutionDir)\BuildScripts\InjectGitVersion.ps1" '$(ProjectDir)' '$(TargetDir)'
BS: args[0]:
'D:\VisualStudio
BS: args[1]:
Projects\software_git_repo\ProgramEditor\'
Doing:
echo ProjectDir:
echo $(ProjectDir)
echo TargetDir:
echo $(TargetDir)
I get:
ProjectDir:
D:\VisualStudio Projects\software_git_repo\ProgramEditor\
TargetDir:
D:\VisualStudio Projects\software_git_repo\ProgramEditor\bin\Debug\
Beware unexpected escaped quotes when a VS directory macro is expanded in a quoted parameter.
A script parameter containing a path should be wrapped in quotes to avoid being interpreted as multiple parameters if the path includes spaces. The following would therefore seem reasonable in Visual Studio's pre/post build event command line:-
powershell.exe -file "$(SolutionDir)script.ps1" -projectDirectory "$(ProjectDir)"
But the macro values that specify directories always include a trailing backslash; so when they're expanded the line becomes, for example:-
powershell.exe -file "C:\sln path\script.ps1" -projectDirectory "C:\sln path\project\"
...where the trailing \" is an escaped character that is interpreted as part of the parameter value, which is passed to the script as:-
C:\sln path\project"
Solution
The fix in my case was to insert an additional backslash after the directory macro to be escaped instead of the quotes, which restores the expected parameter value and prevents the mangling of any subsequent params:-
powershell.exe -file "$(SolutionDir)script.ps1" -projectDirectory "$(ProjectDir)\"
Not very intuitive though. Let me know if I'm missing something more obvious...
One thing I noticed is an extra backslash after $(SolutionDir). I'm using VS2017 and I think consistently $(SolutionDir) has always had a backslash. Not sure though.
I have a .bat in my solution folder that creates a json config file. My Post-Build event looks like this.
call "$(SolutionDir)CreateConfigurationJson.bat" "$(TargetDir)mySettings.json"
{"ProjectName":"$(ProjectName)"}
"$(TargetDir)mySettings.json" is the first parameter of the .bat file
{"ProjectName":"$(ProjectName)"} is the second parameter.
No escape characters necessary. Hope this helps.
The code for the bat file is:
echo off
set filePath=%1
break>%filePath%
set json=%2
echo %json% >>%filePath%
thank you #tranquil_tarn for your great answer!
I struggled with the BATCH + Powershell escaping hell for multiple hours.
Posting final code here to hopefully help someone out.
Solution
#ECHO OFF
cls
echo .SYNOPSIS
echo Allows prebuild to be Powershell, instead of BATCH. (You'll thank me later)
echo Specifically this example shows passing in VisualStudio (ProjectDir) variable AND correctly handles spaces in the path.
echo .DESCRIPTION
echo Call prebuild.ps1 and pass in built-in VS Variables: https://learn.microsoft.com/en-us/visualstudio/ide/reference/pre-build-event-post-build-event-command-line-dialog-box?view=vs-2022
echo Use powershell best-practices named parameters rather than positional to avoid positional-unmatched errors.
echo If Username '$(User)' is blank DO NOT include the Switch at all to avoid powershell errors
REM TASK1: Check a variable for blank/empty and only include the Param if not empty
SET "user=$(User)"
IF [%user%]==[] (
echo "userParam is blank"
) ELSE (
SET "userParam=-User:%user%"
)
echo "userParam: %userParam%"
REM TASK2: Warning: (ProjectDir) in VisualStudio ends in a slash, which when expanded, results in escaped quote \' at end of line
REM if you DO NOT include the quotes, then spaces in path will break it. if you DO include quotes then it gets escaped.
REM solution is to include an extra slash at the end, resulting in double backslash \\ which escapes down to single backslash '\'... whew!
REM 'see https://stackoverflow.com/questions/52205117/how-to-escape-spaces-the-parameters-to-a-ps-script-running-in-vs-post-build-even/64146880#64146880'
cd "$(ProjectDir)\"
SET cmd1=powershell -NoProfile -ExecutionPolicy Unrestricted -Command ^
".\prebuild.ps1 -ProjectDir:'$(ProjectDir)\' -Conf:'$(Configuration)' -ProjectName:'$(ProjectName)' %userParam% "
REM For debugging, view the command (including escaped strings) before calling it.
echo %cmd1%
call %cmd1%

Why does invoke operator (&) and Invoke-Expression produce different results for the same input?

From my understanding, the invoke operator (&) and the Invoke-Expression cmdlet should behave similar. However, as can be seen below, this is not the case:
PS C:\Users\admin> powershell -Command "& {""([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZWNobyAnaGVsb
G93b3JsZCc=')))""}"
echo 'helloworld'
PS C:\Users\admin> powershell -Command "IEX ""([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZWNobyAnaGVs
bG93b3JsZCc=')))"""
helloworld
Here, 'ZWNobyAnaGVsbG93b3JsZCc=' is the Base64 encoded string "echo helloworld".
Can someone clarify?
Invoke-Expression (whose built-in alias is iex) and &, the call operator, serve different purposes:
Invoke-Expression evaluates a given string as PowerShell source code, as if you had executed the string's content directly as a command.
As such, it is similar to eval in bash and therefore only to be used with input that is fully under the caller's control or input that the caller trusts.
There are often better solutions available, so Invoke-Expression should generally be avoided
& is used to invoke a command (& <nameOrPath> [...]) or a script block (& { ... } [...]):
Neither case involves evaluating a string as source code.
In the case at hand:
The core of your command is the following expression, which returns the string
"echo 'helloworld'" (its content doesn't include the enclosing " - this is simply the representation of the resulting string as a PowerShell string literal):
[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('ZWNobyAnaGVsbG93b3JsZCc='))
Also note that, due to how the command line is parsed, the ""..."" surrounding the core expression in your original commands are effectively ignored, which explains why the expression is executed rather than being treated as the content of a string.[1]
Therefore, your two commands amount to:
& { "echo 'helloworld'" }
& executes the statement inside the script block, which happens to be a string, and a string by itself - if it isn't assigned to a variable or redirected elsewhere - is simply output as-is.
In this case, the command is effectively the same as just executing "echo 'helloworld'" by itself (including the enclosing ", which you can think of as
echo "echo 'helloworld'"), so echo 'helloworld' prints to the console.
Note that echo is a built-in alias for the Write-Output cmdlet, whose explicit use is rarely necessary: Return values from commands or expressions are implicitly output, if they are not captured in some form, as in this case, where executing a string by itself as a statement simply outputs the string. (You can try this by submitting just 'hi' at the prompt, for instance).
iex "echo 'helloworld'"
This makes iex (Invoke-Expression) evaluate the string's content as source code, which therefore executes echo 'helloworld', which prints helloworld to the console.
[1] Optional reading: PowerShell quoting woes when calling external programs
Note:
Handling of quoting with respect to external programs or when calling from an external programs is not part of the official documentation, as far as I can tell (as of this writing, neither about_Parsing nor about_Quoting_Rules nor about_Special_Characters mentions it - I've opened this issue on GitHub to address that).
There are flaws in the existing handling, but they cannot be fixed without breaking backward compatibility.
When calling from PowerShell, the best approach is to use a script block, which bypasses the quoting problems - see below.
Even though you correctly embedded " by escaping them as "" inside the overall "..." string from a PowerShell-internal perspective, additional escaping of " with \ is needed in order to pass them through to an external program, even if that external program is another instance of PowerShell called via powershell.exe.
Take this simplified example:
powershell.exe -command " ""hi"" " # !! BROKEN
powershell.exe -command ' "hi" ' # !! BROKEN
PowerShell-internally, " ""hi"" " and ' "hi" ' evaluate to a string with literal contents  "hi" , which, when executed, prints hi.
Regrettably, PowerShell passes this string to powershell.exe as " "hi" " - note how the "" turned into plain " and the enclosing single quotes were replaced with double quotes - which effectively results in  hi  after parsing by the new instance (because " "hi" " is parsed as the concatenation of substrings " ", hi, and " "), so PowerShell ends up trying to execute a (presumably nonexistent) command named hi.
By contrast, if you manage to pass the embedded as " as \" (sic) - after meeting PowerShell's own escaping needs - the command works as intended.
Therefore, as stated, you need to combine PowerShell-internal escaping with for-the-CLI escaping in order to pass an embedded ", so that:
inside overall "...", each embedded " must be escaped as \"" (sic) or \`" (sic)
inside overall '...', \" can be used as-is.
powershell.exe -command " \""hi\"" " # OK
powershell.exe -command " \`"hi\`" " # OK
powershell.exe -command ' \"hi\" ' # OK
Alternatively, use a script block instead of a command string, which bypasses the quoting headaches:
powershell.exe -command { "hi" } # OK, but only works when calling from PS
Note that the script-block technique only works when calling from PowerShell, not from cmd.exe.
cmd.exe has its own quoting requirements:
Notably, cmd.exe only supports "" for embedding double quotes (not also `"); thus, among the solutions above, only
powershell.exe -command " \""hi\"" " works from cmd.exe (a batch file) without additional escaping.
The down-side of \"", however, is that runs of interior whitespace between \""...\"" are collapsed to a single space each. To avoid that, use \"...\", but cmd.exe then sees substrings between the \" instances as unquoted, which would cause the command to break if that substring contained metacharacters such as | or &; e.g., powershell.exe -command " \"a|b\" "; to fix that you must individually ^-escape the following characters: & | < > ^
powershell.exe -command ' "hi" ' is similarly brittle, because cmd.exe doesn't recognize ' as a string delimiter, so any metacharacters outside embedded "..." are again interpreted by cmd.exe itself; e.g., powershell.exe -command ' "hi" | Measure-Object '
Finally, using just "" from cmd.exe for embedding " sometimes works, but not reliably; e.g., powershell.exe -command " 'Nat ""King"" Cole' " prints Nat "King Cole (the closing " is missing).
This appears to have been fixed in PowerShell Core.

Using PowerShell command and backtick newline inside Windows batch

I am trying to write a Windows batch file which will replace occurrences of angled brackets (><) with a newline in between.
I am new to PowerShell, but in searching though possible solutions, I have found the following works from PowerShell:
(get-content input.txt) -replace "><", ">`n<" | set-content output.txt
To use this within a windows batch, I need to wrap it inside
powershell -command "arguments"
So the final command is something like:
powershell -command "(gc input.txt) -replace '><', '>`n<' | sc output.txt"
However, this of course does not work because the single quotes around the replace text causes the grave quote escape character to be treated literally.
I have searched far and wide on the correct combination of escape characters to use to allow the PowerShell escape character to be recognised and have found a similar answer in here, but when I try this suggestion, I get a "< was unexpected at this time" error. I think what I need is more complicated because my search string also contains the angled brackets.
Look at the powershell.exe command line options. You can use a script block:
powershell -command {(gc input.txt) -replace "><", ">`n<" | sc output.txt}
Avoid using the escape character and double quotes?
powershell -command "(gc d:\t\input.txt) -replace '><', ('>'+[char]10+'<') | sc d:\t\output.txt"
I have solved the problem.
I also used delayed expansion, so the final command is:
powershell -Command "(gc !inputfile!) -replace (\"^>^<\", \"^>`n^<\") | sc !outputfile!"
So it actually uses three different types of escape characters! Combination of \ and ^ and `.
I wish I could say I worked it out logically, but in the end it was just a random attempt using different escapes on the ><.
But this is now a good reference on how to use PowerShell inside Windows batch without using single quotes which turn escape characters into literals.

Resources