11

I have a PowerShell 7.1 helper script that I use to copy projects from subversion to my local device. I'd like to make this script easier for me to use by enabling PowerShell to auto-complete parameters into this script. After some research, it looks like I can implement an interface to provide valid parameters via a ValidateSet.

Based on Microsoft's documentation, I attempted to do this like so:

[CmdletBinding()]
param (
    [Parameter(Mandatory)]
    [ValidateSet([ProjectNames])]
    [String]
    $ProjectName,

    #Other params
)

Class ProjectNames : System.Management.Automation.IValidateSetValuesGenerator {
    [string[]] GetValidValues() {
        # logic to return projects here.
    }
}

When I run this, it does not auto-complete and I get the following error:

❯ Copy-ProjectFromSubversion.ps1 my-project
InvalidOperation: C:\OneDrive\Powershell-Scripts\Copy-ProjectFromSubversion.ps1:4
Line |
   4 |      [ValidateSet([ProjectNames])]
     |      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Unable to find type [ProjectNames].

This makes sense since the class isn't defined until after the parameters. So I moved the class above the parameters. Obviously this is a syntax error. So how do I do this? Is it not possible in a simple PowerShell script?

10
  • That's not based of the documentation. Its basically saying, you have to define the Dynamic Parameter using the keyword DynmaicParam, which it in itself wil be the parameter displayed. It also goes to say it can change based off previous Parameter selections. Commented Aug 17, 2021 at 22:45
  • 1
    Hi @AbrahamZinala. I can see how my question may have been confusing. I'm not asking about dynamic parameters, but rather a dynamic validate set. Dynamic parameters are parameters that are only available under certain conditions. A dynamic validate set is a validate set that is determined at runtime. An example of a dynamic validate set might be the validate set of the path parameter of get-childitem. Commented Aug 18, 2021 at 4:29
  • "Dynamic parameters are parameters that are only available under certain condition", can you explain this some more? As far as I'm aware, this isn't true. I use dynamic parameters that include a validation set based of a Get-ChildItem instance from multiple folders in a lot of my scripts. Do you mind rephrasing what you're asking and/or, provide an example of what you mean? @Jason Commented Aug 18, 2021 at 4:57
  • 1
    Hi @AbrahamZinala. Check out this section in the documentation from Microsoft: learn.microsoft.com/en-us/powershell/module/… Commented Aug 18, 2021 at 13:03
  • @AbrahamZinala, to give a specific example: Get-ChildItem's dynamic -File and -Directory parameters are only available for file-system provider locations (because they only make sense there). Commented Aug 18, 2021 at 22:35

2 Answers 2

17

Indeed, you've hit a catch-22: for the parameter declaration to work during the script-parsing phase, class [ProjectNames] must already be defined, yet you're not allowed to place the class definition before the parameter declaration.

The closest approximation of your intent using a stand-alone script file (.ps1) is to use the ValidateScript attribute instead:

[CmdletBinding()]
param (
  [Parameter(Mandatory)]
  [ValidateScript(
    { $_ -in (Get-ChildItem -Directory).Name },
    ErrorMessage = 'Please specify the name of a subdirectory in the current directory.'
  )]
  [String] $ProjectName # ...
)

Limitations:

  • [ValidateScript] does not and cannot provide tab-completion: the script block, { ... }, providing the validation is only expected to return a Boolean, and there's no guarantee that a discrete set of values is even involved.

  • Similarly, you can't reference the dynamically generated set of valid values (as generated inside the script block) in the ErrorMessage property value.

The only way around these limitations would be to duplicate that part of the script block that calculates the valid values, but that can become a maintenance headache.

To get tab-completion you'll have to duplicate the relevant part of the code in an [ArgumentCompleter] attribute:

[CmdletBinding()]
param (
  [Parameter(Mandatory)]
  [ValidateScript(
    { $_ -in (Get-ChildItem -Directory).Name },
    ErrorMessage = 'Please specify the name of a subdirectory in the current directory.'
  )]
  [ArgumentCompleter(
    {
      param($cmd, $param, $wordToComplete)
      # This is the duplicated part of the code in the [ValidateScipt] attribute.
      [array] $validValues = (Get-ChildItem -Directory).Name
      $validValues -like "$wordToComplete*"
    }
  )]
  [String] $ProjectName # ...
)
Sign up to request clarification or add additional context in comments.

3 Comments

@mklement0 you said this is the closest approximation using a stand-alone script. If we drop the single-script constraint, could one still use a class defined in a separate file (e.g. in a module)?
@alazyworkaholic, yes, if you use a module that exports functions, these functions can be defined using class and enum definitions that are part of the same module. And, yes, if a script references class and enum definitions that are already loaded at the time the script is invoked, that works too. Note that if you want to use a module as a source of class and enum definitions that can be explicitly referenced from outside that module, the importer of the module must use using module ... instead of Import-Module ...
I just noticed that the ValidateScript can actually call other script files :) As in [ValidateScript ( {$_ -in (& $PSScriptRoot\helpers\MyBigValidationScriptfile.ps1)} )]. I'm using it for my own part to check if the argument matches any of the computers within a specific OU in my AD.
1

Answer is correct, but Error Message is introduced in Powershell 6. Also, the reason, I assume, you are getting is because you are using earlier version as well. Powershell 5.1 does not have IValidateSetValuesGenerator (interface).

If you are using 5.1 use the code suggested beside the ErrorMessage. Beside that great example!

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.