命令行转义PowerShell的单引号

时间:2014-01-06 20:10:36

标签: powershell cmd escaping

我有一个Windows应用程序,在事件上,它调用这样的命令:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "G:\test.ps1 -name '%x' -data '%y'"

name参数有时会包含'。有可能以某种方式逃脱吗?

6 个答案:

答案 0 :(得分:9)

这实际上比你想象的要复杂得多。转义从cmd传递到PowerShell的字符串中的嵌套引号是一个令人头痛的问题。是什么让这个特别棘手的是你需要在传递给PowerShell脚本参数的单引号参数中传递给powershell.exe的引用参数中,用cmd扩展的变量进行替换。即使是基本的字符串替换,AFAIK cmd也没有任何原生功能,因此您需要PowerShell为您进行替换。

如果 -data 参数的参数(cmd变量 x 中包含的参数)不一定需要单引号,那么最简单要做的是引用它,因此 x 值内的单引号根本不需要转义。我说"最简单",但即使这有点棘手。正如Vasili Syrakis指出的那样,^通常是cmd中的转义字符,但要转义(双引号)字符串中的双引号,则需要使用\。因此,您可以像这样编写批处理命令:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "G:\test.ps1 -name \"%x%\" -data '%y%'"

将以下命令传递给PowerShell:

G:\test.ps1 -name "value of x, which may contain 's" -data 'value of y'

