I’m trying to write a wrapper function in PowerShell that basically evaluates the first parameter and based on that runs a program on the computer. All the remaining parameters to the wrapper 开发者_开发技巧function should then be passed to the program that is ran as well.
So it should look something like this:
function test ( [string] $option )
{
if ( $option -eq 'A' )
{
Write-Host $args
}
elseif ( $option -eq 'B' )
{
. 'C:\Program Files\some\program.exe' $args
}
}
Now just adding $args
does not work, so what do I have to do to make it work? Another option would probably be using Invoke-Expression
, but it feels a bit like eval
so I want to avoid if possible, and in addition I think doing it like that would limit me to string-only parameters right? If possible I would want to have the full support for the wrapped program/cmdlet - basically like a dynamic alias. Is that even possible?
This sort of does what you ask. You may run into trouble if you need to pass dash-prefixed options to the executable that conflict or cause ambiguity with the PowerShell common parameters. But this may get you started.
function Invoke-MyProgram
{
[CmdletBinding()]
Param
(
[parameter(mandatory=$true, position=0)][string]$Option,
[parameter(mandatory=$false, position=1, ValueFromRemainingArguments=$true)]$Remaining
)
if ($Option -eq 'A')
{
Write-Host $Remaining
}
elseif ($Option -eq 'B')
{
& 'C:\Program Files\some\program.exe' @Remaining # NOTE: @ not $ (splatting)
}
}
What you have written does work. Note that what is there is $args
is the unnamed arguments that are over and above the parameters expected by the function.
So if you call test as
test -option "A" 1 2 3
$args
will have 1,2,3
Note that if you call test as
test -option "A" -other "B" 1 2 3
$args
will have -other,B,1,2,3
Your solution works as-is for external programs (such as your
C:\Program Files\some\program.exe
example): you can always pass an array of values (which is what$args
is) to an external program, and its elements will be passed as individual arguments (stringified, if necessary).You can make your solution work with any command if you change
$args
to@args
[1], to take advantage of a PowerShell parameter-passing technique called splatting:
function test ( [string] $option )
{
if ( $option -eq 'A' )
{
Write-Host $args
}
elseif ( $option -eq 'B' )
{
# Use @args to also support passing *named* arguments
# through to *PowerShell* commands.
& $someCommand @args
}
}
Caveats:
The automatic
$args
variable, which collects all arguments for which no parameter was declared, is only available in simple (non-advanced) functions and scripts; advanced functions and scripts - those that use the[CmdletBinding()]
attribute and/or[Parameter()]
attributes - require that all potential parameters be declared.PowerShell has built-in magic that makes the automatic array variable
$args
also support passing named parameters through via splatting, which no custom array or collection supports.- By contrast, custom arrays and collections only support splatting positional (unnamed) arguments, which, however, covers all calls to external programs.
- When calling PowerShell commands, this limitation is problematic, however: For instance, if you wanted to pass the named argument
-Path C:\
through to theSet-Location
cmdlet via splatting, using a custom collection parameter declared viaValueFromRemaining Arguments
, as shown in OldFart's answer (Set-Location @Remaining
), would not work; to support passing through named arguments (other than via@args
, if available), you must use hashtable-based splatting.
Therefore, if your function is an advanced one and you need to support passing named arguments through to other PowerShell commands, a different approach is required: this answer shows two alternatives.
[1] With external programs, there is a corner case where @args
behaves differently from $args
, namely if the $args
array contains --%
, the stop-parsing symbol: @args
recognizes it, $args
treats it as a literal.
精彩评论