Powershell in Jenkins escaping characters for path - powershell

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.

Related

Nested Quoted Strings in Powershell

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

Powershell inline commands on command line, quotation marks and new line

I have a powershell script that takes a parameter from the command line.
The passed parameter is a string with new line characters but I cannot get it to work as I wish.
Example running from command line:
powershell.exe -command "& {& 'C:\My Scripts\Script.ps1' "Some Text`nWoo Hoo"}"
The script above is only passed the text Some. If I use single quotes around the passed text the `n newline character is interpreted literally, not as a newline.
I'm sure there is some way to solve this!
thanks
Since your overall command string is (of necessity when calling from cmd.exe) double-quoted, you must escape the embedded double quotes as \" (even though inside PowerShell it is ` that serves as the escape character, as used in the embedded `n escape sequence):
# From cmd.exe
powershell.exe -command "& 'C:\My Scripts\Script.ps1' \"Some Text`nWoo Hoo\""
Also note that there's never a need to use -Command "& { ... }" when calling the PowerShell CLI - just -Command "..." is sufficient.
In your case, embedded double-quoting (\"...\"), i.e. use of an (escaped) expandable string is a must in order to get interpolation of the `n escape sequence to an actual newline.
In cases where a verbatim (literal) string is sufficient, you can bypass the need to escape by using single-quoting ('...').
See the bottom section of this answer for more information about string literals in PowerShell.

Powershell Parameter Seems to Truncate Value

I have a Powershell script that runs fine in VSCode but from the Powershell Prompt, I'm getting an error. Below is the output.
→ C:\WINDOWS\system32› powershell.exe -file 'D:\Source\Repos\Powershell Scripts\SD-Report-Archive.ps1' -sourcePath 'D:\Archives\' -targetPath 'D:\Archives2\'
D:\Archives\
Get-ChildItem : Cannot find path 'D:\A' because it does not exist.
At D:\Source\Repos\Powershell Scripts\SD-Report-Archive.ps1:25 char:14
+ $files = Get-ChildItem -Recurse -File -Path $sourcePath
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (D:\A:String) [Get-ChildItem], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
As you can see on the 2nd line of the output I do a Write-Output of the parameter value that I am sending in and it's correct. When I execute Get-ChildItem it seems to truncate the value to 'D:\A' and I don't know why.
Param(
[Parameter(Mandatory = $true)]
[string]$sourcePath,
[Parameter(Mandatory = $true)]
[string]$targetPath
)
function Copy-FilesIntoFolders {
param()
Write-Output $sourcePath;
$files = Get-ChildItem -Path $sourcePath -Recurse -File
...
}
On Windows, PowerShell - of necessity - rebuilds the command line in order to invoke external programs.
Notably, most external programs don't understand single-quoted strings ('...') via their CLI, so after having performed its own parsing, PowerShell re-quotes the resulting (stringified) arguments using double quotes ("...") if it deems that necessary.
Unfortunately, this re-quoting is broken in several respects:
If the argument value doesn't contain spaces, no quoting is applied. Values without spaces but with special characters may therefore break commands, especially when another shell, such as cmd.exe is invoked.
E.g., cmd /c echo 'a&b' breaks, because a&b is ultimately passed without quotes, and & has special meaning in cmd.exe
If the argument has embedded double quotes (" chars.), the re-quoting does not automatically escape them for syntactically correct embedding inside "..." or unquoted literal use:
E.g., foo.exe 'Nat "King" Cole' is translated to foo.exe "Nat "King" Cole" - note the lack of escaping of the inner " chars. - which results in a different string when parsed by most applications, namely Nat King Cole (no double quotes).
You have to perform escaping manually, in addition to PowerShell's own escaping requirements, if applicable: foo.exe 'Nat \"King\" Cole' or, with double-quoting, foo.exe "Nat \`"King\`" Cole" (sic).
Similarly - as in your case - if the argument has spaces and ends in \, that trailing \ is not escaped in the resulting double-quoted string, which breaks the argument syntax:
E.g., foo.exe 'a b\' c becomes foo.exe "a b\" c - however, most programs - including PowerShell's own CLI - interpret the \" as an escaped " char. rather than the closing double quote, resulting in misinterpretation of the argument, merging it with the next argument to result in a b" c
Again you have to perform escaping manually, by doubling the \: foo.exe 'a b\\' c
Alternatively, if the argument happens to be a directory path whose trailing \ is optional, simply omit the latter.
Get-ChildItem : Cannot find path 'D:\A' because it does not exist.
The backslash (\) here looks like an escape character to me. Please try with \\

Passing newline character to PowerShell via Cmd

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.

passing parameters to script from powershell.exe

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.