但是,如果 x 也可以包含PowerShell插值字符串("$`)中特殊字符的字符,那么变得更加复杂的 LOT 。问题是%x 是一个cmd变量,在PowerShell有机会触摸它之前会被cmd扩展。如果您在命令中单引号将其传递给powershell.exe并且它包含单引号,那么您将为PowerShell会话提供一个早期终止的字符串,因此PowerShell不会有机会对其进行任何操作。以下显然不起作用,因为 -replace 运算符需要在替换任何内容之前提供有效字符串:

'foo'bar' -replace "'", "''"

另一方面,如果你对它进行双引号,那么PowerShell会在对字符串执行任何替换之前对其进行插值,因此如果它包含任何特殊字符,则在替换之前对它们进行转义。 。我搜索高低不同的其他方式来引用内联字符串(相当于perl' s q // ,其中没有任何东西需要转义但是你选择的分隔符),但是那里似乎没什么。

因此,唯一的解决方案是使用here字符串,这需要一个多行参数。这在批处理文件中很棘手,但可以做到:

setlocal EnableDelayedExpansion
set LF=^


set pscommand=G:\test.ps1 -name @'!LF!!x!!LF!'@ -data '!y!'
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "!pscommand!"
  • 这假设在批处理文件中先前设置了 x y 。如果您的应用只能向cmd发送单行命令,那么您需要将上述内容放入批处理文件中,将以下两行添加到开头:

    set x=%~1
    set y=%~2
    

    然后像这样调用批处理文件:

    path\test.bat "%x%" "%y%"
    

    ~删除了命令行参数周围的引号。您需要引号才能在变量中包含空格,但引号也会添加到变量值中。批处理就是那样愚蠢。

  • set LF=^后面的两个空行是必需的。

它处理单引号,它也解释了 x 值的所有其他字符,但有一个例外:双引号。不幸的是,如果双引号可能是您在评论中指出的价值的一部分,我不相信在不使用第三方实用程序的情况下问题是可以克服的。原因是,如上所述,批处理不具有执行字符串替换的本机方式,并且在PowerShell看到它之前, x 的值会被cmd扩展。

BTW ...... 好问题!!


<强>更新

事实上,事实证明 可以在cmd中执行静态字符串替换。 Duncan补充说明了如何做到这一点。这有点令人困惑,所以我将详细阐述Duncan解决方案中发生的事情。

我们的想法是%var:hot=cold%扩展为变量 var 的值,所有出现的hot都替换为cold

D:\Scratch\soscratch>set var=You're such a hot shot!

D:\Scratch\soscratch>echo %var%
You're such a hot shot!

D:\Scratch\soscratch>echo %var:hot=cold%
You're such a cold scold!

所以,在命令中(为了清楚起见,从Duncan的答案中修改了与OP的例子一致):

powershell G:\test.ps1 -name '%x:'=''%' -data '%y:'=''%'

变量 x y 中出现的所有'都被''替换,命令会扩展为

powershell G:\test.ps1 -name 'a''b' -data 'c''d'

让我们分解其中的关键元素'%x:'=''%'

  • 开头和结尾的两个'是显式外部引号传递给PowerShell引用参数,即OP所在的相同单引号%x
  • :'=''是字符串替换,表示'应替换为''
  • %x:'=''%扩展为变量 x 的值,'替换为'',即a''b
  • 因此,整件事扩展为'a''b'

此解决方案比上面的解决方法更简单地转义变量值中的单引号。但是,OP在更新中表明该变量也可能包含双引号,到目前为止,此解决方案仍然没有将 x 中的双引号传递给PowerShell - 这些仍然会被删除PowerShell收到命令之前的cmd。

好消息是,使用cmd字符串替换方法,这变得可以克服。在设置 x 的初始值后执行以下cmd命令:

  1. '替换为'',以转义PowerShell的单引号:

    set x=%x:'=''%
    
  2. "替换为\",以转义cmd的双引号:

    set x=%x:"=\"%
    

    这两项任务的顺序并不重要。

  3. 现在,可以使用OP首先使用的语法调用PowerShell脚本(去掉powershell.exe的路径以使其全部适合一行):

    powershell.exe -ExecutionPolicy Bypass "G:\test.ps1 -name '%x' -data '%y'"
    
  4. 同样,如果应用程序只能向cmd发送一行命令,这三个命令可以放在批处理文件中,应用程序可以调用批处理文件并传递变量,如上所示(我的第一个子弹)原答案)。

    有一点需要注意的是,如果用"替换\"是内联而不是单独的设置命令,那么 don&#39 ; t 在字符串替换中转义" s,即使它们在双引号字符串中,也就是这样:

    powershell.exe -ExecutionPolicy Bypass "G:\test.ps1 -name '%x:"=\"' -data '%y'"
    

    ... ,就像这样:

    powershell.exe -ExecutionPolicy Bypass "G:\test.ps1 -name '%x:\"=\\"' -data '%y'"
    

答案 1 :(得分:3)

我对%x%y是否为CMD变量的问题略显不清楚(在这种情况下,您应该使用%x%替换它,或者在您的代码中发生替换其他申请。

您需要通过在CMD.EXE命令行中将其加倍来转义要传递给PowerShell的单引号。您可以通过用两个单引号替换变量中的任何引号来完成此操作。

例如:

C:\scripts>set X=a'b

C:\scripts>set Y=c'd

C:\scripts>powershell .\test.ps1 -name '%x:'=''%' '%y:'=''%'
Name is 'a'b'
Data is 'c'd'

test.ps1包含:

C:\scripts>type test.ps1
param($name,$data)

write-output "Name is '$name'"
write-output "Data is '$data'"

如果您在外部应用程序中生成了您提供的命令行,您仍然可以通过首先将字符串分配给变量并使用&分隔命令来执行此操作(小心避免拖尾) set命令上的空格。

set X=a'b& powershell .\test.ps1 -name '%x:'=''%'

CMD shell支持简单的替换形式,以及在替换变量时提取子串的方法。这些仅在替换变量时起作用,因此如果您想同时进行多次替换,或者替换和子串提取,那么您需要一次一个地设置每个步骤的变量。

Environment variable substitution has been enhanced as follows:

    %PATH:str1=str2%

would expand the PATH environment variable, substituting each occurrence
of "str1" in the expanded result with "str2".  "str2" can be the empty
string to effectively delete all occurrences of "str1" from the expanded
output.  "str1" can begin with an asterisk, in which case it will match
everything from the beginning of the expanded output to the first
occurrence of the remaining portion of str1.

May also specify substrings for an expansion.

    %PATH:~10,5%

would expand the PATH environment variable, and then use only the 5
characters that begin at the 11th (offset 10) character of the expanded
result.  If the length is not specified, then it defaults to the
remainder of the variable value.  If either number (offset or length) is
negative, then the number used is the length of the environment variable
value added to the offset or length specified.

    %PATH:~-10%

would extract the last 10 characters of the PATH variable.

    %PATH:~0,-2%

would extract all but the last 2 characters of the PATH variable.

答案 2 :(得分:1)

我相信你可以通过^来逃避它:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "G:\test.ps1 -name ^'%x^' -data ^'%y^'"

答案 3 :(得分:0)

尝试将随机单引号变量封装在一对双引号内,以避免出现问题。

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass "G:\test.ps1 -name `"%x`" -data `"%y`""

问题出现是因为你使用单引号和单引号内出现的随机额外单引号傻瓜PowerShell。如果你用反引号加双引号就不会发生这种情况,因为单引号不会在双引号内抛出任何东西,反引号会允许你加倍/双引号。

答案 4 :(得分:0)

仅供参考,我在Powershell中使用robocopy命令时遇到了一些麻烦,想排除名称中带有单引号的文件夹(并且反引号无济于事,ps和robocopy不能使用双引号);因此,我通过为文件夹创建一个带引号的变量来解决它:

$folder="John Doe's stuff"
robocopy c:\users\jd\desktop \\server\folder /mir /xd 'normal folder' $folder

答案 5 :(得分:0)

解决此问题的一种古怪方法是在 cmd 中使用 echo 将您的命令通过管道传递给 'powershell -c "-" ' 而不是使用 powershell "arguments"

例如:

ECHO Write-Host "stuff'" | powershell -c "-"

输出:

stuff'

几个注意事项:

  1. 不要引用您要回显的命令文本,否则将不起作用

  2. 如果你想在 PowerShell 命令中使用任何管道,它们必须用克拉进行三次转义才能正常工作

    ^^^|