PowerShell 中 Start-Job 的 ScriptBlock / InitializationScript 的最大大小

Max size of ScriptBlock / InitializationScript for Start-Job in PowerShell

当您使用 Start-Job 开始新工作时,您可以将 ScriptBlockInitializationScript 传递给它,例如:

Function FOO {
  Write-Host "HEY"
} 
Start-Job -ScriptBlock {FOO} -InitializationScript {
  Function Foo { $function:FOO }
} | Wait-Job | Receive-Job

您可以传递的初始化脚本的大小似乎有限制,如果它太大则会出现错误,例如

[localhost] An error occurred while starting the background process. Error
reported: The filename or extension is too long.
    + CategoryInfo          : OpenError: (localhost:String) [], PSRemotingTransportException
    + FullyQualifiedErrorId : -2147467259,PSSessionStateBroken

在幕后,PowerShell 正在创建一个新进程并将 InitializationScript 作为 Base64 编码的命令行参数传递。

根据Win32 CreateProcess()函数,命令的最大长度为32,768个字符。所以很明显,如果你的 Base64 编码 InitializationScript 接近这个大小,那么你可能会得到一个错误。

我还没有找到 ScriptBlock 参数大小的限制。有人可以确认没有限制吗?

我假设没有限制,因为看起来 ScriptBlock 是通过标准输入传输到子进程的?

脚本块实际上是有限制的。您可以 运行 使用 3 种方式使用脚本块命令:

$scriptblock = '
get-help
get-command
dir
get-help *command*
get-command *help*
'

iex $scriptblock

或使用它:

$scriptblock = {
get-help
get-command
dir
get-help *command*
get-command *help*

}

Start-Process powershell.exe -ArgumentList "Command $scriptblock"

或者使用这个:

Start-Process powershell {iex  '
get-help
get-command
dir
get-help *command*
get-command *help*


'
}

可以传递给 powershell.exe 的脚本限制是 12190 字节。 但是对于脚本,我从来没有从 powershell 获得超过 4000 行代码的限制。

你猜对了。

PowerShell 在幕后将 Start-Job 调用转换为 PowerShell CLI 调用 (对于 PowerShell (Core) 7+,转换为 powershell.exe for Windows PowerShell, and to pwsh),即,它通过 子进程 实现并行性,调用 PowerShell 会话使用标准输入和输出流与 通信:

  • 因为 -InitializationScript 脚本块被转换为 Base64 编码的字符串,代表块字符串的 UTF-16LE 编码的字节表示,传递给 CLI 的 -EncodedCommand 参数,它的最大值。长度 受限于进程命令行的总长度限制

    • 该限制是 32,766 个字符(Unicode 字符,而不仅仅是 字节),因为终止 NUL 字符是在底层 WinAPI 调用中需要(引用 CreateProcess() WinAPI function documentation 你 link 到:“此字符串的最大长度为 32,767 个字符,包括 Unicode 终止空字符”)。

    • 请注意,PowerShell 可执行文件的完整路径包含在此限制中,以 double-quoted 形式(见下文),真正重要的是整个生成的命令行;因此,鉴于 PowerShell (Core) 7+ 可以安装在任何目录中,其安装位置对有效限制有影响,当前目录的路径长度也有影响(见下一点)。

    • WindowsPowerShell中,其位置是固定的,其CLI参数值在调用中是固定长度的(见下文),这留下 32,655 个字符 用于 Base64 编码的字符串 (32766 - 111 个字符用于可执行路径和固定参数以及 -EncodedCommand 参数姓名); 相似,由于安装位置和 [=20] 的长度不同,PowerShell (Core) 7+ 无法给出固定数量=] (working-directory) 参数取决于当前位置。

    • Base64 编码 UTF-16LE 编码字符串的字节导致 ca。长度增加 2.67 倍,这*达到**最大值。传递给 -InitializationScript 12,244 个字符的脚本块的长度[1] for Windows PowerShell;对于 PowerShell (Core) 7+,它会略低,具体取决于安装位置和当前目录路径的长度。

  • 相比之下,-ScriptBlock参数,即在后台执行的操作,是通过[=发送的79=]stdin(标准输入流)到新启动的 PowerShell 进程,因此有 no 长度限制.

例如,下面的 Start-Job 调用:

Start-Job -ScriptBlock { [Environment]::CommandLine } -InitializationScript { 'hi' > $null } | 
  Receive-Job -Wait -AutoRemoveJob

显示 background-job 子进程启动如下,当 运行 来自 Windows PowerShell:

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Version 5.1 -s -NoLogo -NoProfile -EncodedCommand IAAnAGgAaQAnACAAPgAgACQAbgB1AGwAbAAgAA==

如您所见,-ScriptBlock 参数的文本 出现在结果命令行中(它是通过标准输入发送的),而 -InitializationScript 参数是,作为传递给 -EncodedCommand 的 Base64 编码字符串,您可以按如下方式验证:

# -> " 'hi' > $null ", i.e. the -InitializationScript argument, sans { and }
[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('IAAnAGgAaQAnACAAPgAgACQAbgB1AGwAbAAgAA=='))`)

至于其他参数:

  • -s-servermode 的缩写,它是一个未记录的参数,其唯一目的是促进后台作业(通过其标准流与调用进程通信);有关详细信息,请参阅

  • -Version 5.1 仅适用于 Windows PowerShell,并非绝对必要。

  • -NoLogo 也不是绝对必要的,因为它是 隐含的 通过使用 -EncodedCommand (就像它与-Command-File).

  • 在 PowerShell (Core) 7+ 中,您还会看到一个 -wd-WorkingDirectory 的缩写)参数,因为那里的后台作业现在明智地使用相同的参数作为调用者的工作目录。


[1] [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes('x' * 12244)).Length 产生 32652,这是最接近 32655 极限的值; 12245 的输入长度产生 32656