2

I need to run this command, which is read from another script.

$command = "$arr = @($input)
$arr.count
Write-Host $arr[0]"

"Classical Music for When You are on a Deadline." | & cmd /C powershell -Command {$command}

So I am getting something through the pipe and then I use it in my string command. The code does not work because $command is not expanded to the string inside the call and therefore unknown in the launched powershell command.

These work as expected, but the commands are not taken from a string:

"Classical Music for When You are on a Deadline." | & cmd /C powershell -Command {Invoke-Expression "Write-Host $input"}

# No:   System.Management.Automation.Runspaces.PipelineReader`1+<GetReadEnumerator>d__20[System.Object]
# "Classical Music for When You are on a Deadline." | & cmd /C powershell -Command {Write-Host $input}

"Classical Music for When You are on a Deadline." | & cmd /C powershell -Command {$arr = @($input)
    $arr.count
    Write-Host $arr[0]}
Liviu
  • 1,859
  • 2
  • 22
  • 48

2 Answers2

2
  • { $command } does not turn the value of string $command into a script block - it simply creates a script block that references a variable named $command.

  • Also, do not call powershell.exe via cmd /c - it is generally unnecessary.

Therefore:

$command = '$arr = @($input)
$arr.count
Write-Host $arr[0]'

"Classical Music for When You are on a Deadline." | 
  powershell ([scriptblock]::Create($command))  # -Command is implied.

Note:

  • The above is the dynamic, variable-based equivalent of the last solution attempt in your question.

  • Using a script block rather than a string with PowerShell's CLI - which is only an option from inside PowerShell - is generally preferable, for the following reasons:

    • Limited type fidelity is provided, using the same XML-based serialization infrastructure as for background jobs and remoting; that is, you can return objects other than strings from such a CLI call; similarly, such calls preserve all of PowerShell's output streams.

    • Base64-encoding is used behind the scenes to safely pass the script block's source code to the target PowerShell process, which bypasses any quoting and escaping headaches.

      • Notably, due to a long-standing bug up to PowerShell 7.2.x, passing commands as string requires embedded " characters to be manually escaped as \" - see this answer.

In other words: While powershell -Command $command would have worked with the specific sample string, it would fail if the string contained " characters, invariably in Windows PowerShell, and up to at least PowerShell (Core) 7.2.x.


Taking a step back: Since you're already running in PowerShell, there is no need to create another instance, as a child process, which is expensive.

$command = '$arr = @($input)
$arr.count
Write-Host $arr[0]'

# Invoke the dynamically created script block directly.
"Classical Music for When You are on a Deadline." | 
  & ([scriptblock]::Create($command))

Note:

  • &, the call operator is used to invoke the script block, which runs it in a child scope.

    • If you want the script block to run directly in the caller's scope, use ., the dot-sourcing operator instead.
  • Invoke-Expression (iex) - which should generally be avoided - is not an option here anyway, because it doesn't support using the pipeline to pass data to the code being evaluated.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Thanks, @SantiagoSquarzon: it would, but only if `$command` happens to contain _no embedded `"` characters_ - I've updated the answer to make that clear. – mklement0 Dec 05 '22 at 19:27
  • I see what you mean, if calling from pwsh works ok using a here-string tho it fails when calling from powershell – Santiago Squarzon Dec 05 '22 at 19:35
  • 1
    @SantiagoSquarzon, no, unless you happen to be on v7.3.0 specifically, it won't work from `pwsh` either - and it looks like the v7.3.0 fix will be reverted and become _opt-in_ in v7.3.1+ - see https://github.com/PowerShell/PowerShell/issues/18694 – mklement0 Dec 05 '22 at 19:38
  • 1
    Unfortunately, I didn't tell the whole story: `cmd /c` is mandatory because that's what the (missing here) final command is expecting. My bad, OTOH, the first solution works very well for me with `cmd /C powershell`, so your generous answer solves my specific problem, while covering a more generic problem. Thank you! – Liviu Dec 05 '22 at 20:54
0

you can use the Invoke-Expression cmdlet to run a variable string as a command in PowerShell. For example: Invoke-Expression $commandString

  • [`Invoke-Expression` (`iex`) should generally be avoided](https://stackoverflow.com/a/51252636/45375), which is always worth mentioning. While a security risk is inherent in the scenario at hand - execution of a command stored in a string received from an outside source - a solution without `Invoke-Expression` is still possible and preferable in this case. – mklement0 Dec 05 '22 at 17:48