带有嵌套引号的 Powershell 调用 msbuild

Posted

技术标签:

【中文标题】带有嵌套引号的 Powershell 调用 msbuild【英文标题】:Powershell call msbuild with nested quotation marks 【发布时间】:2011-09-07 15:41:02 【问题描述】:

使用 Powershell 和 Psake 为 Visual Studio 解决方案创建包和部署。 尝试使用 msbuild 部署数据库项目 - 使用 msdos Visual Studio 命令行可以正常工作

   msbuild /target:Deploy /p:UseSandboxSettings=false /p:TargetConnectionString="aConnectionWithSpacesAndSemiColons" "aDatabaseProjectPathWithSpaces"

从powershell调用相同的方法调用会导致错误

& msbuild /target:Deploy /p:UseSandboxSettings=false /p:TargetConnectionString="aConnectionWithSpacesAndSemiColons" "aDatabaseProjectPathWithSpaces"

与空格有关 - 无法弄清楚如何在 powershell 中复制此调用 - 示例数据库连接字符串 数据源=.\SQL2008;初始目录=DocumentExecution;集成安全=True;

【问题讨论】:

【参考方案1】:

短版

如何将包含引号的参数从 PowerShell 传递到本机命令?

在参数字符串中使用单引号而不是双引号:<b>"</b>/p:Target=<b>'</b>Data Source=(local)\SQL;Integrated Security=True<b>'"</b>/p:Target=<b>'</b>Data Source=(local)\SQL;Integrated Security=True<b>'</b>

对参数字符串中的双引号使用反斜杠转义:<b>'</b>/p:Target=<b>\"</b>Data Source=(local)\SQL;Integrated Security=True<b>\"'</b>/p:Target=<b>"</b>Data Source=(local)\SQL;Integrated Security=True<b>"</b>

如果嵌入引号仅用于将参数视为单个字符串,而不是参数的必需部分,则可以使用以下内容:

引用整个参数字符串,而不是在参数中嵌入引号:<b>'</b>/p:Target=Data Source=(local)\SQL;Integrated Security=True<b>'</b>/p:Target=Data Source=(local)\SQL;Integrated Security=True

使用反引号转义所有 PowerShell 特殊字符(这只能作为内联参数完成):/p:Target=<b>`"</b>Data Source=<b>`(</b>local<b>`)</b>\SQL<b>`;</b>Integrated Security=True<b>`"</b>/p:Target=Data<b>` </b>Source=<b>`(</b>local<b>`)</b>\SQL<b>`;</b>Integrated<b>` </b>Security=True/p:Target=Data<b> </b>Source=<b>(</b>local<b>)</b>\SQL<b>;</b>Integrated<b> </b>Security=True

完整的命令行示例(使用第二种选择):

PS> [string[]]$arguments = @(
  '/target:Deploy',
  '/p:UseSandboxSettings=False',
  '/p:TargetDatabase=UpdatedTargetDatabase',
  '/p:TargetConnectionString=\"Data Source=(local)\SQL;Integrate Security=True\"',
  'C:\program files\MyProjectName.dbproj'
)
PS> ./echoargs $arguments
Arg 0 是 Arg 1 是 =False> Arg 2 是 =UpdatedTargetDatabase> Arg 3 是 ="Data Source=(local)\SQL;Integrate Security=True"> Arg 4 是


加长版

当人们在旧的 cmd 系统和 PowerShell 之间移动时,调用本机命令会出现很多情况(几乎与“用逗号分隔参数”的陷阱一样多;)。

我已经尝试并总结了我所知道的关于 PowerShell(v2 和 v3)中的命令调用主题的所有内容,以及我能收集到的所有示例和参考资料。

1) 直接调用原生命令

1.1) 最简单的情况是,对于位于环境路径中的可执行文件,可以直接调用该命令,就像调用 PowerShell cmdlet 一样。 p>

PS> Get-ItemProperty echoargs.exe -Name IsReadOnly
...
IsReadOnly   : True    

PS> attrib echoargs.exe
A    R       C:\Users\Emperor XLII\EchoArgs.exe

