How to get <tab> value suggestions dynamically, without throwing an error if user provided a value that does not exist?

Lets say, I have two functions get-foo
and new-foo
, that I am using to read and edit a tree structure resource. Its really nothing sophisticated, I am using the file system to implement the structure.
The issue am having is get-foo
works as I want it to, it will force the user to only input values that are found in the tree structure.
My issue is, new-foo
is not working as I want it to, I would like values from the tree structure to be suggested similar to how they are with get-foo
, but the user must be able to input arbitrary values, so they can extend the structure. Currently new-foo
will throw an error if the value does not exist.
My code:
Function get-foo{
param(
[ValidateSet([myTree], ErrorMessage = """{0}"" Is not a valid structure")]
[string]$name
)
$name
}
Function new-foo{
param(
[ValidateSet([colNames])]
[string]$name
)
$name
}
Class myTree : System.Management.Automation.IValidateSetValuesGenerator{
[string[]] GetValidValues(){
return [string[]] (Get-ChildItem -path "C:\temp" -Recurse |ForEach-Object {($_.FullName -replace 'c:\\temp\\')})
}}
get-foo
and new-foo
both have a name
parameter, the user is expected to provide a name. The functions check the directory c:\temp
, for valid names.
For example, if c:temp
was as follows:
C:\temp\animal
C:\temp\animals\cat
C:\temp\animals\dog
C:\temp\animals\fish
C:\temp\colours
C:\temp\colours\green
C:\temp\colours\orange
C:\temp\colours\red
C:\temp\plants
C:\temp\plants\carrots
C:\temp\plants\patato
C:\temp\plants\vegatables
Then with get-foo -name anima...<tab>
, then the auto competition examples would be:
get-foo -name animals\cat
get-foo -name animals\dog
get-foo -name animals\fish
But new-foo
will throw an error if the value name
does not already exist. Is there a mechanism that I can use to still get dynamic autocompletion but without the error? I checked the parameter attribute section, from my reading only validatSet
seems applicable.
Am on pwsh 7.4
Answer
You can implement IArgumentCompleter
in your existing class to suggest values but not validate them:
using namespace System
using namespace System.Collections
using namespace System.Collections.Generic
using namespace System.Management.Automation
using namespace System.Management.Automation.Language
class MyTree : IValidateSetValuesGenerator, IArgumentCompleter {
[string[]] GetValidValues() {
return $this.GetValues()
}
[IEnumerable[CompletionResult]] CompleteArgument(
[string] $commandName,
[string] $parameterName,
[string] $wordToComplete,
[CommandAst] $commandAst,
[IDictionary] $fakeBoundParameters) {
$values = foreach ($value in $this.GetValues()) {
[CompletionResult]::new($value)
}
return [CompletionResult[]] @($values)
}
hidden [string[]] GetValues() {
return Get-ChildItem -Path 'c:\\temp\\' -Recurse |
ForEach-Object { $_.FullName -replace 'c:\\temp\\' }
}
}
Then the implementation for Get-Foo
remains the same, using [ValidateSet([MyTree], ...)]
but for New-Foo
you'd use [ArgumentCompleter(...)]
instead:
function New-Foo {
param(
[ArgumentCompleter([MyTree])]
[string] $Name
)
}
Other alternative that doesn't require to implement IArgumentCompleter
would be to use Register-ArgumentCompleter
, you can find an example in this answer.