使用多行字符串参数启动提升的Powershell会话

k2arahey  于 2023-06-06  发布在  Shell
关注(0)|答案(1)|浏览(234)

我试图在启动提升的powershell会话时将脚本块作为命令传递,但我找不到使其工作的方法。我想是因为文件内容是多行的吧?

$content = @"
This is a
multiline
file content.
"@

$path = 'C:\test.txt'

$scriptBlock = {
    param($fileContent, $filePath)
    Write-Host "PATH: $filePath`n`n"
    Write-Host "CONTENT:`n$fileContent"
    [System.IO.File]::WriteAllText($filePath, $fileContent, [System.Text.Encoding]::GetEncoding('Windows-1252'))
    Pause
}

Start-Process -FilePath 'PowerShell' -Verb 'RunAs' -ArgumentList "-NoProfile -ExecutionPolicy Bypass -Command '&{$scriptBlock} `"$content`" `"$path`"'" -Wait

Pause

我错过了什么?有没有更好的方法来实现这一点?
我尝试了上面的代码,但提升的PowerShell会话在打开后立即关闭。我认为开始-过程行是错误的。

编辑:

我试着用一个函数 Package 脚本块内容,并稍微改变了启动进程,现在它工作了:

$content = @"
This is a
multiline
file content.
"@

$path = 'C:\test.txt'

$scriptBlock = {
    function wrappingFunc {
        param(
            $filePath,
            $fileContent
        )

        Write-Host "PATH: $filePath"
        Write-Host "CONTENT: $fileContent"
        [System.IO.File]::WriteAllText($filePath, $fileContent, [System.Text.Encoding]::GetEncoding('Windows-1252'))
        Pause
    }
}

Start-Process -FilePath 'PowerShell' -Verb 'RunAs' -ArgumentList "-NoProfile -ExecutionPolicy Bypass -Command & {$scriptBlock wrappingFunc -filePath '$path' -fileContent '$content'}" -Wait

Pause

我其实不完全明白为什么它是这样工作的。有人能澄清一下吗?
我还注意到一个非常奇怪的行为。如果我将脚本块中的以下行从:

Write-Host "PATH: $filePath"
    Write-Host "CONTENT: $fileContent"

致:

Write-Host -Object "PATH: $filePath"
    Write-Host -Object "CONTENT: $fileContent"

它就会停止工作。为什么简单地添加-Object会破坏它?

ljo96ir5

ljo96ir51#

[1]还要注意zdan有用的调试技巧:在PowerShell CLI调用中临时将-NoExit添加到-Command之前,以保持会话打开,从而允许您检查错误消息。

