As a PowerShell user, I want [ValidateSet] to support a dynamically generated sets _from script blocks_ so that I can make terse, reusable sets of data, and my coworkers are provided useful IntelliSense from my work. In issue #3744, it was proposed to support a dynamically generated set for [ValidateSet]. An example was given with a script block, which would resolve the set. However, the actual merged PR laid the groundwork and implemented a variant, using classes and interfaces. I'd like this issue to specifically track the ability to use [ValidateSet] with a _script block_ and receive IntelliSense feedback.
For example:
[CmdletBinding()]
param (
[ValidateSet({ (Get-MyGitRepos -Code -Config).master })]
$Repo = "foo"
)
It's important to remember that while the [ArgumentCompleter()] script block param can refer to other already-entered parameters via a $fakeBoundParameters param in the script block's param() block, the IValidateSetValuesGenerator interface does not provide the same capability as IArgumentCompleter's interface method does.
It may be possible to copy parts of the code from the [ArgumentCompleter()] constructor that utilises a script block, and the code from IArgumentCompleter in order to permit this much more dynamic and self-referential mode of using ValidateSet, which should make it a far more versatile attribute to work with.
@OlsonDev
and my coworkers are provided useful IntelliSense from my (arbitrary scriptblocks).
Are you talking about intellisense in an editor? Running arbitrary code when you are simply _editing_ is problematic from a security standpoint. Consider all the problems that Word macros caused. Or an evil.ps1 script file:
[CmdletBinding()]
param (
[ValidateSet({Remove-Item -Force -Recurse ~})]
$Repo = "foo"
)
@BrucePay -
Running arbitrary code when you are simply editing is problematic from a security standpoint.
That ship sailed a long time ago - parameter name completion has invoked the dynamicparam block since it was introduced.
@lzybkr We don't instantiate functions until runtime. Without running the script, how is the dynamic parameter block getting turned into executable code?
It falls out naturally when asking for the parameter metadata - PSScriptCmdlet implements IDynamicParameters.
If you ask for the parameter metadata, e.g. to invoke the command or for parameter name completion, you must also query the cmdlet for it's dynamic parameters via IDynamicParameters.
The actual compilation is always on demand, so in this case, it's the implementation of PSScriptCmdlet.GetDynamicParameters that kicks off the compilation.
@BrucePay
Are you talking about intellisense in an editor? Running arbitrary code when you are simply _editing_ is problematic from a security standpoint. Consider all the problems that Word macros caused. Or an
evil.ps1script file:[CmdletBinding()] param ( [ValidateSet({Remove-Item -Force -Recurse ~})] $Repo = "foo" )
I more so meant command completion in the PowerShell console window -- in which case, (my coworkers at least) have the source to every command and every command we maintain is code reviewed when changed/added. I don't really view this kind of completion as any more risky than running a command without digging into its source; it could do anything after all.
@OlsonDev I agree, not any more risky than running a command really, just occurs maybe a little sooner. I suppose the validation code could be put in a constrained runspace but seems like a lot of overhead for little benefit.
FYI as a workaround that is both Core and WinPS compatible, you can use Register-ArgumentCompleter for the autocomplete part and pass it a scriptblock, and (if you're really paranoid) then have one of the first lines of your code run the same scriptblock, compare the result, and throw an error. The error can have basically the same text as ValidateSet, so you get effectively the same result.
function Get-TestArgumentValues {
(get-module).name
}
Register-ArgumentCompleter -CommandName Test-ArgumentCompleter -ScriptBlock {Get-TestArgumentValues}
function Test-ArgumentCompleter ($argument) {
#This part is optional but recommended
$TestArgumentValues = Get-TestArgumentValues
if ($argument -notin $TestArgumentValues) {throw "Parameter invalid, you must specify one of the following options: $($TestArgumentValues -join ', ')"}
#End Optional Part
echo $argument
}
#Now try Test-ArgumentCompleter -Argument [tab]
You can also dynamically generate an enum on module import and set your parameter type to that, but then you have to get the enum into the "user" scope because of how dumb classes are. The above is "cleaner" in my opinion.
I have some other alternatives for this purpose listed in my blog post here:
https://vexx32.github.io/2018/11/29/Dynamic-ValidateSet/
But yes, given that the implementation via class is already a thing, I don't really see how that's all that different to just accepting and using a script block just like ArgumentCompleter already does.
@vexx32 I agree, the class already has the functionality in PSCore so the decision has already been made that this is "OK", so going forward the syntax should be able to be simplified to just a scriptblock to make it more intuitive for less advanced powershell users :)
I do wish this was implemented as for my current workflow, it would work a wonder. Trying to use IValidateSetValuesGenerator now, but it's just not very intuitive.
Most helpful comment
@vexx32 I agree, the class already has the functionality in PSCore so the decision has already been made that this is "OK", so going forward the syntax should be able to be simplified to just a scriptblock to make it more intuitive for less advanced powershell users :)