【问题标题】:Nested Quoted Strings in PowershellPowershell中的嵌套引用字符串
【发布时间】:2020-11-26 02:30:00
【问题描述】:

我正在 Powershell 中编写服务安装程序脚本,该服务需要复杂的引用命令行。
所以我试图简化它并将每个选项分解为单个变量,但是在创建最终的命令行字符串时,字符串不会转义它们的引号。因此命令行不起作用。

我想将所有选项分开,以便其他管理员可以配置和安装服务,而无需担心转义引号。

我想我需要先执行搜索和替换或使用特定于 shell 的安全/转义字符串命令来操作各个字符串。

不知道服务的命令行是怎么解析的,所以不知道用什么shell转义方法。

我已经对字符串中的引号进行了搜索,但它们似乎从来没有处理带引号的字符串嵌套在带引号的字符串中。

这是我的安装脚本,我确实可以控制应用程序服务,所以如果您知道将参数输入服务的更好方法,也将不胜感激。

$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

谢谢

【问题讨论】:

  • 您需要创建的格式应该是什么? "SessionUser"="Engineering" ?或"SessionUser='Engineering'" 或??
  • 它们在命令行上被解析(我假设是 cmd),只需要用空格分隔。使用此代码在 OnStart 方法中读取它们; string[] clArgs = Environment.GetCommandLineArgs(); 所以命令行传递的最后一个参数不应该有任何引号; SessionUser=Engineering

标签: powershell quotes string-literals


【解决方案1】:

感谢所有花时间评论和回答的人。

这是最终工作的单行 PS 脚本字符串(连续查看 6 个双引号);

"$($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"

最终类似于此命令行,如 WMI win32_service 的 PathName 属性所示。

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

最后是服务读取的命令行,在OnStart() 方法中使用Environment.GetCommandLineArgs();(逗号分隔)

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

我对脚本进行了一些重大更改,将配置放入 HashTable 并使用 -f 字符串格式化程序。

$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

这有望保护任何其他包含空格的路径。

附言。您看到的从一个文本代码块到下一个文本代码块的任何差异都只是因为我在测试时复制并粘贴了它。

【讨论】:

  • 它确实需要额外的引号。由于此字符串随后由 cmd 解析,因此各个参数被组合在一起并由我的服务正确接收。我只用一个单引号替换了它并删除了 {0} 周围的引号。这失败了,因为 CMD 看到转义的引号字符并继续将空格视为参数分隔符。最重要的是,我的服务调用 CreateProcess 对其执行另一个参数解析并需要保护空间,以便该命令正确接收参数。希望这是有道理的。谈论《盗梦空间》!
  • 啊,是的 - 结果字符串中的三重引号来自"...",在ProgramArguments 属性值(通过$programarguments)中嵌入、转义了""..."" 字符串,这实际上是我的回答也会导致。在这种情况下,无关紧要:我认为执行服务命令行时不涉及cmd.exe - 直接调用目标可执行文件。
  • CreateProcess()lpCommandLine 参数必须进行一些智能解析,因为在调用 createprocess 时它是一个 LPSTR,但作为原始数组到达正在执行的程序。所以它必须执行某种引用和空格转义规则。
  • Windows 上的参数传递从来都不是基于 array 的,遗憾的是;相反,会传递一个对所有参数进行编码的命令行字符串,并由被调用的程序来解析它。 CreateProcess() 唯一要做的就是分离出可执行文件名——第一个空格分隔的未加引号或双引号的标记——如果可执行文件名或路径作为lpCommandLine 的一部分而不是单独通过lpApplicationName 传递。剩余的字符串通过。
  • 对于 C/C++/C# 应用程序,它是 底层运行时 为您进行解析(如果您需要自定义解析,您可以绕过它);见docs.microsoft.com/en-us/cpp/cpp/…
【解决方案2】:

注意:

  • 这个答案解决了所提出的问题,关于通过单个字符串构造命令行所需的引用和转义

  • 有关通过哈希表和循环构造命令行的强大程序化替代方案,请参阅Mark's own answer


潜在的问题:如果$installpath.toString() 返回一个带空格的路径,您还必须对可执行路径使用嵌入式引用。

确定问题:

以下参数本身嵌入了"

$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"'

但是,这个嵌入的引用没有正确放置:可执行路径和-jar 参数单独需要包含在"..." 中,因为两者都嵌入了空格

$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"'

为了将此值嵌入另一个双引号字符串以供命令行使用,您必须转义其嵌入的"""\"(请参阅下文了解何时使用哪个),您可以使用-replace '"', '""'-replace '"', '\"'

以下使用可扩展的此处字符串 (@"<newline>...<newline>"@) 来简化嵌入式引用并将命令分散到多行以提高可读性(生成的换行符随后用-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'

注意:

  • 以上内容仅对 <property>=<value> 对的 value 部分使用嵌入式双引号(例如,foo="bar none" 而不是 "foo=bar none"),不幸的是,这并不是一个不常见的要求在 Windows 上(尤其是 msiexec),这里似乎也有必要,judging from your own answer

  • 嵌入在$programarguments 值中的" 被转义为"" 而不是\"

    • 任何一种转义形式通常都有效,但"" 的优点是您不必担心以\ 结尾的值,而\"-escaping 会 要求您将尾随的 \ 转义为 \\

    • 警告是,虽然 大多数 Windows 上的 CLI 将 两者 ""\" 识别为转义的 ",但有些识别\,例如Ruby、Perl,尤其是使用CommandLineToArgv WinAPI 函数的应用程序。

另见:


顺便说一句:

  • 反引号 ` 是 PowerShell 的通用转义字符。

  • "..." 字符串中(仅限),您可以或者转义嵌入的" 字符。作为""

例如," `"hi`" "`" ""hi"" " 都逐字返回  "hi" 

例外是当PowerShell被从外部调用时,通过它的CLI只有\"是在 Windows PowerShell 中将识别为转义的",以便与其他 CLI 保持一致,而 PowerShell [Core] v6+ 也接受 ""

【讨论】:

    【解决方案3】:

    你应该用反引号转义你的引号:

    $test = "`"test`""
    
    Write-Host $test
    

    或者你可以像这样使用here string

    $test = @'
    "test"
    '@
    
    Write-Host $test
    

    两者都将"test" 写入控制台

    【讨论】:

    • 或三重双引号Write-Host """test"""
    • 这不是一个简单的如何转义字符串问题。我作为个人收到正确引用的字符串,但随后我必须将这些字符串嵌套到另一个带引号的字符串中。 $a='"A"' $b='"b"' $c ="""$a"" ... ""$b"""
    • 我的编辑仍在等待中,所以,如果你这样做$test2 = "``"$test``""(我无法在评论中正确设置:每边只有一个转义字符)上面的$test声明,@ 987654328@会包含""test"",我想这就是你想要得到的
    • @Doug,这并不是真正的三重引用,而是将嵌入的" 字符转义为"",这是马克在他的问题中使用的,它只是`" 的替代品-转义 - 这就是为什么这个答案没有帮助。
    • @CFou,这个问题原则上已经显示了正确的字符串插值,""`" 可以互换使用以在"..." 内转义"。关于元注释:要在评论中包含逐字的`(就像我在这里所做的那样),\ -escape it。
    猜你喜欢
    • 2017-02-28
    • 2021-08-04
    • 1970-01-01
    • 1970-01-01
    • 2021-01-22
    • 1970-01-01
    • 1970-01-01
    • 2017-05-04
    • 1970-01-01
    相关资源
    最近更新 更多