根据另一个参数的已指定值用 Tab 键完成一个参数值

Tab-complete a parameter value based on another parameter's already specified value

这个自我回答的问题针对以下场景:

示例场景:

假设的 Get-Property 命令有一个接受任何类型对象的 -Object 参数,以及一个接受 属性 名称的 -Property 参数,其值从对象中提取。

现在,在键入 Get-Property 调用的过程中,如果已经为 -Object 指定了一个值,制表符完成 -Property 应该循环指定对象的名称(public) 个属性。

$obj = [pscustomobject] @{ foo = 1; bar = 2; baz = 3 }

Get-Property -Object $obj -Property # <- pressing <tab> here should cycle
                                    # through 'foo', 'bar', 'baz'

以下解决方案使用参数特定的 [ArgumentCompleter()] 属性作为 Get-Property 函数本身定义的一部分,但该解决方案类似地适用于通过 Register-CommandCompleter 命令。

限制

  • PowerShell 调用的自定义完成脚本块 ({ ... }) 从根本上说 只能看到通过 参数 指定的值,而不是通过管道.

    • 也就是说,如果您键入 Get-Property -Object $obj -Property <tab>,脚本块可以确定 $obj 的值将绑定到 -Object 参数,但这不起作用与
      $obj | Get-Property -Property <tab>(即使 -Object 被声明为管道绑定)。
  • 即使在通过参数指定的那些值中,也只有可以评估的值没有副作用 实际上在脚本块中是可访问的;具体来说,这意味着:

    • 文字 值(例如,-Object ([pscustomobject] @{ foo = 1; bar = 2; baz = 3 })
    • 简单变量引用(例如,-Object $obj)或属性-accessindex-access 表达式(例如,-Object $obj.Foo-Object $obj[0]
    • 值得注意的是,以下值不可访问:
      • 方法-调用结果(例如,-Object $object.Foo()
      • 命令输出(通过(...)$(...)@(...),例如
        -Object (Invoke-RestMethod http://example.org))
      • 此限制的原因是在实际提交命令之前评估这些值可能会产生不良副作用和/或可能需要很长时间才能完成。
function Get-Property {

  param(

    [object] $Object,

    [ArgumentCompleter({

      # A fixed list of parameters is passed to an argument-completer script block.
      # Here, only two are of interest:
      #  * $wordToComplete: 
      #      The part of the value that the user has typed so far, if any.
      #  * $preBoundParameters (called $fakeBoundParameters 
      #    in the docs):
      #      A hashtable of those (future) parameter values specified so 
      #      far that are side effect-free (see above).
      param($cmdName, $paramName, $wordToComplete, $cmdAst, $preBoundParameters)

        # Was a side effect-free value specified for -Object?
        if ($obj = $preBoundParameters['Object']) {

          # Get all property names of the objects and filter them
          # by the partial value already typed, if any, 
          # interpreted as a name prefix.
          @($obj.psobject.Properties.Name) -like "$wordToComplete*"

        }
      })]
    [string] $Property

  )

  # ...

}