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:
validateScript
, can provide a more flexible solution and would work well if you needed additional parameter validation. This also allows you to get a list of the valid parameters outside of the foo
function, with the creation of the get-validTypes
function.
Function Foo {
Param (
[validateScript({test-validTypes $_})]
[String]$Type = 'Startup'
)
get-validTypes
}
function get-validTypes {
$powerOptions = @('Startup', 'Shutdown', 'LogOn', 'LogOff')
Write-Output $powerOptions
}
function test-validTypes {
[cmdletbinding()]
param ($typeInput)
$validTypes = get-validTypes
if ($validTypes.contains($typeInput)){
return $true
} else {
Write-Error "Invalid Type Paramater, Must be on of the following: $powerOptions"
}
}
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
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)
)
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.