PowerShell中使用Start-Job命令的ScriptBlock / InitializationScript的最大大小是多少?

3

当你使用Start-Job开始一份新工作时,你可以传递一个ScriptBlock和一个InitializationScript,例如:

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通过标准输入流传输到子进程中。

2个回答

2

你的猜测是正确的。

PowerShell在幕后将Start-Job调用转换为一个PowerShell CLI调用(对于Windows PowerShell,转换为powershell.exe,对于PowerShell (Core) 7+,转换为pwsh),也就是说,它通过一个子进程实现并行处理,由调用PowerShell会话与之通信,使用标准输入和输出流:

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

    • 该限制为32,766个字符(Unicode字符,而不仅仅是字节),因为在底层WinAPI调用中需要一个终止NUL字符(引用来自您链接的CreateProcess() WinAPI函数文档:“此字符串的最大长度为32,767个字符,包括Unicode终止空字符”)。

    • 请注意,PowerShell可执行文件的完整路径以双引号形式包含在此限制中(请参见下文),实际上是整个结果命令行才重要;因此,鉴于PowerShell(Core)7+可以安装在任何目录中,其安装位置会影响有效限制,当前目录路径的长度也会有影响(请参见下一点)。

    • 在Windows PowerShell中,其位置固定,并且其CLI参数值在调用中具有固定长度(请参见下文),这使得Base64编码字符串有32,655个字符可用于 -InitializationScript (32766-可执行路径和固定参数以及-EncodedCommand 参数名称的111个字符);虽然类似,但对于PowerShell(Core)7+,由于安装位置不同以及-wd(工作目录)参数取决于当前位置的长度,因此无法给出固定数字。

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

  • 相比之下,-ScriptBlock 参数,即在后台执行的操作,通过标准输入流stdin发送到新启动的PowerShell进程中,因此没有长度限制。

例如,以下Start-Job调用:
Start-Job -ScriptBlock { [Environment]::CommandLine } -InitializationScript { 'hi' > $null } | 
  Receive-Job -Wait -AutoRemoveJob

当从Windows PowerShell运行时,显示后台作业子进程是如下启动的:

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

正如您所看到的,-ScriptBlock参数的文本在生成的命令行中不存在(它是通过标准输入发送的),而-InitializationScript参数的文本则以Base64编码的字符串形式通过-EncodedCommand传递,您可以按照以下方式进行验证:

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

0

脚本块实际上有限制。您可以通过以下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行代码的限制。


你有任何证据表明没有限制,或者有没有理由说明没有限制? - Bob Mortimer
@BobMortimer:我在脚本块中写了大约4000行代码,你的文本或脚本块限制取决于你的硬件,例如内存... - saftargholi
@BobMortimer:但是当你传递脚本时,你只需要将12190字节传递给powershell.exe。 - saftargholi
我不相信12k的限制 - 我能够在ScriptBlock中传递约118k的John Gay的《乞丐歌剧》全文。 - Bob Mortimer
1
我的问题是是否有限制,如果有的话是什么。我不是在问如何规避限制(如果有的话)。你似乎认为有一个限制——12k的数字是从哪里来的? - Bob Mortimer
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接