2

I would just like to get the validation arguments provided by an IValidateSetValuesGenerator class similar to a return when we use a bad argument (see example command Sample -Verb 'BadVerb')

Below is an example of code:

class verb : System.Management.Automation.IValidateSetValuesGenerator
{
    [String[]] GetValidValues()
    {
        [System.Collections.ArrayList]$Verbs = @()
        $VerbsSource = Get-Verb

        foreach ($Verb in $VerbsSource)
        {
            $Verbs.Add([PSCustomObject]@{'verb' = $Verb.Verb})
        }

        return ($Verbs).Verb
    }
}


function Sample
{
    [CmdletBinding(SupportsShouldProcess=$true, 
                  PositionalBinding=$false,
                  ConfirmImpact='Medium')]
    [Alias()]
    [OutputType([bool])]
    Param
    (
        [Parameter(Mandatory=$true)]
        [ValidateSet([verb], ErrorMessage="Value '{0}' is invalid. Try one of: {1}")]
        [string]$Verb
    )
    Begin
    {

    }
    Process
    {

    }
    End
    {
        return $Verb
    }
}

Sample -Verb 'BadVerb'
Sample -Verb 'Get'
Xenixium
  • 35
  • 3
  • 2
    `[verb]::new().GetValidValues()` should do the trick – Mathias R. Jessen Jun 17 '21 at 11:40
  • As an aside (I understand that your example may just be a trimmed-down, simplified version of more complex code): As written, the body of your `GetValidValues()` method can be simplified to: `return (Get-Verb).Verb` – mklement0 Jun 17 '21 at 15:07

2 Answers2

2

Mathias R. Jessen has provided the crucial pointer:

# PSv5+ syntax: 
# Construct (create an instance of) the [verb] class and call its
# .GetValidValues() instance method.
[verb]::new().GetValidValues() # Returns a [string[]] array of valid values.

Your [verb] class implements the [System.Management.Automation.IValidateSetValuesGenerator] interface (for use in [ValidateSet] attributes to allow constraining parameter values to a dynamically generated set of valid (permissible) values).

This interface, has a single instance method, .GetValidValues(), which returns the permissible values, and which PowerShell calls behind the scenes during parameter validation.

Therefore, in order to call this method yourself, you need to create an instance of your [verb] class first:

  • In PowerShell v5+, the best choice is to use the static ::new() method, which is PowerShell's way of exposing public constructors; that is, [verb]::new() is equivalent to new verb() in C#.

  • In older PowerShell versions you must use the New-Object cmdlet for calling constructors; the equivalent of [verb]::new() is New-Object verb

# PowerShell v5+
[verb]::new().GetValidValues()

# PowerShell v4-, but also works in higher versions.
(New-Object verb).GetValidValues()

Syntax pitfalls:

::new() uses method syntax (as in C#), whereas New-Object, as a cmdlet, uses command syntax,[1] i.e. is invoked like a shell command: no (...) around the list of arguments, whitespace as the argument separator.

The following example - using a constructor with arguments - illustrates the difference:

# PSv5+ ::new() call - method syntax.
# Equivalent of this C# constructor call:
#   new Regex("^\w+=.+", RegexOptions.Multiline, new Timespan(1000));
[regex]::new('^\w+=.+', [System.Text.RegularExpressions.RegexOptions]::Multiline, [timespan]:new(1000))

# Equivalent New-Object call - command syntax,
# using verbose *named* parameter binding.
# Note the following:
#  * absence of (...) around the list of arguments as a whole
#  * use of whitespace to separate arguments (and also parameter names from their arguments)
#  * the need to separate the *constructor* arguments with ","
#    as they must be passed as an *array*.
#  * the need to enclose [System.Text.RegularExpressions.RegexOptions]::Multiline individually in (...)
New-Object -TypeName regex -ArgumentList '^\w+=.+', ([System.Text.RegularExpressions.RegexOptions]::Multiline)

# Equivalent call using *positional* (unnamed) parameter binding.
New-Object regex  '^\w+=.+', ([System.Text.RegularExpressions.RegexOptions]::Multiline)

A particular pitfall is when a constructor takes a single argument that is an array or a collection, which with New-Object requires wrapping the array in an aux., transitory array:

$array = 1, 2

# OK: Initialize an ArrayList instance via an array that
#     binds as a whole to the `System.Collections.ICollection c`
#     constructor parameter.
[System.Collections.ArrayList]::new($array)

# !! BROKEN
# $array is interpreted as *multiple* (two separate) arguments.
New-Object System.Collections.ArrayList -ArgumentList $array

# OK
# Need to wrap the array in an aux. transitory array.
New-Object System.Collections.ArrayList -ArgumentList (, $array)

[1] In PowerShell terms, method syntax is parsed in expression mode, whereas command syntax is parsed in argument mode. See the conceptual about_Parsing help topic.

mklement0
  • 382,024
  • 64
  • 607
  • 775
1

In addition to mklement0's excellent explanation of [Verb]::new(), allow me to provide you with an example of how you could structure the value generator class so that you don't need an instance to generate the values:

class verb : System.Management.Automation.IValidateSetValuesGenerator
{
    [String[]] GetValidValues()
    {
        return [Verb]::GetValidValues()
    }
    
    static [String[]] GetValidValues()
    {
        [System.Collections.ArrayList]$Verbs = @()
        $VerbsSource = Get-Verb

        foreach ($Verb in $VerbsSource)
        {
            $Verbs.Add([PSCustomObject]@{'verb' = $Verb.Verb})
        }

        return ($Verbs).Verb
    }
}

Now we've moved the actual heavy lifting to a static method, and the instance method (which is what PowerShell is going to call) simply calls the static method.

Now you can get the value list without creating an instance of [verb]:

PS ~> [verb]::GetValidValues()
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
  • I can't use static with GetValidValues, I can with another name. If I use PowerShell return this error: ````Error during creation of type "Verb". Error message: Method 'GetValidValues' in type Verb' from assembly 'PowerShell | Class Assembly, Version=1.0.0.43, Culture=neutral, PublicKeyToken=null' does not have an implementation.```` – Xenixium Jun 19 '21 at 07:56
  • @Xenixium look at the answer again - you need both a static and a non-static version of GetValidValues() – Mathias R. Jessen Jun 19 '21 at 08:28