How do I add into the registry in which the value of the UninstallString needs to have quotation marks at the beginning and end.
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Visual Studio 2010 Tools for Office Runtime (x64)
tried to edit the value of the registry with this but received an error
New-ItemProperty -Path "HKLM:\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Visual Studio 2010 Tools for Office Runtime (x64)" -Name "UninstallString" -Value ""C:\Program Files\Common Files\Microsoft Shared\VSTO\10.0\Microsoft Visual Studio 2010 Tools for Office Runtime (x64)\install.exe"" -PropertyType "String"
Your question boils down to this: how can I pass an argument that has embedded " characters, i.e. " characters that are a verbatim part of the argument?
Specifically, you want New-ItemProperty's -Value parameter to see the following value verbatim:
"C:\Program Files\Common Files\Microsoft Shared\VSTO\10.0\Microsoft Visual Studio 2010 Tools for Office Runtime (x64)\install.exe"
The tl;dr solution, using '...' quoting, is:
New-ItemProperty `
-Path "HKLM:\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Visual Studio 2010 Tools for Office Runtime (x64)" -Name "UninstallString" `
-Value '"C:\Program Files\Common Files\Microsoft Shared\VSTO\10.0\Microsoft Visual Studio 2010 Tools for Office Runtime (x64)\install.exe"' `
-PropertyType String
Your own attempt - passing ""C:\Program Files\..."" - was flawed in that the initial "" created an empty-string argument, followed by argument C:\Program, and so on - which isn't what you intendded.
Read on for a overview of all solution options.
The following is a systematic overview of the solution options; it uses Write-Output and verbatim value "foo bar" for brevity, which enables easy verification of whether the argument was passed as intended:
Use a verbatim (single-quoted) string ('...'), if the value can be expressed as such, i.e., literally:
Write-Output '"foo bar"'
Use an expandable (double-quoted) string ("..."), if the value must be derived from variable values and/or subexpressions:
Inside "...", embedded " chars. must be escaped; while doubling them works (""), the preferable solution for consistency is to use `", because it uses PowerShell's general-purpose escape character, `, the so-called backtick:
$var = 'foo'
Write-Output "`"$var bar`""
Alternatively, use the here-string variants of the string-literal forms shown above, whose syntax is invariably multiline, and which are especially useful for defining multiline strings in a readable format; however, they are also useful for defining single-line strings, because they avoid the need for escaping of embedded quotes:
Verbatim (single-quoted) here-string:
Note: Since " do not require escaping in regular '...' strings anyway, there's not much benefit to using the here-string form in this case.
However, if the string contained embedded ' chars., you would benefit from not having to escape them (in regular verbatim strings, you'd have to escape them as '').
Write-Output #'
"foo bar"
'# # IMPORTANT: Closing delimiter must be at the VERY START OF THE LINE
Expandable (double-quoted) here-string:
Note how the embedded " need no escaping in this case.
The only character that potentially needs escaping - with ` - is $, namely if you do not want it to be considered the start of variable reference (e.g,. $foo) or subexpression (e.g, $(1 + 2)) to be expanded.
$var = 'foo'
Write-Output #"
"$var bar"
"# # IMPORTANT: Closing delimiter must be at the VERY START OF THE LINE
Caveat re calling external programs:
Up to PowerShell 7.2.x, the sad reality is that an extra, manual layer of \-escaping of embedded " characters is required.
See this answer.
Since PowerShell 7.3.0, this is now mostly no longer necessary, but on Windows there are selective exceptions:
See this answer.
Related
I have a declarative pipeline that should execute a powershell step for mounting a path.
The path is provided as a parameter:
parameters {
string(name: 'UNC', defaultValue: '\\\\server01.lab.local\\shared_data', description: 'Shared location to build-data')
}
When I use this value in a simple print, it is working as expected:
...
powershell('''
write-host "test: "${env:UNC}""
''')
...
So the next step was actually to mount it. However, it is not working as expected:
...
powershell('''
New-PSDrive -Name "k" -PSProvider "FileSystem" -Root "\"${env:UNC}\"\"
''')
...
The error that I'm getting here is: powershell.exe : New-PSDrive : A positional parameter cannot be found that accepts argument '\\server01.lab.local\shared_data'. New-PSDrive -Name "k" -PSProvider "FileSystem" -Root ""${ ...
This is when I understand that quoting is the issue. The Powershell command that does work is:
New-PSDrive -Name "k" -PSProvider "FileSystem" -Root "\\server01.labl.local\shared_data"
So what am I missing here in terms of escaping the quotes in the UNC path?
Thanks
Do not try to quote ${env:UNC} at all:
powershell('''
New-PSDrive -Name "k" -PSProvider "FileSystem" -Root ${env:UNC}
''')
In cases where you do need to escape " inside "...", use `" not \"; e.g.,
"Nat `"King`" Cole" yields verbatim Nat "King" Cole - read on for more information.
Inside PowerShell, \ has no special meaning and instead it is ` (the so-called backtick) that serves as the escape character, namely in these contexts:
Inside double-quoted strings ("...")
Inside "..." only, you can use "" as an alternative to `" for escaping embedded " chars (but the other two characters that require escaping inside "...", $ and ` itself, can only be escaped as `$ and ``, respectively).
In unquoted command arguments (not often seen); e.g.:
Get-Item C:\Program` Files # Note the escaped space char.
Note that ` isn't just used to signal that the next character is to be used verbatim, but also as the start of escape sequences representing control characters and, in PowerShell (Core) 6+ only, Unicode characters; e.g., "`t" expands to a tab character, and "`u{fc}" to ü. See the conceptual about_Special_Characters topic.
Double-quoting variable references (or expression results) used as command arguments is never necessary in PowerShell (unless you explicitly need to convert to a string first).
E.g., the following works just fine:
$dir = 'C:\Program Files'
Get-Item $dir # NO need to double-quote $dir
If you really need to pass verbatim " chars. as part of an argument's actual value:
Write-Output "Nat `"King`" Cole"
Note: Even when calling external executables double-quoting around variable references / expression isn't needed, because PowerShell then automatically applies double-quoting behind the scenes, as needed, based on whether a value contains spaces or not.
Unfortunately, passing arguments with verbatim embedded " characters to external programs is still broken as of PowerShell 7.1, though a fix is finally being considered - see this answer.
As for what you tried:
\ is not special in PowerShell (though it is needed to escape verbatim " chars. in arguments to the PowerShell CLI[1]).
Therefore, "\"${env:UNC}\"\" is parsed as follows:
Argument 1:
"\" is a double-quoted string with verbatim content \. Since a quoted string at the start of a compound token (i.e. if directly followed by another quoted or unquoted token) is always considered an argument by itself, this value becomes its own argument. This notable pitfall is discussed in this answer.
Argument 2:
${env:UNC}\ expands to the value of environment variable UNC followed by verbatim \
"\" is again a double-quoted string with verbatim content \; because it directly follows the unquoted token ${env:UNC}\, it is considered part of the same argument.
The following example, which outputs the arguments received enclosed in <...>, each on its own line, demonstrates this:
PS> $env:UNC='foo bar';
& { foreach ($arg in $args) { "<$arg>" } } "\"${env:UNC}\"\"
<\>
<foo bar\\>
[1] This applies to powershell.exe, the Windows PowerShell CLI; pwsh, the PowerShell (Core) v6+ CLI, alternatively accepts ""- see this answer for more information.
I'm writing a service installer script in Powershell where the service requires a complicated quoted command line.
So I tried to simplify it and broke each option down to individual variables, however when creating the final command line string the strings don't escape their quotes. Thus the command line doesn't work.
I'd like to keep all the options separate so that other admins can configure and install the service without needing to worry about escaping quotes.
I'm thinking I need to perform a search and replace or use a shell specific safe/escape string command to operate on the individual strings first.
I don't know how the command line of a service is parsed, so not sure which shell escape method to use.
I've done a search on quotes in strings but they never seem to deal with nesting of strings with quotes inside strings with quotes.
This is my install script and I do have control over the applicationservice, so if you know of a better method to get arguments into a service that would also be appreciated.
$installpath = (get-location)
$name="landingZone"
$displayName="LandingZone Starter"
$description="Sony CI automated download client"
$sessionuser="Engineering"
$processname="explorer"
$logname="Landing Zone"
$programpath="C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe"
$programarguments='"C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe -jar C:\Program Files (x86)\Sony CI\Sony-Ci-LZ-1.4.22\Sony-Ci-LZ-1.4.22.jar"'
$WorkDirectory="C:\Program Files (x86)\Sony CI\Sony-Ci-LZ-1.4.22"
$Visible=1
$TerminateTimeout=1000
# Arg! help me!
$binpath = $installpath.toString() + "\applicationservice.exe ""SessionUser=$sessionuser"" ""ProcessName=$processname"" ""LogName=$logname"" ""ProgramPath=$programpath"" ""ProgramArguments=$programarguments"" ""WorkDirectory=$workdirectory"" Visible=$visible TerminateTimeout=$terminatetimeout"
New-Service -Name $name -BinaryPathName $binPath -DisplayName $displayname -Description $description -StartupType Manual
Thanks
Thanks to everyone who took time to comment and answer.
This is the single line PS script string which ended up working (look 6 double quotes in a row);
"$($inspath)\applicationservice.exe SessionUser=Engineering ProcessName=explorer
LogName=""Landing Zone"" ProgramPath=""C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe""
ProgramArguments=""""""C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe"""" -jar """"c:\users\Engineering\Desktop\Sony CI\Sony-Ci-LZ-1.4.22\Sony-Ci-LZ-1.4.22.jar""""""
WorkDirectory=""c:\users\Engineering\Desktop\Sony CI\Sony-Ci-LZ-1.4.22"" Visible=1
TerminateTimeout=1000"
Which ends up like this command line, as shown in the PathName property of the WMI win32_service.
C:\Users\mheath\Documents\20201106-MakeMeAService\ServiceScripts\applicationservice.exe
SessionUser=Engineering ProcessName=explorer LogName="Landing Zone" ProgramPath="C:\Program Files
(x86)\Common Files\Oracle\Java\javapath\java.exe" ProgramArguments="""C:\Program Files (x86)\Common
Files\Oracle\Java\javapath\java.exe"" -jar ""c:\users\Engineering\Desktop\Sony
CI\Sony-Ci-LZ-1.4.22\Sony-Ci-LZ-1.4.22.jar""" WorkDirectory="c:\users\Engineering\Desktop\Sony
CI\Sony-Ci-LZ-1.4.22" Visible=1 TerminateTimeout=1000
And finally the command line as read by the service, using Environment.GetCommandLineArgs(); in the OnStart() method (comma separated)
C:\Users\mheath\Documents\20201106-MakeMeAService\ServiceScripts\applicationservice.exe,SessionUser=mheath,ProcessName=explorer,LogName=Landing Zone test,ProgramPath=C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe,ProgramArguments="C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe" -jar "c:\users\Engineering\Desktop\Sony CI\Sony-Ci-LZ-1.4.22\Sony-Ci-LZ-1.4.22.jar",WorkDirectory=c:\users\Engineering\Desktop\Sony CI\Sony-Ci-LZ-1.4.22,Visible=1,TerminateTimeout=1000
I made some significant changes to the script, putting the config in a HashTable and using the -f string formatter.
$installpath = (get-location)
$name="testlz"
$displayName="Testlz"
$description="Testlz"
$config = #{
SessionUser='mheath';
ProcessName='explorer';
LogName='Landing Zone test';
ProgramPath= 'C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe';
ProgramArguments='"C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe" -jar "c:\users\Engineering\Desktop\Sony CI\Sony-Ci-LZ-1.4.22\Sony-Ci-LZ-1.4.22.jar"';
WorkDirectory='c:\users\Engineering\Desktop\Sony CI\Sony-Ci-LZ-1.4.22';
Visible=1;
TerminateTimeout="1000";
}
$escconfig = #{}
$escconfig.Add('ServicePath', $installpath.toString() + '\applicationservice.exe')
foreach ($it in $config.Keys)
{
$escconfig[$it] = '"{0}"' -f ($config[$it] -replace '"','""')
}
$binpath = '{0} SessionUser={1} ProcessName={2} LogName={3} ProgramPath={4} ProgramArguments={5} WorkDirectory={6} Visible={7} TerminateTimeout={8}' `
-f $escconfig['ServicePath'], $escconfig['SessionUser'], $escconfig['ProcessName'], $escconfig['logname'], `
$escconfig['ProgramPath'], $escconfig['ProgramArguments'], $escconfig['WorkDirectory'], $escconfig['Visible'], $escconfig['TerminateTimeout']
New-Service -Name $name -BinaryPathName $binPath -DisplayName $displayname -Description $description -StartupType Manual
This hopefully protects any other paths that contain spaces.
PS. Any discrepancies you see from one text code block to the next, are just because I copied and pasted it as I was testing.
Note:
This answers addresses the question as asked, with respect to the quoting and escaping required to construct a command line via a single string.
For a robust programmatic alternative that constructs the command line via a hashtable and a loop, see Mark's own answer.
Potential problem: If $installpath.toString() returns a path with spaces, you'll have to use embedded quoting for the executable path as well.
Definite problem:
The following argument itself has embedded ":
$programarguments='"C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe -jar C:\Program Files (x86)\Sony CI\Sony-Ci-LZ-1.4.22\Sony-Ci-LZ-1.4.22.jar"'
However, this embedded quoting isn't placed correctly: the executable path and the -jar argument individually need enclosing in "...", because both have embedded spaces:
$programarguments='"C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe" -jar "C:\Program Files (x86)\Sony CI\Sony-Ci-LZ-1.4.22\Sony-Ci-LZ-1.4.22.jar"'
In order to embed this value inside another double-quoted string for command-line use, you must escape its embedded " as either "" or \" (see below for when to use which), which you can do with -replace '"', '""' or -replace '"', '\"'.
The following uses an expandable here-string (#"<newline>...<newline>"#) to simplify the embedded quoting and spreads the command across several lines for readability (the resulting newlines are removed afterwards with -replace '\r?\n'):
$binpath = $installpath.toString() + #"
\applicationservice.exe
SessionUser="$sessionuser"
ProcessName="$processname"
LogName="$logname"
ProgramPath="$programpath"
ProgramArguments="$($programarguments -replace '"', '""')"
WorkDirectory="$workdirectory"
Visible=$visible
TerminateTimeout=$terminatetimeout
"# -replace '\r?\n'
Note:
The above only uses embedded double-quoting for the value part of the <property>=<value> pairs (e.g., foo="bar none" rather than "foo=bar none"), which, unfortunately, is not an uncommon requirement on Windows (notably with msiexec), and it also seems to be necessary here, judging from your own answer,
" embedded inside the $programarguments value are escaped as "" rather than as \":
Either form of escaping typically works, but "" has the advantage that you needn't worry about values ending in \, which with \"-escaping would additionally require you to escape that trailing \ as \\.
The caveat is that while most CLIs on Windows recognize both "" and \" as an escaped ", some recognize only \, such as Ruby, Perl, and notably also applications that use the CommandLineToArgv WinAPI function.
See also:
string literals in PowerShell (bottom section)
expandable (interpolating) strings
the regex-based -replace operator.
As an aside:
The backtick ` is PowerShell's general-purpose escape character.
Inside "..." strings (only), you can alternatively escape an embedded " char. as "".
For instance, both " `"hi`" "` and " ""hi"" " return verbatim "hi" .
The exception is that when PowerShell is called from the outside, via its CLI, only \" is recognized as an escaped " in Windows PowerShell, so as to be consistent with other CLIs, whereas PowerShell [Core] v6+ also accepts "".
You should escape your quotes with a backtick:
$test = "`"test`""
Write-Host $test
Or you can use a here string like this
$test = #'
"test"
'#
Write-Host $test
Both write "test" to the console
I have this command that works ok on powershell
Compare-Object (Get-Content "tex1.txt") (Get-Content "tex2.txt") | Where-Object{$_.SideIndicator -eq "<="} | select inputobject | ft -hidetableheaders
I'm trying to running in cmd by doing this:
powershell -Command " & {Compare-Object (Get-Content "tex1.txt") (Get-Content "tex2.txt") | Where-Object{$_.SideIndicator -eq "<="} | select inputobject | ft -hidetableheaders}"
but it says something like: the name, the directory or the volume syntax is incorrect (is in spanish so i dont know the exact translation)
I think the problem is the pipes, since running everything before the pipe: Compare-Object (Get-Content "tex1.txt") (Get-Content "tex2.txt") works
PD: I also tried to write ^ before the pipes but I haven't succeeded.
tl;dr
When calling the PowerShell CLI (powershell.exe for Windows PowerShell, pwsh for the cross-platform PowerShell [Core] 6+ edition):
Using embedded " in an overall "..." string comes with escaping challenges.
If feasible for a given command, formulating it without embedded " is the easiest solution:
powershell -Command "Compare-Object (Get-Content tex1.txt) (Get-Content tex2.txt) | Where-Object {$_.SideIndicator -eq '<='} | select inputobject | ft -hidetableheaders"
Read on, if you do need to use embedded ".
eryksun points out that your problem is your lack of escaping of embedded " chars. inside the overall "..." string, which causes cmd.exe to see multiple strings, including parts it considers unquoted, which causes problems with special characters such as | and < - they never reach PowerShell.
Nesting double-quote strings from cmd.exe is tricky business:
To make cmd.exe happy, you need to double the embedded " chars. ("")
Separately, to make powershell.exe happy, you need to \-escape " chars.
Note: PowerShell [Core] 6+, the cross-platform edition, on Windows now also accepts "" by itself, which is the most robust choice.
Generally, dealing with quoting and escaping arguments when calling from cmd.exe is a frustrating experience with no universal solutions, unlike in the Unix world. Sadly, PowerShell has its own challenges, even in the Unix world.[1]
In short: Escape embedded " chars. when calling the Windows PowerShell CLI, powershell.exe, from cmd.exe as follows (for the PowerShell (Core) 7+ CLI, pwsh.exe, "" is the robust choice):
Use "^"" (sic) when using powershell.exe -Command, which works robustly.
Caveat: "^"" does not work for calling other programs.
This saves you from additional escaping, as would be necessary if you used \"
Example: powershell -command " Write-Output "^""a & b"^"" " yields a & b, as expected, and the & didn't need escaping.
If you use the simpler - and customary - \", you may need to perform additional escaping: Specifically, you must individually ^-escape the following cmd.exe metacharacters with ^ inside \"...\" runs: & | < > ^Thanks, LotPings.
Example: powershell -command " Write-Output \"a ^& b\" " yields a & b; that is, the & needed escaping with ^.
Additionally, to treat % (and, with enabledelayedexpansion , !) verbatim, the escaping syntax unfortunately depends on whether you're calling from the command line or a batch file: use %^USERNAME% (!^USERNAME) from the former, and %%USERNAME%% (^!USERNAME^! / ^^!USERNAME^^! inside \"...\" runs) from the latter - see this answer for the gory details.
It is unfortunate that cmd.exe makes use of \" treacherous, given that it is supported by virtually all programs (except batch files), and if it weren't for these extra escaping requirements, command lines that use it have the potential to work across different platforms and shells - with the notable exception of calling from PowerShell, where, sadly, an additional layer of escaping is needed and " inside "..." must be escaped as \`" (sic); see this answer.
See the bottom section for ways to ease the escaping pain by avoiding use of nested ".
Other programs, including PowerShell Core:
Use just "" for programs compiled with Microsoft compilers and, on Windows, also Python and Node.js as well as PowerShell Core (pwsh.exe).
Regrettably, this robust option does not work with powershell.exe, i.e. Windows PowerShell.
Use \" for programs with Unix heritage, such as Perl and Ruby - which comes with the escaping headaches discussed above.
Avoiding embedded ":
When you call PowerShell's CLI, you can often get away without needing to embed double quotes:
There may be arguments in your string that don't require quoting at all, such as text1.txt and text2.txt
You can alternatively use single-quoting ('...') inside the overall command string, which require no escaping; note that such strings, from PowerShell's perspective, are string literals.
To put it all together:
powershell -Command "Compare-Object (Get-Content tex1.txt) (Get-Content tex2.txt) | Where-Object {$_.SideIndicator -eq '<='} | select inputobject | ft -hidetableheaders"
Note that I've also removed the & { ... } around your command, as it isn't necessary.
[1] eryksun puts it as follows: "This is the inescapable frustration of the Windows command line. Every program parses its own command line, using whatever rules it wants. So the syntax of a command line has to work with not only the shell (CMD) but also all programs invoked in the pipeline. In the Unix world the shell parses the command line into argv arrays, so typically you only have to get the syntax right to make the shell happy."
The problems with PowerShell Core, even on Unix, stem from how it re-quotes arguments behind the scenes before passing them on - see this GitHub docs issue.
I'm trying to run a PowerShell script from Windows cmd.exe. The input to the PowerShell script is a string, which contains newline characters using PowerShell backtick escaping - i.e:
`r`n
For demonstration purposes, the input string is then written to the console, and also dumped to a file.
The issue I have is that when the script is run from cmd.exe using the syntax
powershell.exe script.ps1 "TEST`r`nTEST"
The newline characters in the string are not treated as newline, and are included literally in both the console output and the output text file.
TEST`r`nTEST
However, if I run this from a PowerShell environment, I get the expected result (i.e. the newline characters are parsed correctly, and a newline is inserted in the appropriate location).
TEST
TEST
Similarly, if I pass in \r\n instead of the escaped newline characters through Windows cmd.exe, and do a .replace in the PowerShell script
$date = $data.replace("\r\n","`r`n")
I get the expected output:
TEST
TEST
Is anyone able to shed some light on why this happens?
The test script is as follows:
param([string]$data) # data to send
Write-Host $data
[IO.File]::WriteAllText('d:\temp.txt', $data)
return 0
And the file is called from the command line as:
powershell.exe script.ps1 "TEST`r`nTEST"
The script is running on Windows Server 2012 R2, using PowerShell v4.0
tl;dr
Use -Command and pass the entire PowerShell command as a single string; e.g.:
C:\> powershell -NoProfile -Command "script.ps1 \"TEST`r`nTEST\""
TEST
TEST
Note how the internal " instances are escaped as \", which PowerShell requires when called from the outside (alternatively, for full robustness, use "^"" (sic) in Windows PowerShell and "" in PowerShell (Core) v6+).
In your specific case,
powershell -NoProfile -Command script.ps1 "TEST`r`nTEST" would have worked too, but generally that only works as intended if the string has no embedded spaces.
Given that -Command is the default up to PSv5.1, your command - as currently posted - should work as-is.
As of PowerShell v5.1, arguments passed to powershell.exe from the outside:
ARE subject to interpretation by PowerShell, including string interpolation, by default and when you use -Command (i.e., specifying neither -File nor -Command currently defaults to -Command).
Caveat: The default behavior will change in v6: -File will be the default then - see the relevant change on GitHub.
are NOT subject to interpretation if you use -File to invoke a script - (after potential interpretation by cmd.exe) PowerShell treats all arguments as literal strings.
Caveat: This behavior is currently being discussed with respect to v6, given that it is overtly problematic in at least one case: trying to pass Boolean values.
Optional reading: Why you should pass the entire PowerShell command as a single argument when using -Command:
When you use -Command with multiple arguments, PowerShell essentially assembles them into a single command line behind the scenes before executing it.
Any "..."-quoting around the individual arguments is lost in the process, which can have unexpected results; e.g.:
C:\> powershell -NoProfile -Command "& { $args.count }" "line 1`r`nline 2"
3 # !! "line 1`r`nline 2" was broken into 3 arguments
Given that the outer "..." quoting was removed in the process of parsing the command line, the actual command line that PowerShell ended up executing was:
C:\ PS> & { $args.Count } line 1`r`nline 2
3
To illustrate why, let's look at an equivalent command that uses explicit quoting:
C:\ PS> & { $args.Count } "line" "1`r`nline" "2"
In other words: After the enclosing " were removed, the resulting token was broken into multiple arguments by spaces, as usual.
The parameter will need to be reinterpreted as a PowerShell string. Will this get you down the road?
The reason your -replace did not work is that the original string actually contains a backtick. It needs to be escaped in the search string.
C:\src\t>type p1.ps1
Param([string]$s)
Write-Host $s
$p = Invoke-Expression `"$s`"
Write-Host $p
$p2 = $s -replace "``r``n","`r`n"
Write-Host $p2
C:\src\t>powershell -noprofile -file .\p1.ps1 "TEST`r`nTEST"
TEST`r`nTEST
TEST
TEST
TEST
TEST
Carriage return and Linefeed are bytes with values 13 and 10, you can't write them, you can't see them.
As a convenience, when writing PowerShell code, the language will let you write:
"`r`n"
in a double quoted string, and when processing PowerShell source code (and at no other time), it will read those and replace them with bytes value 13 and 10.
It is this line of code in the PowerShell tokenizer which does it.
There is nothing special about backtick-n to the cmd.exe interpreter, and nothing special about having it in a string - you can put it there in a single quoted string
'`n'
or replacing it in a string - except that you have to note when the replacement happens. e.g. in your comment:
For example, if you pass in 'r'n and then replace 'r'n with 'r'n, the 'r'n is still output literally
Because your code
-replace "`r`n"
becomes
-replace "[char]13[char]10"
and your string passed in from outside contains
`r`n
and they don't match. Backtick-n in a string isn't magic, strings are not all interpreted by the PowerShell engine as PowerShell code, nor are parameters, or anything. And it's only in that context - when you write your -replace code, that is when the swap for actual newline characters happens.
I have a script like this:
param(
[string]$root,
[string]$bin,
[string]$out,
[string]$zdir
)
echo "args..."
echo "Root: $root", "zdir: $zdir", "out: $out", "bin: $bin"
I invoke it like follows:
powershell.exe -nologo -noprofile -file "C:\Users\arun_jayapal\Documents\Visual Studio 2013\Projects\OutlookCompass\OutlookCompass\zip.ps1" -root "C:\Users\arun_jayapal\Documents\Visual Studio 2013\Projects\OutlookCompass\OutlookCompass\" -zdir "Zip" -out "output.zip" -bin "C:\Users\arun_jayapal\Documents\Visual Studio 2013\Projects\OutlookCompass\OutlookCompass\bin\Debug\"
But my output is quite to the contrary:
C:\code\misc>powershell.exe -nologo -noprofile -file "C:\Users\arun_jayapal\Docu
ments\Visual Studio 2013\Projects\OutlookCompass\OutlookCompass\zip.ps1" -root "
C:\Users\arun_jayapal\Documents\Visual Studio 2013\Projects\OutlookCompass\Outlo
okCompass\" -zdir "Zip" -out "output.zip" -bin "C:\Users\arun_jayapal\Documents\
Visual Studio 2013\Projects\OutlookCompass\OutlookCompass\bin\Debug\"
args...
Root: C:\Users\arun_jayapal\Documents\Visual Studio 2013\Projects\OutlookCompas
s\OutlookCompass" -zdir Zip -out output.zip -bin C:\Users\arun_jayapal\Document
s\Visual
zdir:
out: 2013\Projects\OutlookCompass\OutlookCompass\bin\Debug"
bin: Studio
Your problem is that you are invoking your powershell script from cmd.exe, and for many legacy .exe programs the sequence \" is used to escape a quote into a parameter. Powershell respects this convention so when you invoke Powershell.exe you have to follow this escaping convention.
So:
-root "something\" -zdir "Zip" -out "output.zip" -bin "something\"
Is a single argument containing two double quotes escaped into the string with \". cmd removes the other double quotes, so Zip and output.zip are strings outside the quotes, but as they don't contain any spaces they don't split the argument.
This should work:
-root "something\\" -zdir "Zip" -out "output.zip" -bin "something\\"
Doubling the backslashes before the quotes mean a single backslash is passed through and the quote mark loses its special meaning. Don't double any of the other backslashes though as cmd only regards the backslash as special when a sequence of one or more of them precede a double quote.
Alternatively leave off the trailing backslashes and insert them where needed in your script.
Or just use powershell itself as your main command prompt and ditch cmd altogether.
BTW, if you invoke another copy of powershell from inside itself it is recognised as something special and the arguments are encoded in base64 and passed with the -EncodedCommand command-line option so in that case there is no need to worry about escaped quotes.
Update: The problem still affects Windows PowerShell, but has been fixed in PowerShell (Core) 7+.
Duncan's helpful answer contains the crucial pointer (and helpful background information as well): \" at the end of the parameter values is interpreted as an escaped ", which causes parameter boundaries to be misinterpreted.
Your options are:
Use \\" instead of \" at the end of each parameter value, as Duncan suggests (e.g., "c:\foo bar\\")
Omit the trailing \ from the paths altogether (e.g., "c:\foo bar" instead of "c:\foo bar\")
If you want to avoid having to modify the parameter values, use the following, single-quoted solution (verified on v3).
Single-quoted solution:
This assumes that you're calling this from cmd.exe, whether from a batch file or at the command prompt (inside PowerShell, you could just use & <script> ...)
use single quotes instead of double quotes
use the -Command parameter instead of -File
prefix the script path with & (escaped with ^ to have cmd.exe treat it as a literal)
powershell.exe -noprofile -command ^& 'C:\Users\arun_jayapal\Documents\Visual Studio 2013\Projects\OutlookCompass\OutlookCompass\zip.ps1' -root 'C:\Users\arun_jayapal\Documents\Visual Studio 2013\Projects\OutlookCompass\OutlookCompass\' -zdir 'Zip' -out 'output.zip' -bin 'C:\Users\arun_jayapal\Documents\Visual Studio 2013\Projects\OutlookCompass\OutlookCompass\bin\Debug\'
This works, because by using -Command (and PowerShell's & (call) operator to invoke a script whose path has embedded spaces), the rest of the command line can be quoted PowerShell-style with single-quoted strings that aren't subject to interpretation (not even by the C-style argument parsing that underlies PowerShell's own startup command-line parsing, with the exception of embedded " chars. - see below).
The only characters that need escaping with this approach are:
Use '' to embed a literal ' inside a parameter.
Use \" to embed a literal " inside a parameter.
If you need to escape % chars. to protect them from interpretation by cmd.exe as part of environment-variable references (which you'd have to do irrespective of whether you use single- or double-quoted strings), see this answer of mine.
Note that in order to execute a script in the current directory you must .\-prefix the script filename, as you would inside PowerShell.