question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

ValidateSet with class does not work

See original GitHub issue

Prerequisites

Steps to reproduce

Create two files in a directory.

test.ps1

using module './testvalidate.psm1'

param
(
    [Parameter(Mandatory)]
    [ValidateSet([TestValidate])]
    [string]$foo
)

testvalidate.psm1

class TestValidate : System.Management.Automation.IValidateSetValuesGenerator
{
    [String[]] GetValidValues()
    {
        $values = @()
        $values += 'bar'
        $values += 'baz'
        return $values
    }
}

Expected behavior

cmdlet test.ps1 at command pipeline position 1
Supply values for the following parameters:
foo:

Actual behavior

InvalidOperation: E:\Software development\Royalty Calculator\trunk.powershell\test.ps1:6
Line |
   6 |      [ValidateSet([TestValidate])]
     |      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Unable to find type [TestValidate].

Error details

Exception             :
    Type        : System.Management.Automation.RuntimeException
    ErrorRecord :
        Exception             :
            Type    : System.Management.Automation.ParentContainsErrorRecordException
            Message : Unable to find type [TestValidate].
            HResult : -2146233087
        TargetObject          : [ValidateSet([TestValidate])]
        CategoryInfo          : InvalidOperation: ([ValidateSet([TestValidate])]:AttributeAst) [], ParentContainsErrorRecordException
        FullyQualifiedErrorId : TypeNotFound
        InvocationInfo        :
            ScriptLineNumber : 6
            OffsetInLine     : 5
            HistoryId        : -1
            ScriptName       : E:\Software development\Royalty Calculator\trunk.powershell\test.ps1
            Line             : [ValidateSet([TestValidate])]

            PositionMessage  : At E:\Software development\Royalty Calculator\trunk.powershell\test.ps1:6 char:5
                               +     [ValidateSet([TestValidate])]
                               +     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            PSScriptRoot     : E:\Software development\Royalty Calculator\trunk.powershell
            PSCommandPath    : E:\Software development\Royalty Calculator\trunk.powershell\test.ps1
            CommandOrigin    : Internal
        ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
    TargetSite  :
        Name          : NewValidateSetAttribute
        DeclaringType : System.Management.Automation.Language.Compiler, System.Management.Automation, Version=7.3.4.500, Culture=neutral, PublicKeyToken=31bf3856ad364e35
        MemberType    : Method
        Module        : System.Management.Automation.dll
    Message     : Unable to find type [TestValidate].
    Data        : System.Collections.ListDictionaryInternal
    Source      : System.Management.Automation
    HResult     : -2146233087
    StackTrace  :
   at System.Management.Automation.Language.Compiler.NewValidateSetAttribute(AttributeAst ast)
   at System.Management.Automation.Language.Compiler.GetAttribute(AttributeAst attributeAst)
   at System.Management.Automation.Language.Compiler.GetRuntimeDefinedParameter(ParameterAst parameterAst, Boolean& customParameterSet, Boolean& usesCmdletBinding)
   at System.Management.Automation.Language.Compiler.GetParameterMetaData(ReadOnlyCollection`1 parameters, Boolean automaticPositions, Boolean& usesCmdletBinding)
   at System.Management.Automation.CompiledScriptBlockData.InitializeMetadata()
   at System.Management.Automation.CompiledScriptBlockData.Compile(Boolean optimized)
   at System.Management.Automation.PSScriptCmdlet..ctor(ScriptBlock scriptBlock, Boolean useNewScope, Boolean fromScriptFile, ExecutionContext context)
   at System.Management.Automation.CommandProcessor.Init(IScriptCommandInfo scriptCommandInfo)
   at System.Management.Automation.CommandDiscovery.GetScriptAsCmdletProcessor(IScriptCommandInfo scriptCommandInfo, ExecutionContext context, Boolean useNewScope, Boolean fromScriptFile, SessionStateInternal sessionState)
   at System.Management.Automation.CommandDiscovery.CreateCommandProcessorForScript(ExternalScriptInfo scriptInfo, ExecutionContext context, Boolean useNewScope, SessionStateInternal sessionState)
   at System.Management.Automation.CommandDiscovery.CreateScriptProcessorForSingleShell(ExternalScriptInfo scriptInfo, ExecutionContext context, Boolean useLocalScope, SessionStateInternal sessionState)
   at System.Management.Automation.CommandDiscovery.LookupCommandProcessor(CommandInfo commandInfo, CommandOrigin commandOrigin, Nullable`1 useLocalScope, SessionStateInternal sessionState)
   at System.Management.Automation.CommandDiscovery.LookupCommandProcessor(String commandName, CommandOrigin commandOrigin, Nullable`1 useLocalScope)
   at System.Management.Automation.ExecutionContext.CreateCommand(String command, Boolean dotSource)
   at System.Management.Automation.PipelineOps.AddCommand(PipelineProcessor pipe, CommandParameterInternal[] commandElements, CommandBaseAst commandBaseAst, CommandRedirection[] redirections, ExecutionContext context)
   at System.Management.Automation.PipelineOps.InvokePipeline(Object input, Boolean ignoreInput, CommandParameterInternal[][] pipeElements, CommandBaseAst[] pipeElementAsts, CommandRedirection[][] commandRedirections, FunctionContext funcContext)
   at System.Management.Automation.Interpreter.ActionCallInstruction`6.Run(InterpretedFrame frame)
   at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
TargetObject          : [ValidateSet([TestValidate])]
CategoryInfo          : InvalidOperation: ([ValidateSet([TestValidate])]:AttributeAst) [], RuntimeException
FullyQualifiedErrorId : TypeNotFound
InvocationInfo        :
    ScriptLineNumber : 6
    OffsetInLine     : 5
    HistoryId        : -1
    ScriptName       : E:\Software development\Royalty Calculator\trunk.powershell\test.ps1
    Line             : [ValidateSet([TestValidate])]

    PositionMessage  : At E:\Software development\Royalty Calculator\trunk.powershell\test.ps1:6 char:5
                       +     [ValidateSet([TestValidate])]
                       +     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    PSScriptRoot     : E:\Software development\Royalty Calculator\trunk.powershell
    PSCommandPath    : E:\Software development\Royalty Calculator\trunk.powershell\test.ps1
    CommandOrigin    : Internal
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1

Environment data

Name                           Value
----                           -----
PSVersion                      7.3.4
PSEdition                      Core
GitCommitId                    7.3.4
OS                             Microsoft Windows 10.0.19045
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Visuals

No response

Issue Analytics

  • State:open
  • Created 4 months ago
  • Comments:10 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
mklement0commented, May 20, 2023

There is a workaround, but it is somewhat cumbersome: It relies on the fact that referencing types in script blocks used in parameter attributes happens at runtime, so the imported type is then already available:

using module ./testvalidate.psm1

param(
    [Parameter(Mandatory)]
    [ValidateScript({ 
      if ($_ -notin [TestValidate]::new().GetValidValues()) { throw "Not a valid value: $_" }
      $true
    })]
    [ArgumentCompleter({ 
      param($command, $param, $wordToComplete)
      [TestValidate]::new().GetValidValues() -like "$wordToComplete*"
    })]
    [string]$foo
)

The unfortunate side effect of using ArgumentCompleter with a script block (rather than with an IArgumentCompleter-implementing class) is that if what the user typed doesn’t match any of the valid values, the default file-name completion behavior kicks in; see:

1reaction
rhubarb-geek-nzcommented, May 19, 2023

I would expect so. For tab completion to work I expect it needs to have access to all the classes to do the value matching. If all the command line interpreter does is read the param stanza then it won’t have enough information. I don’t expect that tab-completion is done by actually running the script.

about_Functions_Argument_Completion

Can you not do something like their example?

Param(
    [ValidateSet('hello', 'world')]
    [string]$Message
)

$Message = 'bye'

The auto-completion offered both ‘hello’ and ‘world’ as options

This also errors when you try and assign ‘bye’ to $Message

Read more comments on GitHub >

github_iconTop Results From Across the Web

PowerShell ValidateSet: Choosing from a List
A ValidateSet list is a comma-separated list of string values, wrapped in single or double-quotes. Adding a ValidateSet attribute to a script or ......
Read more >
Couldn't use predefined array inside Validateset - Powershell
ps1 file) that is invoked directly, i.e. a script that starts with a param(...) block. The above approaches wouldn't work, because any class...
Read more >
ValidateSet Attribute Declaration - PowerShell
The cmdlet is run only if the parameter argument matches an element in the set. If no match is found, an error is...
Read more >
PowerShell parameter validate set and removing already ...
I have a function with several parameters defined. One of the parameters is a validate set consisting of 3 items. This works as...
Read more >
The classy way to complete and validate PowerShell ...
ValidateSets define allowed choices, if the value entered is not in the set, PowerShell will throw an error saying “valid values are …”....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found