1

I need to execute an external command from PowerShell as a string.

Is it possible to run an external command (ping in this example) with parameters via dot-sourcing or something similar?

Or do you have to use Invoke-Expression?

# This works:
$commandText = "ping"
. "$commandText"

# This results in: The term 'ping -t 127.0.0.1' is not recognized as a name of a cmdlet, function, script file, or executable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
$commandText = "ping -t 127.0.0.1"
. "$commandText"

I've tried & and quoting the parameters but can't get anything to work.

mklement0
  • 382,024
  • 64
  • 607
  • 775
Per
  • 13
  • 2

3 Answers3

0

Put the argument in an array:

$commandText = "ping"
[string[]]$a = "-t","127.0.0.1"
 . "$commandText" $a

output:

PS D:\TEMP> . "$commandText" $a

Pinging 127.0.0.1 with 32 bytes of data:
Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
Reply from 127.0.0.1: bytes=32 time<1ms TTL=128

Ping statistics for 127.0.0.1:
    Packets: Sent = 3, Received = 3, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 0ms, Maximum = 0ms, Average = 0ms
Control-C
Luuk
  • 12,245
  • 5
  • 22
  • 33
  • Thank you for the reply, I am aware of this method but for my use case it will become to difficult to construct and read - I will sometimes have 8-10 arguments. I don't have enough reputation yet on this account to upvote but you will get an upvote once I get about 15 rep. :) – Per May 20 '23 at 16:12
0

Put the command inside of a scriptblock, and cast it as a scriptblock. This can be invoked by the call operator, ampersand. As follows:

$command = [scriptblock]{ping -t 127.0.0.1 }
'Getting ready to run the command inside the variable'
& $command
Walter Mitty
  • 18,205
  • 2
  • 28
  • 58
  • Note that `{ ... }` _is_ a script block (literal), so the `[scriptblock]` cast is redundant. Conversely, with a _string_ as input (which what the question seems to be about), such a cast won't work. – mklement0 May 20 '23 at 15:33
  • Thank you for the reply but I need the "ping -t 127.0.0.1"-part to be a string. – Per May 20 '23 at 16:09
  • 1
    @mklement0 - you are right. I mistakenly thought that the cast was necessary to prevent premature evaluation of the scriptblock. – Walter Mitty May 20 '23 at 16:14
  • @Per - why do you need a string? – Walter Mitty May 20 '23 at 16:15
  • @Walter Mitty - I need to output the command used to console/log files together with the result which is why I need to have the entire command as a string. – Per May 20 '23 at 17:17
  • You could always cast it to a string: $string = [string]$command – Walter Mitty May 20 '23 at 18:52
0

If you're given a string representing a whole command line - i.e. a string containing both an executable name/path and its arguments, you indeed can not use ., the dot-sourcing operator, nor can you use &, the call operator, because these operators only accept an executable name or path as their first argument, with the executable's arguments needing to be specified separately.

  • If, by contrast, you want to construct a command line yourself, programmatically, use the array-based technique shown in Luuk's answer.

  • Note that with external programs such as ping, . behaves the same as & - only with PowerShell scripts or script blocks do these operators behave differently: . runs the script (block) directly in the caller's scope, & - the more common case as - runs it in a child scope.
    Also note that use of & for invoking scripts and external programs whose names/paths are neither quoted nor are expressed via variables is optional.

Invoke-Expression (iex) - which should generally be avoided - is the simplest solution, but the usual caveat applies:

  • Given that Invoke-Expression allows execution of arbitrary code stored in a string, be sure that you either fully control or implicitly trust the string's content.
$commandText = "ping -t 127.0.0.1"
# !! Be sure that you trust $command to do what you expect.
Invoke-Expression $commandText

Alternatively, you can create a script block { ... } and invoke it on demand with & - but note that this involves the same risks as using Invoke-Expression:

$commandText = "ping -t 127.0.0.1"

# Parse the command line into a script block.
$scriptBlock = [scriptblock]::Create($commandText)

# Invoke the script block, which you can do repeatedly.
& $scriptBlock
  • For repeated invocations this is slightly more efficient than using Invoke-Expression every time, though that probably won't matter in practice.

  • At least hypothetically, you can use the technique to support passing additional arguments ad hoc on each invocation (@args is the splatted form of the automatic $args variable); e.g.:

    $commandText = 'cmd /c echo one'
    
    # Append @args to the original command line, which expands to
    # to the arguments passed on invocation of the script block.
    # Note: 
    #   Placing @args *last* may not always work.
    $scriptBlock = [scriptblock]::Create($commandText + ' @args')
    
    # Pass additional arguments on invocation.
    # -> 'one two three'
    & $scriptBlock two three
    

If the command line was written for a different shell / for a no-shell invocation:

Command lines written for cmd.exe and for no-shell invocation contexts such as scheduled tasks may or may not work when executed directly by PowerShell, depending on the specific command, due to PowerShell's different parameter syntax.
Notably, PowerShell has more metacharacters than cmd.exe that need quoting or escaping, such as @, (, ), and {, }, and its escape character (`, the so-called backtick) is different (cmd.exe uses ^).

In that case, it is simplest to pass the command line to the cmd.exe CLI, using its /c parameter:

$commandText = "ping -t 127.0.0.1"
# Let cmd.exe execute the command line, via cmd /c
cmd /c $commandText

Note:

  • On Unix-like platforms, use /bin/sh -c $commandText instead.
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Thank you for the detailed and very helpful answer. It confirms my suspicions. I know of the downsides of using Invoke-Expression but in this case I'll have to live with them. My use case is running commands locally and the input will come from the same user that is running the script anyway. The args array option will become very difficult to read with 8+ arguments. I need to output the command used to console/log files together with the result which is why I need to have the entire command as a string. – Per May 20 '23 at 16:08
  • Glad to hear it helped, @Per; my pleasure, and thanks for the clarification of the use case. – mklement0 May 20 '23 at 16:35