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.
The problem is that the dictionary of provisionally bound parameters, $fakeBoundParameters
, only contains information about other parameters if you haven't typed a prefix of a non-initial array element to be completed.
That is, with el1
and el2
representing previously typed / tab-completed array elements, pressing Tab after el1, el2,
causes the parameter at hand not to be included as an entry in $fakeBoundParameters
, so that the el1
and el2
values cannot be examined that way.
$fakeBoundParameter
dictionary passed to argument-completers, up to at least PowerShell 7.4.0-preview.3: a syntactically incomplete expression such as el1, el2,
seemingly prevents inclusion of the array elements provided so far; see GitHub issue #17975Therefore, for an array parameter being tab-completed without having typed at least one character at the start of the new element, any array elements previously typed / completed are in effect not reflected in $fakeBoundParameters
.
The workaround is to search the command AST passed reflected in the $commandAst
completer script-block for string-constant expressions that end end in .xml
, as shown below:
.xml
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
:
You got an error, because you didn't use the full type name as the base class name in your custom class
definition, System.Management.Automation.ArgumentCompleterAttribute
Attribute
suffix, i.e. ArgumentCompleter
does seem to work without a namespace qualifier.That said, instead of sub-classing this ArgumentCompleter
, you could simply use it directly, passing the same script block you used with Register-ArgumentCompleter
as an attribute property; that is, inside your Deploy-SignedWDACConfig
command, you can decorate the $PolicyPaths
parameter declaration with [ArgumentCompleter({ ... })]
.