1.2) 在环境路径之外,对于特定目录(包括当前目录)中的命令,可以使用命令的完整路径或相对路径强>。这个想法是让操作员明确声明“我想调用 this 文件”,而不是让一个恰好具有相同名称的任意文件在其位置运行 (see this question for more info on PowerShell security)。在需要时未能使用路径将导致“无法识别术语”错误。

PS> echoargs arg
The term 'echoargs' is not recognized as the name of a cmdlet, function, script file,
 or operable program...

PS> ./echoargs arg
Arg 0 is <arg>

PS> C:\Windows\system32\attrib.exe echoargs.exe
A    R       C:\Users\Emperor XLII\EchoArgs.exe

1.3) 如果路径包含特殊字符,可以使用调用运算符或转义字符。例如,可执行文件以数字开头,或位于包含空格的目录中。

PS> $env:Path
...;C:\tools\;...

PS> Copy-Item EchoArgs.exe C:\tools\5pecialCharacter.exe
PS> 5pecialCharacter.exe special character
Bad numeric constant: 5.

PS> & 5pecialCharacter.exe special character
Arg 0 is <special>
Arg 1 is <character>

PS> `5pecialCharacter.exe escaped` character
Arg 0 is <escaped character>


PS> C:\Users\Emperor XLII\EchoArgs.exe path with spaces
The term 'C:\Users\Emperor' is not recognized as the name of a cmdlet, function,
 script file, or operable program...

PS> & 'C:\Users\Emperor XLII\EchoArgs.exe' path with spaces
Arg 0 is <path>
Arg 1 is <with>
Arg 2 is <spaces>

PS> C:\Users\Emperor` XLII\EchoArgs.exe escaped` path with` spaces
Arg 0 is <escaped path>
Arg 1 is <with spaces>

2) 间接调用原生命令

2.1) 当您不以交互方式输入命令,而是将路径存储在变量中时,调用运算符也可用于调用在变量中命名的命令

PS> $command = 'C:\Users\Emperor XLII\EchoArgs.exe'
PS> $command arg
Unexpected token 'arg' in expression or statement.

PS> & $command arg
Arg 0 is <arg>

2.2) 传递给命令的参数也可以存储在变量中。 变量中的参数可以单独传递,也可以在数组中传递。 对于包含空格的变量,PowerShell 将自动转义空格,以便本机命令将其视为单个参数。 (请注意,调用运算符将​​第一个值视为命令,其余值视为参数;参数不应与命令变量组合。)

PS> $singleArg = 'single arg'
PS> $mushedCommand = "$command $singleArg"
PS> $mushedCommand
C:\Users\Emperor XLII\EchoArgs.exe single arg

PS> & $mushedCommand
The term 'C:\Users\Emperor XLII\EchoArgs.exe single arg' is not recognized as the
 name of a cmdlet, function, script file, or operable program...

PS> & $command $singleArg
Arg 0 is <single arg>

PS> $multipleArgs = 'multiple','args'
PS> & $command $multipleArgs
Arg 0 is <multiple>
Arg 1 is <args>

2.3) 数组格式对于为本地命令构建动态参数列表特别有用。 对于要识别为不同参数的每个参数,重要的是参数被存储在一个数组变量中,而不仅仅是在一个字符串中组合在一起。 (注意,常用缩写$args是PowerShell中的自动变量,可能会导致其中保存的值被覆盖;相反,最好使用$msbuildArgs这样的描述性名称以避免命名冲突。)

PS> $mungedArguments = 'initial argument'
PS> $mungedArguments += 'second argument'
PS> $mungedArguments += $(if( $someVariable )  'dynamic A'  else  'dynamic B' )
PS> ./echoargs $mungedArguments
Arg 0 is <initial argumentsecond argumentdynamic B>

PS> $arrayArguments = @('initial argument')
PS> $arrayArguments += 'second argument'
PS> $arrayArguments += $(if( $someVariable )  'dynamic A'  else  'dynamic B' )
PS> ./echoargs $arrayArguments
Arg 0 is <initial argument>
Arg 1 is <second argument>
Arg 2 is <dynamic B>

2.4) 此外,对于脚本、函数、cmdlet 等,PowerShell v2 可以使用称为“喷溅”的技术发送包含在哈希表中的命名参数,而无需担心关于参数顺序。这不适用于不参与 PowerShell 对象模型且只能处理字符串值的本机命令。

PS> $cmdletArgs = @ Path = 'EchoArgs.exe'; Name = 'IsReadOnly' 
PS> $cmdlet = 'Get-ItemProperty'
PS> & $cmdlet $cmdletArgs     # hashtable object passed to cmdlet
Cannot find path 'C:\Users\Emperor XLII\System.Collections.Hashtable'...

PS> & $cmdlet @cmdletArgs     # hashtable values passed to cmdlet
...
IsReadOnly   : True

PS> ./echoargs @cmdletArgs
Arg 0 is <Name>
Arg 1 is <IsReadOnly>
Arg 2 is <Path>
Arg 3 is <EchoArgs.exe>

3) 使用复杂参数调用本机命令

3.1) 对于简单的参数,用于本机命令的自动转义通常就足够了。但是,对于括号、美元符号、空格等,PowerShell 使用的字符需要转义才能按原样发送到本机命令,而不需要解析器对其进行解释。这可以使用反引号转义字符 ` 或将参数放在单引号字符串中来完成。

