Powershell: ValidateScript does not work for class properties

Created on 29 Apr 2020  路  10Comments  路  Source: PowerShell/PowerShell

Steps to reproduce

# not working
class TestClass {
    [ValidateScript({ $_ -le 10 })]
    [Int]$ClassVar
}

Output:

ParserError: 
Line |
   2 |      [ValidateScript({ $_ -le 10 })]
     |                      ~~~~~~~~~~~~~
     | At line:2 char:21 +     [ValidateScript({ $_ -le 10 })] +                     ~~~~~~~~~~~~~
Attribute argument must be a
     | constant.
# working
class TestClass {
    [ValidateRange(0,10)]
    [Int]$ClassVar
}
$cl = [TestClass]::new()
$cl.ClassVar = 10
$cl.ClassVar



md5-cd593de05fd63b59e9a3118948b54ea7



10



md5-e83e58612765c140b6482597a9da33a1



```none
Name                           Value
----                           -----
PSVersion                      5.1.18362.752
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.18362.752
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
Documentation Needed Issue-Bug WG-Engine

Most helpful comment

Yeah @vexx32 has hit it on the head. It looks like we should handle this more gracefully.

I imagine that this is "expected if you think about the implementation and extrapolate", but has not been anticipated by the existing implementation.

Currently classes are compiled into dynamic .NET assemblies, and won't tolerate attributes that do something that .NET doesn't like, namely use values that can't be embedded into .NET static metadata.

I don't see ValidateScript ever working as an attribute for classes, but I also don't see that as a big issue since classes don't need to do parameter binding. Instead the validation can be done in the method body (similar to how it would be done for a C# method).

I think we should:

  • Emit a parse-time semantic error when this attribute is used on a class method
  • Update documentation to explicitly document how attributes work with classes

All 10 comments

perhaps class is by design static scoping and scriptblock with validateScript is interpreted dynamically...maybe you can create your own attribute with class [System.Management.Automation.ValidateArgumentsAttribute]

In this specific case you could use [ValidateRange(0, 10)] or [ValidateRange([int]::MinValue, 10)] depending on your needs.

But yeah, classes are handled at parse time, so attributes have some extra restrictions. I'm not sure there's a way around that? Might be, but I would _guess_ there probably isn't much that's really doable there.

@vexx32 The simple example is just that - a simple example. My need for ValidateScript is obviously much more complex.

Could you point me to the documentation where this is listed as expected behavior since you are not surprised about this behavior?

I could not find many relevant matches doing a web search about validatescript and class but several did have other people wondering about ValidateScript and this problem. So it would not seem to be very well documented.

It is disappointing. If class methods are not static like this then I might be able to figure out an ugly workaround if I have to.

I'm mainly not surprised because I've seen it before, both in PS and (more importantly) in C# itself. In compiled code you can't have non-constant/non-primitive values for attributes. It's actually part of the attribute language specification which states:

The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:

  • One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
  • The type object.
  • The type System.Type.
  • An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility (Attribute specification).
  • Single-dimensional arrays of the above types.

A constructor argument or public field which does not have one of these types, cannot be used as a positional or named parameter in an attribute specification.

I think there are _similar_ underlying requirements for dynamically compiled assemblies, and .NET in the more general case as well. I'm pretty sure the only reason [ValidateScript()] is even a thing is because PowerShell script can do more or less whatever it want, as long as it's willing to handle things on its own. [ValidateScript()] is possible in PS probably because PS isn't directly compiling functions, it's doing it piecemeal and can handle attributes however differently it wants, more or less. Attribute classes are free to have whatever properties they want, but they can only use the above types in their constructor parameters when they're decorating a class and need to be resolved at compile time.

Classes in PS are a different story entirely, and have to be compiled as-is at parse time, meaning there's no deferred compilation or special handling of attributes for the most part, they just behave very much like C# / .NET attributes.

As far as documenting it in PowerShell, I'd tend to agree that it should be mentioned somewhere in the classes documentation.

/cc @rjmholt

Yeah @vexx32 has hit it on the head. It looks like we should handle this more gracefully.

I imagine that this is "expected if you think about the implementation and extrapolate", but has not been anticipated by the existing implementation.

Currently classes are compiled into dynamic .NET assemblies, and won't tolerate attributes that do something that .NET doesn't like, namely use values that can't be embedded into .NET static metadata.

I don't see ValidateScript ever working as an attribute for classes, but I also don't see that as a big issue since classes don't need to do parameter binding. Instead the validation can be done in the method body (similar to how it would be done for a C# method).

I think we should:

  • Emit a parse-time semantic error when this attribute is used on a class method
  • Update documentation to explicitly document how attributes work with classes

@rjmholt Are there any other Validation attributes that don't work with classes?

I am guessing that ValidateDrive and ValidateUserDrive might be a problem. Can you confirm?

Those should work, I think the main issue with ValidateScript is that it has to take a non-constant value (a scriptblock) to do its job.

At least, "work" in the sense that the attribute can be applied to the class property. Whether the PS engine will recognise them on a class member or not... is another matter entirely. Not sure there, tbh.

Interesting discussion. I would still like to see this limitation documented since my original research turned up several other similar questions.

After the explanation above it is obvious why such a limitation exists but many users out there, like me, do not have as deep understanding of PowerShell as you do.

My original planned use case for this was doing data binding or simple manual storage&validation of data from a GUI. It is my first attempt at doing more advanced GUI things so it may have been a bad/impossible idea in any case even if the ValidateScript would have worked. It looked like an elegant way to do validation to the untrained eye...

@rjmholt Are there any other Validation attributes that don't work with classes?

I am guessing that ValidateDrive and ValidateUserDrive might be a problem. Can you confirm?

Seem to work just fine.

Was this page helpful?
0 / 5 - 0 ratings