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

When you start a new job with Start-Job, you can pass it a ScriptBlock and a InitializationScript for example:

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

There seems to be a limit to the size of the initialization script you can pass, if it is too big then you get an error such as

[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

Behind the scenes, PowerShell is creating a new process and passing InitializationScript as a Base64 encoded command line parameter.

According to the Win32 CreateProcess() function, the max size of the command ine is 32,768 characters. So obviously if your Base64 encoded InitializationScript is getting near this size then you will probably get an error.

I haven't yet found a limit for the size of the ScriptBlock parameter. Can someone confirm that there is no limit?

I assume that there is no limit because it looks like the ScriptBlock is transmitted to the child process via standard input?



Solution 1:[1]

Your guess was correct.

PowerShell translates a Start-Job call into a PowerShell CLI call behind the scenes (to powershell.exe for Windows PowerShell, and to pwsh for PowerShell (Core) 7+), that is, it achieves parallelism via a child process that the calling PowerShell session communicates with, using the standard in- and output streams:

  • Because the -InitializationScript script block is translated to a Base64-encoded string representing the bytes of the UTF-16LE encoding of the block's string representation, which is passed to the CLI's -EncodedCommand parameter, its max. length is limited by the overall length limit of a process command line.

    • That limit is 32,766 characters (Unicode characters, not just bytes), because a terminating NUL character is required in the underlying WinAPI call (to quote from the CreateProcess() WinAPI function documentation you link to: "The maximum length of this string is 32,767 characters, including the Unicode terminating null character").

    • Note that the full path of the PowerShell executable is included in this limit, in double-quoted form (see below), and it really is the entire resulting command line that matters; therefore, given that PowerShell (Core) 7+ can be installed in any directory, its installation location has an effect on the effective limit, as does the length of the path of the current directory (see next point).

    • In Windows PowerShell, whose location is fixed, and whose CLI parameter values are of fixed length in the invocation (see below), this leaves 32,655 characters for the Base64-encoded string (32766 - 111 characters for the executable path and fixed parameters and the -EncodedCommand parameter name); while similar, no fixed number can be given for PowerShell (Core) 7+, due to differing install locations and the length of the -wd (working-directory) argument depending on the current location.

    • Base64-encoding the bytes of a UTF-16LE-encoded strings results in a ca. 2.67-fold increase in length, which*makes the **max. length of a script block passed to -InitializationScript 12,244 characters[1] for Windows PowerShell; for PowerShell (Core) 7+, it'll be slightly lower, depending on the installation location and the length of the current directory's path.

  • By contrast, the -ScriptBlock argument, i.e. the operation to perform in the background, is sent via stdin (the standard input stream) to the newly launched PowerShell process, and therefore has no length limit.

For instance, the following Start-Job call:

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

reveals that the background-job child process was launched as follows, when run from Windows PowerShell:

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

As you can see, the -ScriptBlock argument's text is not present in the resulting command line (it was sent via stdin), whereas the -InitializationScript argument's is, as the Base64-encoded string passed to -EncodedCommand, which you can verify as follows:

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

As for the other parameters:

  • -s is short for -servermode, and it is an undocumented parameter whose sole purpose is to facilitate background jobs (communication with the calling process via its standard streams); see this answer for more information.

  • -Version 5.1 applies only to Windows PowerShell, and isn't strictly necessary.

  • -NoLogo is also not strictly necessary, because it is implied by the use of -EncodedCommand (as it would be with -Command and -File).

  • In PowerShell (Core) 7+, you'd also see a -wd (short for: -WorkingDirectory) parameter, because background jobs there now sensibly use the same working directory as the caller.


[1] [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes('x' * 12244)).Length yields 32652, which is the closest you can come to the 32655 limit; an input length of 12245 yields 32656.

Solution 2:[2]

the script block actually have limit. and you can run command with scriptblock with 3 way :

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

iex $scriptblock

or use it :

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

}

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

or use this :

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


'
}

the limit of script that can pass to powershell.exe is 12190 bytes. but for script i never get limit from powershell more than 4000 line code.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2