1

Trying to run simple http server in separate process using powershell. But can't figure out how to pass parameters to it. Tried several approaches and constantly getting exception. Could you advice?

$port = 9700
$prefix = 'http://+:{0}/' -f $port;  

 $ScriptBlock = {

    $http = [System.Net.HttpListener]::new() 
    $http.Prefixes.Add($args[0]); 
    $http.Start(); 
    $Error.Clear(); }


Start-Process pwsh -ArgumentList '-NoProfile', '-NoExit',  "-Command" $ScriptBlock $prefix -NoNewWindow -Wait     

In result of example above it: A positional parameter cannot be found that accepts argument ' $http = [System.Net.HttpListener]::new() | $http.Prefixes.Add($args[0]); $http.Start(); $Error.Clear(); '.

Alexander
  • 13
  • 2
  • 1
    You can't pass a ```ScriptBlock``` *object* as an argument to ```Start-Process```. You'll need to serialize your commands into a string and pass that in the argument list. – mclayton Apr 18 '23 at 09:40

1 Answers1

0
  • All pass-through arguments passed to Start-Process must be the elements of a single array passed to -ArgumentList.

    • $ScriptBlock and $prefix in your call aren't part of the -ArgumentList array and therefore constitute additional, positional arguments from Start-Process's perspective, which it doesn't recognize - that caused the error you saw.
  • Fundamentally, all pass-through arguments are invariably strings, so you cannot pass a script block as such (it would be turned into its verbatim source code, not including the enclosing { and })

However, since are looking for synchronous invocation (-Wait) in the same window (-NoNewWindow), there's no need for Start-Process at all, and you can simply call pwsh directly, which - from inside a PowerShell session ony - does allow you to use a script block:

pwsh -NoProfile -NoExit $ScriptBlock -args $prefix

See the docs for PowerShell (Core)'s CLI, pwsh.


If you do want to use Start-Process - which is only necessary if you want to run the code in a new window or with elevation (as admin) or with a different user identity - use the following:

  • For readability and ease of embedding quotes, the solution uses an expandable here-string (@"<newline>...<newline>@").

  • It takes advantage of the fact that script blocks serialize as their verbatim source code, excluding { and }, so you make the target PowerShell instance invoke it by enclosing it in & { ... } in the command string.

    • While & { $ScriptBlock } would work in your particular case, to make the technique robust, any embedded " characters must be escaped as \"-escaped in order to ensure that the CLI's initial command-line parsing doesn't remove them.
      This is what the -replace '(?<!\\)(\\*)"', '$1$1\"' operation below does.
Start-Process pwsh @"
-NoProfile -Command & { $($ScriptBlock -replace '(?<!\\)(\\*)"', '$1$1\"') } "$prefix"
"@

Note:

  • All pass-through arguments for pwsh are encoded in a single string passed to the (positionally implied) -ArgumentList parameter.

    • While passing pass-through arguments individually - as the elements of an array, as you originally tried - may be conceptually preferable, a long-standing bug unfortunately makes the single-string approach preferable, because it makes the situational need for embedded double-quoting explicit - see this answer.
  • While you could alternatively make use of the -EncodedCommand and (currently undocumented) -EncodedArguments CLI parameters, doing is even more cumbersome, due to requiring Base64 encoding.

    • In direct invocation, as shown in the first snippet, PowerShell actually makes use of these parameters behind the scenes, which makes invocation convenient - see this answer for background information.
  • If you do want to use these parameters with Start-Process, see this answer for an example.

mklement0
  • 382,024
  • 64
  • 607
  • 775