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([myTree])]
[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
Edit: I made a slight edit above, new-foo
's -name
parameter was not using the same validateSet class as set-foo
, they are both using the same validateSet class.
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 {
hidden [StringComparison] $_comparer = [StringComparison]::InvariantCultureIgnoreCase
[string[]] GetValidValues() {
return $this.GetValues()
}
[IEnumerable[CompletionResult]] CompleteArgument(
[string] $commandName,
[string] $parameterName,
[string] $wordToComplete,
[CommandAst] $commandAst,
[IDictionary] $fakeBoundParameters) {
$values = foreach ($value in $this.GetValues()) {
if ($value.StartsWith($wordToComplete, $this._comparer)) {
[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.