views:

81

answers:

2

I have an advanced function in PowerShell, which roughly looks like this:

function Foo {
    [CmdletBinding]
    param (
        [int] $a = 42,
        [int] $b
    )
}

The idea is that it can be run with either two, one or no arguments. However, the first argument to become optional is the first one. So the following scenarios are possible to run the function:

Foo a b    # the normal case, both a and b are defined
Foo b      # a is omitted
Foo        # both a and b are omitted

However, normally PowerShell tries to fit the single argument into a. So I thought about specifying the argument positions explicitly, where a would have position 0 and b position 1. However, to allow for only b specified I tried putting a into a parameter set. But then b would need a different position depending on the currently-used parameter set.

Any ideas how to solve this properly? I'd like to retain the parameter names (which aren't a and b actually), so using $args is probably a last resort. I probably could define two parameter sets, one with two mandatory parameters and one with a single optional one, but I guess the parameter names have to be different in that case, then, right?

+3  A: 

Ideas on how to solve this properly? No. I've argued for a similar feature where if a parameter at say position 0 is pipeline bound, the "position" of any further parameters slide down by one so you could continue to provide positional parameters instead of having to resort to all named parameters. The team thought this would be too confusing for folks - having parameters shift position. This behavior is a good reason to put your pipeline bound parameters at the end of your positional parameters. :-)

BTW with advanced functions, $args isn't an option. Kind of like $_ - it doesn't apply. Use ValueFromRemainingArguments instead e.g.:

    [Parameter(ValueFromRemainingArguments=$true)]
    $rest
Keith Hill
Heh Keith, I think we posted those within a minute of each other. Great minds procrastinate alike!
x0n
Ok, I don't think I desperately need it to be an advanced function. In something as simple as `function foo { ... }` I can use both `$args` and `$input`, right?.
Joey
Keith Hill
+1  A: 

I'm afraid you won't be able to do this. The closest you could get involves having to name the first argument. An optional unnamed positional argument must be last in the set.

function test-optional {
    [cmdletbinding(defaultparametersetname="SingleOrNone")]
    param(
        [parameter(
            parametersetname="Both",
            mandatory=$true,
            position=1)]      
        [int]$A,

        [parameter(
            parametersetname="Both",
            mandatory=$false,
            position=2)]
        [parameter(
            parametersetname="SingleOrNone",
            mandatory=$false,
            position=1)]
        [int]$B   
    )

    end {
        "parameterset: {0}; a: $a; b: $b" -f $pscmdlet.parametersetname, $a, $b
    }
}

test-optional 1 # ok
test-optional 3 4 # fail
test-optional -a 3 4 # ok
test-optional -a 1 # ok
test-optional -b 1 # ok
test-optional -a 1 -b 2 # ok
test-optional # ok
x0n