Powershell: ArgumentCompleter attribute usage

Created on 25 Jun 2018  路  14Comments  路  Source: PowerShell/PowerShell

image

This fails (EXPECTED TO WORK)
scriptblock variable gets passed to the attribute and fails for some reason
image

But this works
however when there is no variable but pure scriptblock - it works
image

Do i do something wrong?

Resolution-Answered

Most helpful comment

You can also delegate to a function, like

function completeme{
    param($c, $p, $w, $a, $fb)

...
}

function Verb-Noun{
   param(
     [ArgumentCompleter({completeme @args})
     [string] $First
   )
}


All 14 comments

I think what the error message is saying is that attributes in general only accept _literals_ as arguments - either as constants or as script-block _literals_.

In other words: a script block itself cannot be specified via a _variable_, as you've attempted.

Well, it is ok, but i think it would be good to be able to pass it as a variable.

If you want to use a variable to store the completer script block (e.g., in order to associate it with _multiple_ functions) you can use the Register-ArgumentCompleter _cmdlet_ instead of the ArgumentCompleter attribute.

You can also delegate to a function, like

function completeme{
    param($c, $p, $w, $a, $fb)

...
}

function Verb-Noun{
   param(
     [ArgumentCompleter({completeme @args})
     [string] $First
   )
}


@powercode: That's a great alternative, but I suggest only using it in the context of a _module_, so as to ensure that the delegate (helper function) is packaged with the function(s) that rely on it.

Well, function is a great option, but in case there is some 'dynamicity' is needed it is easier to generate a scriptblock, than a function

@eosfor: I see three basic scenarios:

  • (a) "Bake" a script-block literal directly in the function, with no external dependencies (other than relying on standard features and cmdlets to generate completions)

  • (b) In the context of a module, use a helper function that your reference from the script block the way @powercode suggests, so as to either use the same completer with multiple functions or to separate the implementation of a lengthy completer from the function's main code.

  • (c) Associate arbitrary completers with existing functions, possibly by someone other than the function author, using Register-ArgumentCompleter - though this technique could also be used module-internally. Completers are indeed passed as _script blocks_ to Register-ArgumentCompleter.

Does your scenario not fall into one of the 3 categories above?

@mklement0:

Yes, I think (a) is something I talking about. However if a script-block is big and complex, and we have a lot of parameters to "auto-complete", then a function definition will be really long and unreadable.

Using variables will give an option to move script-block code off a function definition. But avoiding complexity of generating a full-featured function, especially if a function is not needed for anything else but for completer.

IMHO of course.

@eosfor:

then a function definition will be really long and unreadable.

So in that case you can reference a unit of code the way @powercode suggests, whether that unit of code is
a function ([ArgumentCompleter({ completeme @args })])
or a script block ([ArgumentCompleter({ & $sb2 @args})])

avoiding complexity of generating a full-featured function

The bodies of functions are script blocks, so the only complexity that a function definition adds is the word function:

$sb2 = { ... }
# vs.
function sb2 { ... }

Aha, this is what i was looking for :). It still isn't obvious :D, IMHO. Kind of script-block calling another script-block, looks like a hack

$sb = {
    Get-Process | select -ExpandProperty name
}

function Verb-Noun {
    [CmdletBinding()]
    param (
        [ArgumentCompleter({ & $sb })]
        $name
    )    
    process {
    }

}

Especially (i'm not a dev however, so may misinterpret things), here there is a piece of code which takes a script-block directly (Line 45):

https://github.com/PowerShell/PowerShell/blob/c1c5344a8897262433141ecbc13bb06ac2c4bbef/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs#L21-L54

so my expectation were that it should take something of type ScriptBlock, like variable. But looks like this error is generated elsewhere.

But, like i said, this { & $sb } approach works for me.

@eosfor:

Note that in order for your completer to work fully as expected you must:

  • pass arguments through to it, as in @powercode's example: [ArgumentCompleter({ & $sb @Args })]

  • declare parameters in your script block so you can _access the part (prefix) of the argument to complete that the user has already typed_.

Here's the full example:

$sb = {
  param($cmdName, $paramName, $wordToComplete)
  Get-Process $wordToComplete* | select-Object -ExpandProperty name
}

function Verb-Noun {
  [CmdletBinding()]
  param (
      [ArgumentCompleter({ & $sb @Args })]
      $name
  )    
  process {
  }

}

Again, note that there's no advantage to using a script block over defining a function here.

As for the rationale behind only allowing literals (and constants $null, $True, and $False) in attribute arguments:

Others are better qualified to comment on that, but my guess is that the main reason is that function definitions are parsed _before_ execution begins, which means that at that time the value of variable $sb is not known yet.

By contrast, a script-block _literal_ can be parsed, and any variables it references aren't resolved until the block is later _invoked_ at runtime.

Kind of script-block calling another script-block, looks like a hack

Again, using a _function_ is the better choice.

@eosfor The restriction for static arguments in attibutes is a general check done at parse time. See SemanticChecks.cs

I am not really wanting to necro this thread but as someone who was fooling around with this recently, afaict if you use a function in an argument completer attribute then that function has to be exported with the module. I did not see if a script block had the same limitation so do not know if that's an advantage/disadvantage or not. If I figure that out I'll update this comment.

Update: seems maybe this is reported here https://github.com/PowerShell/PowerShell/issues/7265

Was this page helpful?
0 / 5 - 0 ratings