Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find the values in ValidateSet

Tags:

powershell

I was wondering if there was a way to retrieve the values used in the clause Param() for ValidateSet. Something like this would be great:

Function Foo {
    Param (
        [ValidateSet('Startup', 'Shutdown', 'LogOn', 'LogOff')]
        [String]$Type = 'Startup'
    )

    $Type.ValidateSet
}

But of course there is no such property on the Type object. Is it possible to retrieve the values set in ValidateSet?

like image 586
DarkLite1 Avatar asked Mar 09 '17 13:03

DarkLite1


People also ask

What is ValidateSet in PowerShell?

The ValidateSetAttribute attribute specifies a set of possible values for a cmdlet parameter argument. This attribute can also be used by Windows PowerShell functions.

Is ValidateSet case sensitive?

By default, the ValidateSet attribute is case insensitive. This means that it will allow any string granted it's in the allowed list with any capitalization scheme.

What is a parameter set in PowerShell?

PowerShell uses parameter sets to enable you to write a single function that can do different actions for different scenarios. Parameter sets enable you to expose different parameters to the user. And, to return different information based on the parameters specified by the user.

What is dynamic parameter in PowerShell?

These parameters are added at runtime and are referred to as dynamic parameters because they're only added when needed. For example, you can design a cmdlet that adds several parameters only when a specific switch parameter is specified. Note. Providers and PowerShell functions can also define dynamic parameters.


2 Answers

function Foo {
    param (
        [ValidateSet('Startup', 'Shutdown', 'LogOn', 'LogOff')]
        [String]$Type = 'Startup'
    )

    $ParameterList = (Get-Command -Name $MyInvocation.MyCommand).Parameters
    $ParameterList["Type"].Attributes.ValidValues
}

After your comment:

param (
        [ValidateSet('Startup', 'Shutdown', 'LogOn', 'LogOff')]
        [String]$Type = 'Startup'
)


(Get-Variable "Type").Attributes.ValidValues

The Get-Variable call also works in a function.

like image 178
David Brabant Avatar answered Oct 02 '22 19:10

David Brabant


All solutions below work in both functions and scripts.

Most robust solution that should work in any invocation scenario, PSv2+:

param (
    [ValidateSet('Startup', 'Shutdown', 'LogOn', 'LogOff')]
    [String]$Type = 'Startup'
)

($MyInvocation.MyCommand.Parameters['Type'].Attributes |
  Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues

A simpler, but fragile PSv3+ solution, which assumes:

  • that Set-StrictMode is either set to -version 1 or not set.

    • Set-StrictMode may have been set outside of your control, so if you don't fully control the execution environment, it is safer to use the more verbose, PSv2-compatible command above.
      (The Set-StrictMode setting behaves like a variable: it is inherited by descendent scopes, but setting it in a descendent scope sets it locally (only affects that scope and its descendants).)

    • However, if you define a function as part of a module, the outside world's Set-StrictMode setting does not apply.

  • that, up to at least Windows PowerShell v5.1 / PowerShell Core v6.0-alpha16, running into this bug when repeatedly dot-sourcing a script is not a concern.

param (
    [ValidateSet('Startup', 'Shutdown', 'LogOn', 'LogOff')]
    [String]$Type = 'Startup'
)

(Get-Variable Type).Attributes.ValidValues

Optional background information

The PSv3+ shorthand syntax (Get-Variable Type).Attributes.ValidValues is essentially the equivalent of:

(Get-Variable Type).Attributes | ForEach-Object { $_.ValidValues }

That is, PowerShell automatically enumerates the collection .Attributes and collects the values of each element's .ValidValues property.

In the case at hand, only one attribute in the .Attributes collection - the one of subtype [System.Management.Automation.ValidateSetAttribute] - has a .ValidValues property, so that single value is returned.

Given that the other attributes have no such property, setting Set-StrictMode to -version 2 or higher causes the attempt to access a nonexistent property to raise an error, and the command fails.

((Get-Variable Type).Attributes |
  Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] }).ValidValues

bypasses this problem by explicitly targeting the one attribute of interest (using the -is operator to identify it by type) that is known to have a .ValidValues property.

The more verbose alternative to accessing the attributes of parameter [variable] $Type with (Get-Variable Type).Attributes is to use $MyInvocation.MyCommand.Parameters['Type'].Attributes.

Use of the $MyInvocation.MyCommand.Parameters collection enables enumerating and inspecting all parameters without needing to know their names in advance.


David Brabant's answer is helpful, but (as of this writing):

  • It may create the mistaken impression that separate approaches are needed for scripts and functions.

  • The Get-Command -Name $MyInvocation.MyCommand part is:

    • unnecessary, because $MyInvocation.MyCommand itself provides the information of interest:
      $MyInvocation.MyCommand is an instance of type [System.Management.Automation.ExternalScriptInfo] in scripts, and type [System.Management.Automation.FunctionInfo] in functions, both of which derive from type [System.Management.Automation.CommandInfo], which is the type that Get-Commmand returns - so not only do they provide the same information, they also unambiguously refer to the enclosing script/function.

    • brittle:

      • $MyInvocation.MyCommand is converted to a string due to being passed to the -Name parameter, which in a script results in the script's mere filename (e.g., script.ps1), and in a function in the function's name (e.g., Foo).

      • In a script, this will typically cause Get-Command not to find the script at all - unless that script happens to be in the PATH (one of the directories listed in $env:PATH). But that also means that a different script that happens to have the same filename and that happens to be / come first in the PATH may be matched, yielding incorrect results.
        In short: Get-Command -Name $MyInvocation.MyCommand in scripts will often break, and when it does return a result, it may be for the wrong script.

      • In a function, it can identify the wrong command too, although that is much less likely:
        Due to PowerShell's command precedence, a given name is first interpreted as an alias, and then as a function, so, in theory, with a Foo alias defined, Get-Command -Name $MyInvocation.MyCommand inside function Foo would mistakenly return information about the alias.
        (It's nontrivial to invoke function Foo while alias Foo is defined, but it can be done; e.g.: & (Get-Item Function:Foo))

like image 21
mklement0 Avatar answered Oct 02 '22 19:10

mklement0