powershellpowershell-coretab-completionpowershell-7.3

How to make a PowerShell argument completer that only suggests files not already selected?


I'm not sure if it's possible to do this so going to first explain what I want to happen.

my PowerShell module function has this parameter

[ValidateScript({ Test-Path $_ -PathType Leaf })][ValidatePattern("\.xml$")][parameter(Mandatory = $true)][string[]]$PolicyPaths,

It accepts multiple .xml files.

I've been using this argument completer for it:

$ArgumentCompleterPolicyPaths = {
    Get-ChildItem | where-object { $_.extension -like '*.xml' } | foreach-object { return "`"$_`"" }
}
Register-ArgumentCompleter -CommandName "Deploy-SignedWDACConfig" -ParameterName "PolicyPaths" -ScriptBlock $ArgumentCompleterPolicyPaths

It's been working fine. Now I want to improved it so that when I need to select multiple .xml files from the current working directory, and start selecting them by pressing Tab (without typing anything because I don't know the file names), the suggested files don't contain the ones I've already selected. The way I select them is by pressing Tab first and after each selected file, add a comma , and then press Tab again to select another from the suggestions.

I've tried 2 new argument completers but none of them work the way I want.

here is the first one:

$ArgumentCompleterPolicyPaths = {
  # Get the current command and the already bound parameters
  param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
  # Get the xml files in the current directory
  Get-ChildItem | Where-Object { $_.Extension -like '*.xml' } | ForEach-Object {
    # Check if the file is already selected
    if ($fakeBoundParameters.PolicyPaths -notcontains $_.FullName) {
      # Return the file name with quotes
      return "`"$_`""
    }
  }
}
Register-ArgumentCompleter -CommandName "Deploy-SignedWDACConfig" -ParameterName "PolicyPaths" -ScriptBlock $ArgumentCompleterPolicyPaths

and here is the second one:

# Define a class that inherits from ArgumentCompleterAttribute
class XmlFileCompleter : ArgumentCompleterAttribute {
  # Override the GetArgumentCompletionSuggestions method
  [System.Collections.Generic.IEnumerable[System.Management.Automation.CompletionResult]] GetArgumentCompletionSuggestions(
    [System.Management.Automation.CommandAst]$commandAst,
    [System.Management.Automation.CommandParameterAst]$parameterAst,
    [System.Collections.IDictionary]$fakeBoundParameters,
    [System.Management.Automation.Language.IScriptPosition]$cursorPosition
  ) {
    # Get all XML files in the current directory
    $xmlFiles = Get-ChildItem -Path . -Filter *.xml
    # Filter out the files that have already been selected
    $xmlFiles = $xmlFiles | Where-Object { $fakeBoundParameters[$parameterAst.ParameterName] -notcontains $_.Name }
    # Return the file names as completion results
    foreach ($xmlFile in $xmlFiles) {
      [System.Management.Automation.CompletionResult]::new($xmlFile.Name, $xmlFile.Name, 'ParameterValue', $xmlFile.Name)
    }
  }
}

The last throws this error in VS code:

Unable to find type [ArgumentCompleterAttribute].

P.S This is current behavior with my argument tab completer, see how it suggests the same file that I already selected: https://1drv.ms/u/s!AtCaUNAJbbvIhupw8-67jn6ScaBOGw?e=a35FoG

it's APNG file so just drag n drop it on a browser.


Solution

  • Register-ArgumentCompleter `
      -CommandName Deploy-SignedWDACConfig `
      -ParameterName PolicyPaths `
      -ScriptBlock {
        # Get the current command and the already bound parameters
        param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    
        # Find all string constants in the AST that end in ".xml"
        $existing = $commandAst.FindAll({ 
            $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] -and 
            $args[0].Value -like '*.xml' 
          }, 
          $false
        ).Value  
    
        # Get the xml files in the current directory
        Get-ChildItem -Filter *.xml | ForEach-Object {
          # Check if the file is already selected
          if ($_.FullName -notin $existing) {
            # Return the file name with quotes
            "`"$_`""
          }
        }
      }
    

    As for your attempt to use ArgumentCompleterAttribute: