Peyre
Peyre

Reputation: 573

powershell v2 function, how to have a default value for the 1st argument w/o naming the 2nd?

the problem is quite simple. I have a function with two arguments and I would like to be able to call it without providing the first one when it has its default value, without naming the second one.
for example (this is not my real function but a simple example to show what I mean)

function foo 
{ param([int]$int=-1,[string]$str="''") 
  $int
  $str
}

I was hoping that forcing the type in the argument list will make PS bind the right value to the right argument but it seams that it's not the case

PS C:\> foo
-1
''
PS C:\> foo 1
1
''
PS C:\> foo 1 x
1
x
PS C:\> foo -str x
-1
x
PS C:\> foo x
foo : Impossible de traiter la transformation d'argument sur le paramètre «int». Impossible de convertir la valeur «x» en type «System.Int32». Erreur: «Le format de la chaîne
d'entrée est incorrect.»
Au caractère Ligne:1 : 5
+ foo x
+     ~
    + CategoryInfo          : InvalidData : (:) [foo], ParameterBindingArgumentTransformationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,foo

is what I want possible in PS ?

EDIT

here's a better example of what I'm trying to do

test.ps1

[cmdletbinding()]
param(
    [parameter()]
    [string[]]$___where=@('*')
)

function InvokeOn {
[cmdletbinding()] 
param(
    [string[]]
    ${___names}=@('*'), 

    [scriptblock]
    ${___block}
)
    $___doExec = ($___names -contains '*')
    foreach($___name in $___names) { $___doExec = ($___doExec -or ($___where -contains $___name)) }
    if($___doExec) { &$___block }
}

$null = new-item alias:__LOG__ -value InvokeOn

__LOG__ c1    { Write-host '-- 1 --' }
__LOG__ c2    { Write-host '-- 2 --' }

__LOG__ c1,c2 { Write-host '-- 1 or 2 --' }

__LOG__ { Write-host 'always, defaulted' }
__LOG__  -___block { Write-host 'always, named'  }

and a few run

PS C:\> .\test
always, named
PS C:\> .\test c1
-- 1 --
-- 1 or 2 --
always, named
PS C:\> .\test c2
-- 2 --
-- 1 or 2 --
always, named
PS C:\> .\test c2,c1
-- 1 --
-- 2 --
-- 1 or 2 --
always, named

as you can see, __LOG__ { Write-host 'always, defaulted' } never fires as PS binds the scriptblock the wrong parameter.

Parameters names are on purpose complex and should not even be known by the developer using the aliased function.

Swapping the parameters is not practical as the scriptblock may be long and even for short ones, make the case where __LOG__ will fire less readable.

SOLUTION

applying majkinetor idea, I modified my code this way

function InvokeOn {
[cmdletbinding()] 
param(
    [string[]]
    ${___names} = @('*'),

    [scriptblock]
    ${___block}
)
    if(!$PSBoundParameters.ContainsKey('___block')) { $___names,$___block = @('*'),[scriptblock]::create($___names[0]) }
    $___doExec = ($___names -contains '*')
    foreach($___name in $___names) { $___doExec = ($___doExec -or ($___where -contains $___name)) }
    if($___doExec) { &$___block }
}

and now it works as expected :)

Upvotes: 1

Views: 409

Answers (2)

Ansgar Wiechers
Ansgar Wiechers

Reputation: 200203

I consider hiding parameter names from the user and evaluating parameters solely based on their type a Bad Idea™ and strongly recommend against doing this.

With that said, if you for some obscure reason you absolutely must have it this way, I'd drop named parameters entirely and use the $args automatic variable instead.

function foo {
  # define default values for your "parameters"
  $int = -1
  $str = "''"
  ...

  # evauluate $args and override default values
  foreach ($arg in $args) {
    switch ($arg.GetType().FullName) {
      'System.Int32'  { $int = $arg }
      'System.String' { $str = $arg }
      ...
      default { throw "Unrecognized type ${_}." }
    }
  }

  # handle missing arguments if required
  # Example:
  #   if ($args.Count -eq 0) { throw "No arguments provided." }

  ...
}

Upvotes: 0

majkinetor
majkinetor

Reputation: 9036

You could do something like:

 function foo 
 { 
    param ($int=-1,[string]$str="''") 
    if ($int.gettype().Name -eq 'String') { $str = $int; $int = -1 }
    $int
    $str
 }

Notice - $int must not have type.

Upvotes: 3

Related Questions