PS> ./echoargs money=$10.00
Arg 0 is <money=.00>

PS> ./echoargs money=`$10.00
Arg 0 is <money=$10.00>


PS> ./echoargs value=(spaces and parenthesis)
The term 'spaces' is not recognized as the name of a cmdlet, function, script file,
 or operable program...

PS> ./echoargs 'value=(spaces and parenthesis)'
Arg 0 is <value=(spaces and parenthesis)>

3.2) 不幸的是,当涉及双引号时,这并不是那么简单。作为本机命令的参数处理的一部分,PowerShell 处理器尝试对参数中的所有双引号进行规范化,以便将无引号的参数内容作为单个值传递给本机命令。本机命令参数处理在解析后作为单独的步骤发生,因此 正常转义对双引号不起作用;只能使用转义的单引号或反斜杠转义的双引号

PS> ./echoargs value="double quotes"
Arg 0 is <value=double quotes>

PS> ./echoargs 'value="string double quotes"'
Arg 0 is <value=string>
Arg 1 is <double>
Arg 2 is <quotes>

PS> ./echoargs value=`"escaped double quotes`"
Arg 0 is <value=escaped double quotes>

PS> ./echoargs 'value=\"backslash escaped double quotes\"'
Arg 0 is <value="backslash escaped double quotes">


PS> ./echoargs value='single quotes'
Arg 0 is <value=single quotes>

PS> ./echoargs "value='string single quotes'"
Arg 0 is <value='string single quotes'>

PS> ./echoargs value=`'escaped` single` quotes`'
Arg 0 is <value='escaped single quotes'>

3.3) PowerShell v3 添加了一个新的停止解析符号--%(请参阅about_Parsing)。在复杂参数之前使用时,--% 将按原样传递参数,无需任何解析或变量扩展,除了类似 cmd 的 %ENVIRONMENT_VARIABLE%

PS> ./echoargs User:"$env:UserName" "Hash"#555
Arg 0 is <User:Emperor XLII>
Arg 1 is <Hash>

PS> ./echoargs User: "$env:UserName" --% "Hash"#555
Arg 0 is <User:Emperor XLII>
Arg 1 is <Hash#555>

PS> ./echoargs --% User: "%USERNAME%" "Hash"#555
Arg 0 is <User:Emperor XLII>
Arg 1 is <Hash#555>

这也可用于取消表示多个参数的单个字符串,方法是在字符串中传递停止解析符号(尽管最佳做法是不要在第一名)。

PS> $user = 'User:"%USERNAME%"'
PS> $hash = 'Hash#' + $hashNumber
PS> $mungedArguments = $user,$hash -join ' '
PS> ./echoargs $mungedArguments
Arg 0 is <User:%USERNAME% Hash#555>

PS> ./echoargs --% $mungedArguments
Arg 0 is <$mungedArguments>

PS> ./echoargs '--%' $mungedArguments
Arg 0 is <User:Emperor XLII>
Arg 1 is <Hash#555>

4) 调试原生命令

有两个关键工具可用于调试 PowerShell 传递给本机命令的参数。

