The simplest and most convenient way to pass unknown arguments through is by spatting
the automatic $args
array - as @args
- in a simple function or script (one that neither uses a [CmdletBinding()]
nor [Parameter()]
attributes):
# Note: @args rather than $args makes the function work with named
# arguments for PowerShell commands too - see explanation below.
function runArtisanCommand { php artisan @args }
# As in your question: Define alias 'art' for the function
# Note: Of course, you could directly name your *function* 'art'.
# If you do want the function to have a longer name, consider one
# that adheres to PowerShell's Verb-Noun naming convention, such as
# 'Invoke-ArtisanCommand'.
Set-Alias art runArtisanCommand
As an aside: Since the target executable, php
, is neither quoted nor specified based on a variable or expression, it can be invoked as-is; otherwise, you would need &
, the call operator - see this answer for background information.
As for what you tried:
The problem was that use of -c
as a pass-through argument only works if you precede it with --
:
# OK, thanks to '--'
art -- route:list -c
--
tells PowerShell to treat all remaining arguments as unnamed (positional) arguments, instead of trying to interpret tokens such as -c
as parameter names.
Without --
, -c
is interpreted as referring to your -command
parameter (the parameter you declared as $command
with ValueFromRemainingArguments = $true
), given that PowerShell allows you to specify name prefixes in lieu of full parameter names, as long as the given prefix is unambiguous.
Because a parameter of any type other than [switch]
requires an associated argument, -c
(aka -command
) failed with an error message to that effect.
You could have avoided the collision by naming your parameter so that it doesn't collide with any pass-through parameters, such as by naming it -_args
(with parameter variable $_args
):
function runArtisanCommand
{
param(
# Note: `Mandatory = $false` and `Position = 0` are *implied*.
[Parameter(ValueFromRemainingArguments)]
$_args
)
php artisan @_args
}
However, given that use of a [Parameter()]
attribute implicitly makes your function an advanced function, it invariably also accepts common parameters, such as -ErrorAction
, -OutVariable
, -Verbose
... - all of which can be passed by unambiguous prefix / short alias too; e.g., -outv
for -OutVariable
, or alias -ea
for ErrorAction
; collisions with them cannot be avoided.
Therefore, intended pass-through arguments such as -e
still wouldn't work:
# FAILS, because -e ambiguously matches common parameters -ErrorAction
# and -ErrorVariable.
PS> art router:list -e
Parameter cannot be processed because the parameter name 'e' is ambiguous.
Possible matches include: -ErrorAction -ErrorVariable.
Again, --
is needed:
# OK, thanks to '--'
art -- router:list -e
Summary:
Especially for functions wrapping calls to external programs, such as php.exe
, using a simple function with @args
, as shown at the top, is not only simpler, but also more robust.
For functions wrapping PowerShell commands (with explicitly declared parameters):
- a simple function with
@args
works too,
- but if you also want support for tab-completion and showing a syntax diagram with the supported parameters, by passing
-?
, or via Get-Help
, consider defining an (invariably advanced) proxy (wrapper) function via the PowerShell SDK - see below.
Optional background information: Pass-through arguments in PowerShell
As Mathias R. Jessen points out, the simplest way to pass (undeclared) arguments passed to a function or script through to another command is to use the automatic $args
variable, which is an automatically populated array of all the arguments passed to a simple function or script (one that isn't advanced, through use of the [CmdletBinding()]
and/or [Parameter()]
attributes).
As for why @args
(splatting) rather than $args
should be used:
Using $args
as-is in your wrapper function only works for passing positional arguments through (those not prefixed by the parameter name; e.g., *.txt
), as opposed to named arguments (e.g., -Path *.txt
).
If the ultimate target command is an external program (such as php.exe
in this case), this isn't a problem, because PowerShell of necessity then treats all arguments as positional arguments (it cannot know the target program's syntax).
However, if a PowerShell command (with formally declared parameters) is ultimately called, only splatting the $args
array - which syntactically means us of @args
instead - supports passing named arguments through.[1]
Therefore, as a matter of habit, I suggest always using @args
in simple wrapper functions, which equally works with external programs.[2]
To give an example with a simple wrapper function for Get-ChildItem
:
# Simple wrapper function for Get-ChildItem that lists recursively
# and by relative path only.
function dirTree {
# Use @args to make sure that named arguments are properly passed through.
Get-ChildItem -Recurse -Name @args
}
# Invoke it with a *named* argument passed through to Get-ChildItem
# If $args rather than @args were used inside the function, this call would fail.
dirTree -Filter *.txt
Using a proxy function for more sophisticated pass-through processing:
The use of @args
is convenient, but comes at the expense of not supporting the following:
tab-completion, given that tab-completion only works with formally declared parameters (typically with a param(...)
block).
showing a syntax diagram with the supported parameters, by passing -?
, or via Get-Help
To overcome these limitations, the parameter declarations of the ultimate target command must be duplicated in the (then advanced) wrapper function; while that is cumbersome, PowerShell can automate the process by scaffolding a so-called proxy (wrapper) function via the PowerShell SDK - see this answer.
Note:
With respect to common parameters such as -ErrorAction
, it is the proxy function itself that (automatically) processes them, but that shouldn't make a difference to the caller.
Scaffolding a proxy function only works with PowerShell commands, given that PowerShell has no knowledge of the syntax of external programs.
- However, you can manually duplicate the parameter declarations of the external target program.
[1] Note that the automatic $args
array has built-in magic to support this; passing named arguments through with splatting is not supported with a custom array and requires use of a hash table instead, as discussed in the help topic about splatting linked to above.
[2] In fact, only @args
also supports the correct interpretation of --%
, the stop-parsing symbol.