问题不是使用了 multiline 命令字符串,而是与 quoting有关:

  • 不要使用'...'-在-Command参数中引用(除非这些'字符是要执行的PowerShell代码的一部分)-当从外部调用 * 时-使用Start-Process就是一个例子- PowerShell将其 * 进程命令行 * 上的'...'字符串视为 * 逐字字符串 *。
  • 从PowerShell外部(例如,包括从cmd.exe或从计划任务调用时),当调用其CLI(powershell.exe用于Windows PowerShell,pwsh用于PowerShell(Core)7+)时,只有"..."引用具有 * 语法 * 功能(在进程命令行上)。
  • 转义"字符,这些字符将成为PowerShell命令的一部分,以\"的形式执行-否则PowerShell的初始 CLI 解析将删除它们。
    让你的代码基于这些要求 * 健壮地工作*有点麻烦(这里使用了一个字符串来引用方便和可读性):
# ...

Start-Process -FilePath PowerShell -Verb RunAs -Wait -ArgumentList @"
  -NoProfile -ExecutionPolicy Bypass -Command "
    & {
      $($scriptBlock -replace '(\\*)"', '$1$1\"')
    } '$($content -replace '(\\*)"', '$1$1\"' -replace "'", "''")' '$($path -replace "'", "''")'
  "
"@
  • -replace操作确保任何"字符。被转义为\",以保护它们免受初始CLI解析。
  • 正则表达式中的(\\*)捕获值中紧接在"之前的任何\字符,这些字符必须 * 加倍 * 才能保留,这就是替换表达式中的$1$1所做的。
  • $content$path的情况下,由于使用了嵌入式'...'引用,因此任何嵌入式'字符。必须另外转义为''(因为Windows上的文件路径不能包含"字符,所以$paths值只需要 * 这个转义。
  • 使用嵌入式'...'引号而不是"..."确保$content$replace的值不会在目标PowerShell示例中进行 * 另一轮 * 插值。
    重要
  • 上面的代码是为了处理$scriptBlock$content$path中的 * 任何值 * 而设计的- * 在特定情况下 * 您可以 * 不执行-replace操作 *,但您需要完全理解规则以了解何时执行-因此,下一节中介绍的Base64解决方案更可取。
  • 将传递给-Command的PowerShell代码封装在"..."中并不是严格需要的,但是 * 不 * 这样做会产生 * 空白规范化 * 的副作用(通常 * 不会 * 是一个问题,但可能是);例如,如果$content包含Four spaces: ...,它将变成Four spaces: ...。也就是说,任何 * 两个或多个空格 * 的运行都将变成 * 单个 * 空格。
  • 即使不使用整个"..." shell ,作为要执行的PowerShell代码的一部分的"字符 * 必须 * 转义为\"
  • 上面解释了你后来添加到你的问题中的代码的-british-行为,在“编辑:”部分:
  • 虽然更新后的代码通过删 debugging 误的整个'...' shell 避免了 * 原始 * 尝试的主要问题,但它仍然会删除未转义的"字符,这解释了您尝试将-Object添加到Write-Host调用的问题:
  • 由于删除了"字符,Write-Host -Object "PATH: $filePathnn"变成了Write-Host -Object PATH: $filePathnn,这导致了一个 syntax 错误,因为使用 named 参数绑定-Object只接受一个 * 单个 * 参数(然而,它可能是一个,分隔的 * 数组 * 值),但是"删除将单个参数变成了 * 多个 * 参数。(仅当您使用 * 位置 * 参数绑定时,即 * 如果没有 * -ObjectWrite-Host是否接受 * 多个单独的 * 参数,这就是为什么-尽管删除了"-它仍然 * 碰巧工作 *。
  • 如果您的$content$path值包含'字符,代码也会中断。

如果你想*避免 * 引号和-replace操作,你可以Base64编码你的脚本块和参数,并使用结果与**-EncodedCommand和(目前没有文档)-EncodedArguments**CLI参数:

# ... 

# Create a Base64 representation of the bytes that make up the
# "Unicode" (UTF-16LE) encoding of the script block, for use
# with the -EncodedCommand PowerShell CLI parameter.
# Note that $scriptBlock is implicitly *stringified* here, resulting
# in its verbatim source code, but without the enclosing { ... }
$encodedCommand = [Convert]::ToBase64String(
  [Text.Encoding]::Unicode.GetBytes($scriptBlock)
)

# Create a Base64 representation of the bytes that make up the
# "Unicode" (UTF-16LE) encoding of the - invariably positional -
# arguments to pass, for use with `-EncodedArguments`.
# Note the need to pass the arguments as an *array* (@(...)).
$encodedArgs = [Convert]::ToBase64String(
  [Text.Encoding]::Unicode.GetBytes(
    [System.Management.Automation.PSSerializer]::Serialize(@($content, $path))
  )
)

# Note the use of -EncodedCommand and -EncodedArguments
Start-Process -FilePath 'PowerShell' -Verb 'RunAs' -Wait -ArgumentList @"
  -NoProfile -ExecutionPolicy Bypass -NoExit -EncodedCommand $encodedCommand -EncodedArguments $encodedArgs
"@

注意事项:

  • 这种方式只支持 * 位置 * 参数传递;也就是说,您可以将 valuesfooc:\bar传递给脚本块的参数,这些值的 order 意味着-fileContent-filePath作为目标参数,但您不能使用-fileContent foo-filePath bar,即不能使用 named 参数。
  • 支持 named 参数的唯一方法是通过字符串插值将这些命名参数“烘焙”到脚本块中,而不是像更新的尝试那样传递单独的参数,但这同样需要小心地引用和转义。

相关问题