5

I'd like to pass all arguments that were given to the script and execute.

For example, given execute.ps1 script:

Invoke-Expression $($args[0])

I can run:

.\execute.ps1 hostname
myhostname

Same with two parameters with this script:

Invoke-Expression "$($args[0]) $($args[1])"

and executing it by:

.\execute.ps1 echo foo
foo

How I can make the script universal to support unknown number of arguments?

For example:

.\execute.ps1 echo foo bar buzz ...

I've tried the following combinations which failed:

Invoke-Expression $args

Invoke-Expression : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Command'. Specified method is not supported.


Invoke-Expression [system.String]::Join(" ", $args)

Invoke-Expression : A positional parameter cannot be found that accepts argument 'System.Object[]'.


Invoke-Expression $args.split(" ")

Invoke-Expression : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Command'. Specified method is not supported.


Invoke-Expression [String] $args

Invoke-Expression : A positional parameter cannot be found that accepts argument 'System.Object[]'.

kenorb
  • 155,785
  • 88
  • 678
  • 743
  • could you not just specify input parameters for your .ps1 file? – WayneA Jul 26 '18 at 17:07
  • I need it dynamic and I don't know how many parameters there are. I've one use case when I've aws-ecr-login.ps1 which logins to different AWS role and executes docker commands. Then I'd like to allow user to pass arguments into the script, so it's invoked after the login process, but there could be unknown number of parameters. The use case doesn't really matters, therefore I've simplified the example as I could. – kenorb Jul 26 '18 at 17:15
  • [I would caution against the use of `Invoke-Expression`.](https://blogs.msdn.microsoft.com/powershell/2011/06/03/invoke-expression-considered-harmful/) – Bill_Stewart Jul 26 '18 at 17:33

3 Answers3

11

I recommend Bill_Stewart's answer to avoid issues with the command itself (first argument) having spaces. But even with that answer, you would have to be careful to individual quote arguments, with the caveat that that itself itself a complicated thing.


You can just do:

 Invoke-Expression "$args"

By default converting to a string that way will join it with spaces (technically, join it with the default output field separator, which is the value of $OFS, which defaults to a space).

You can also do a manual join as in Wayne's answer.

briantist
  • 45,546
  • 6
  • 82
  • 127
  • @Bill_Stewart true, though neither will the other answers. This method of passing parameters will require the invoker to understand that PowerShell will interpret the first set of quotes, and repeat-quote as necessary `execute.ps1 echo '"hello there"'`. This is true of any shell really though, isn't it? – briantist Jul 26 '18 at 18:00
  • I don't recommend this answer because [`Invoke-Expression` is considered harmful](https://devblogs.microsoft.com/powershell/invoke-expression-considered-harmful/). – Bill_Stewart May 30 '20 at 13:58
  • @Bill_Stewart I've updated my answer, but most of what's in that article isn't addressed by using `&` alone either; and of course the injection angle is only going to be addressed by not trying to execute arguments. – briantist May 30 '20 at 14:58
4

$args is a whitespace delimited array of strings created from the imput

   Invoke-Expression -Command "$($args -join " ")"

Re-joining it with a whitespace character, and passing it to invoke-expression as a string works for me.

WayneA
  • 339
  • 1
  • 7
  • 1
    Could also be done manually a little more simply: `-Command ($args -join ' ')` – briantist Jul 26 '18 at 17:18
  • I tend to get a little "cast as string" happy in my scripts. I can never remember all of the rules of what works when ;) – WayneA Jul 26 '18 at 17:20
  • 1
    Seems harmless to do a subexpression (any performance impact would be negligible unless it's a really big loop) , but [subexpressions have some bugs that you may never run into, but really suck](https://stackoverflow.com/a/47877473/3905079) so I try to avoid them when they aren't needed. – briantist Jul 26 '18 at 17:22
  • If you have arrays in $args (e.g., one of parameters is 1,2,3), they are passed as the string "System.Object[]" because each item in $args is converted to string using ToString() before concatenation, and Array.ToString() loses data. – Michael Yutsis Oct 02 '19 at 15:49
3

My recommendation would be to avoid Invoke-Expression and use & instead. Example:

$command = $args[0]
$params = ""
if ( $args.Count -gt 1 ) {
  $params = $args[1..$($args.Count - 1)]
}
& $command $params

Of course, parameters containing spaces would still need to contain embedded quotes.

Bill_Stewart
  • 22,916
  • 4
  • 51
  • 62
  • If `$command` is a function, it will receive just one parameter `$params` which is an array, instead of array of parameters as it expects. To avoid it, write the last line in this way: `& $command @params` – Michael Yutsis Oct 02 '19 at 16:27
  • The question is about when the first element of the array is the command name and the subsequent elements are the arguments. (That's why my answer is written the way it is.) – Bill_Stewart Oct 02 '19 at 16:38