4.1) 第一个是 EchoArgs.exe,这是一个来自 PowerShell Community Extensions 的控制台应用程序,它只是在尖括号之间写回传递给它的参数(如图所示上面的例子)。

4.2) 第二个是Trace-Command,一个可以显示PowerShell如何处理管道的许多细节的cmdlet。特别是,NativeCommandParameterBinder 跟踪源将显示 PowerShell 接收并传递给本机命令的内容。

PS> Trace-Command *NativeCommand*  ./echoargs value="double quotes"  -PSHost
DEBUG: NativeCommandParameterBinder : Raw argument string:  "value=double quotes"
DEBUG: NativeCommandParameterBinder : Argument 0: value=double quotes

PS> Trace-Command *NativeCommand*  ./echoargs 'value="double quotes"'  -PSHost
DEBUG: NativeCommandParameterBinder : Raw argument string:  "value="double quotes""
DEBUG: NativeCommandParameterBinder : Argument 0: value=double
DEBUG: NativeCommandParameterBinder : Argument 1: quotes

PS> Trace-Command *NativeCommand*  ./echoargs value=`"double quotes`"  -PSHost
DEBUG: NativeCommandParameterBinder : Raw argument string:  value="double quotes"
DEBUG: NativeCommandParameterBinder : Argument 0: value=double quotes

PS> Trace-Command *NativeCommand*  ./echoargs 'value=\"double quotes\"'  -PSHost
DEBUG: NativeCommandParameterBinder : Raw argument string:  "value=\"double quotes\""
DEBUG: NativeCommandParameterBinder : Argument 0: value="double quotes"

其他资源

文章

2012-01-02 - PowerShell V3 CTP2 Provides Better Argument Passing to EXEs 2011-03-10 - The problem with calling legacy/native apps from PowerShell 2010-11-04 - Escaping Spaces 2010-02-01 - The trials and tribulations of using MSDeploy with PowerShell 2008-10-17 - Executing commands which require quotes and variables is practically impossible [连接] 2006-05-15 - Cannot enter an argument containing double quotes [连接]

问题

2013-09-11 - powershell executing external command not accepting parameter 2013-02-20 - Parameters with double quotes are not properly passed to Scriptblock by ArgumentList 2013-01-02 - ERROR: Description = Invalid query   2012-09-18 - How can I execute an external program with parameters in PowerShell? 2012-09-10 - Invoke executable (w/parameters) from powershell script 2012-08-16 - How do I pass a property value containing a semicolon on the MSBuild command line when running it from PowerShell? 2012-08-08 - Call ruby script from powershell 2012-08-01 - Brackets or quotation marks breaking a powershell command 2012-07-13 - Problems using powershell to perform a source safe get by label 2012-06-13 - Missing argument -m using svn at windows powershell 2012-06-01 - Powershell command line argument with spaces and curly brackets? 2012-04-18 - Spaced paths, msbuild, and psake 2012-04-12 - Make Power shell ignore semicolon 2012-03-08 - Simple Powershell Msbuild with parameter fails 2012-02-10 - Quote Madness in Powershell 2012-01-18 - Powershell: run msiexec with dynamically created parameters 2012-01-18 - PowerShell's call operator (&) syntax and double-quotes 2012-01-16 - PowerShell - passing calculated paths with spaces 2012-01-09 - powershell: script to start a program with parameters?   2011-12-20 - Powershell - calling icacls with parantheses included in parameters 2011-12-15 - Msbuild log doesn't work when executed through powershell 2011-12-06 - Pass parameters to InstallUtil from Powershell 2011-11-23 - Executing an exe with arguments using Powershell 2011-11-08 - Powershell remove quotes when start process 2011-09-16 - Commands executed in PowerShell with variables surrounded in quotes fail. Why? 2011-07-25 - Powershell parsing quotes strangely(其中一个答案包括 a short analysis of quote parsing) 2011-07-15 - powershell stripping double quotes from command line arguments 2011-06-14 - In Powershell, how do you execute an arbitrary native command from a string? 2011-06-03 - Powershell call msbuild with nested quotation marks 2011-05-13 - powershell - passing parameters to exe 2011-03-02 - Why does this PowerShell script fail to execute this external command properly? 2011-01-09 - Executing an EXE file using powershell script   2010-12-13 - Command-line arguments to an exe 2010-10-08 - What is up with this PowerShell command line quoting/escaping? 2010-10-05 - Running an exe using powershell from a directory with spaces in it 2010-08-28 - Executing a Command stored in a Variable from Powershell 2010-08-17 - How do you call msdeploy from powershell when the parameters have spaces? 2010-04-12 - How to suppress quotes in Powershell commands to executables 2010-01-26 - powershell sending multiple parameter to a external command   2009-11-04 - How to run exe in powershell with parameters with spaces and quotes 2009-03-16 - PowerShell - Start-Process and Cmdline Switches 2009-01-14 - How to escape command line arguments on Powershell?

【讨论】:

rkeithhill.wordpress.com/2012/01/02/…【参考方案2】:

如果您使用带有 -ArgumentList 参数的 Start-Process cmdlet,这一切都会变得容易得多。我很惊讶这还没有被提及。

例子:

Start-Process -FilePath msbuild.exe -ArgumentList '/target:Deploy /p:UseSandboxSettings=false /p:TargetConnectionString="aConnectionWithSpacesAndSemiColons" "aDatabaseProjectPathWithSpaces"';

这是我比较喜欢使用的一种方法,它允许变量替换:

$ConnectionString = 'aConnectionWithSpacesAndSemiColons';
$DatabaseProjectPath = 'aDatabaseProjectPathWithSpaces';
$MsbuildArguments = '/target:Deploy /p:UseSandboxSettings=false /p:TargetConnectionString="0" "1"' -f $ConnectionString, $DatabaseProjectPath;
Start-Process -FilePath msbuild.exe -ArgumentList $MsbuildArguments;

【讨论】:

【参考方案3】:

将整个参数放在单引号中:

& msbuild /target:Deploy /p:UseSandboxSettings=false '/p:TargetConnectionString="aConnectionWithSpacesAndSemiColons"' "aDatabaseProjectPathWithSpaces"

额外的引用级别意味着 PSH 不会按照 PSH 的规则处理内容。 (字符串中的任何单引号都需要加倍——这是 PSH 单引号字符串中唯一的转义类型。

【讨论】:

【参考方案4】:

@Richard - 测试这会产生一个不同的错误,指出没有提供有效的项目文件。 我已经通过 echoargs pscx helper 运行它以显示一些更详细的示例。

    使用单引号包裹 TargetConnectionString - Powershell 将连接字符串中的每个空格评估为新行:

    & echoargs /target:Deploy /p:UseSandboxSettings=false    /p:TargetDatabase=UpdatedTargetDatabase /p:TargetConnectionString='"Data Source=(local)\SQLEXPRESS;Integrated Security=True;Pooling=False"' "C:\program files\MyProjectName.dbproj"
    
    Arg 0 is </target:Deploy>
    Arg 1 is </p:UseSandboxSettings=false>
    Arg 2 is </p:TargetDatabase=UpdatedTargetDatabase>
    Arg 3 is </p:TargetConnectionString=Data>
    Arg 4 is <Source=(local)\SQLEXPRESS;Integrated>
    Arg 5 is <Security=True;Pooling=False>
    Arg 6 is <C:\program files\MyProjectName.dbproj>
    

    用反引号分隔每个参数会重新创建初始问题 = 连接字符串周围没有引号:

    & echoargs /target:Deploy `
    /p:UseSandboxSettings=false `
    

    c /p:TargetConnectionString="Data Source=(local)\SQLEXPRESS;Integrated Security=True;Pooling=False" "C:\程序文件\MyProjectName.dbproj"

    Arg 0 is </target:Deploy>
    Arg 1 is </p:UseSandboxSettings=false>
    Arg 2 is </p:TargetDatabase=UpdatedTargetDatabase>
    Arg 3 is </p:TargetConnectionString=Data Source=(local)\SQLEXPRESS;Integrated Se
    curity=True;Pooling=False>
    Arg 4 is <C:\program files\MyProjectName.dbproj>
    

    在引号中添加反引号的行为与示例 1 相同:

    & echoargs /target:Deploy `
    /p:UseSandboxSettings=false `
    /p:TargetDatabase=UpdatedTargetDatabase `
    "/p:TargetConnectionString=`"Data Source=(local)\SQLEXPRESS;Integrated Security=True;Pooling=False"`"  `
    "C:\program files\MyProjectName.dbproj"
    

    使用@运算符尝试拆分参数仍然会忽略引号:

    $args = @('/target:Deploy','/p:UseSandboxSettings=false','     /p:TargetDatabase=UpdatedTargetDatabase','/p:TargetConnectionString="Data Source=(local)\SQLEXPRESS;Integrated Security=True;Pooling=False"','C:\program files\MyProjectName.dbproj'); $args 
    
    /target:Deploy
    /p:UseSandboxSettings=false
    /p:TargetDatabase=UpdatedTargetDatabase
    /p:TargetConnectionString="Data Source=(local)\SQLEXPRESS;Integrated           Security=True;Pooling=False"
    C:\program files\MyProjectName.dbproj
    
    & echoargs $args
    

    使用行分隔符转义连接字符串的反引号 - 结果与示例 1 相同:

    & echoargs /target:Deploy `
    /p:UseSandboxSettings=false `
    /p:TargetDatabase=UpdatedTargetDatabase `
    "/p:TargetConnectionString=`"Data Source=(local)\SQLEXPRESS;Integrated Security=True;Pooling=False"`" `
    "C:\program files\MyProjectName.dbproj"
    

【讨论】:

我很幸运in the past 使用了您的示例 4,并且在我的机器上进行的快速测试保留了引号。变量$args在powershell中有特殊含义;你可以试试其他名字,比如$msbuildArgs【参考方案5】:

您的问题是 PowerShell 在将引号传递给命令行应用程序时不会对其进行转义。我自己遇到了这个问题,并认为 PowerShell 正在吃引号。就这样做吧。

msbuild /target:Deploy /p:UseSandboxSettings=false /p:TargetDatabase=UpdatedTargetDatabase '/p:TargetConnectionString=\"Data Source=(local)\SQLEXPRESS;Integrated Security=True;Pooling=False\"' "C:\program files\MyProjectName.dbproj"

【讨论】:

差不多,但是这会在 TargetConnectionString 参数中嵌入双引号,这会破坏部署步骤。【参考方案6】:

多亏了 JohnF 的回答,我终于能够弄清楚这一点。

echoargs /target:clean`;build`;deploy /p:UseSandboxSettings=false /p:TargetConnectionString=`"Data
Source=.`;Integrated Security=True`;Pooling=False`" .\MyProj.dbproj
Arg 0 is </target:clean;build;deploy>
Arg 1 is </p:UseSandboxSettings=false>
Arg 2 is </p:TargetConnectionString=Data Source=.;Integrated Security=True;Pooling=False>
Arg 3 is <.\MyProj.dbproj>

简而言之,但在双引号和分号前面加反引号。任何更少(或更多!)都会搞砸。

【讨论】:

【参考方案7】:

this answer 的文章中提到了它,但是对于PowerShell 3,您可以使用 --% 来停止 PowerShell 的正常解析。

msbuild --% /target:Deploy /p:UseSandboxSettings=false /p:TargetConnectionString="aConnectionWithSpacesAndSemiColons" "aDatabaseProjectPathWithSpaces"

【讨论】:

抱歉,我之前错过了,但在看到您在此处提及和Mitul's recent investigation 之后,我为这个 PSv3 功能添加了 3.3 部分 :) @EmperorXLII:很高兴看到你改进了你的优秀答案。我建议这是最简单的方法(如果您有 v3),并且可能应该在您的长版本中占据更高的位置,并在您的短版本中提及?

以上是关于带有嵌套引号的 Powershell 调用 msbuild的主要内容,如果未能解决你的问题,请参考以下文章

当带有单引号的文件通过脚本传递时,Powershell 脚本失败。备用批处理文件也因 & 和 ! 而失败人物

关于js生成的字符串带有单双引号嵌套的问题

带有字符串键或嵌套引号的数组到 php 中的 lambda 函数中

通过 TeamCity 将带引号的参数传递给 PowerShell 脚本

命令行转义PowerShell的单引号

CMD 在参数中嵌套双引号