powershellvalidationparametersattributes

How to use ValidateScript on a collection instead of the elements in the collection?


Microsoft docs mention that if I apply the ValidateScript attribute to a collection, it will evaluate each element of the collection:

If this attribute is applied to a collection, each element in the collection must match the pattern.

I am trying to validate the collection itself. For example its type:

function testme {
    param(
        [ValidateScript({$_ -is [array] -or $_ -is [hashtable]}, ErrorMessage = "The item '{0}' did not pass validation of script '{1}'")]
        [psobject]$args
    )
}

$arr = [psobject]@("aaa", "bbb")
$arr -is [array] #returns True 
testme -args $arr

This - as expected - nets me this result: Cannot validate argument on parameter 'args'. The item 'aaa' did not pass validation of script '$_ -is [array] -or $_ -is [hashtable]'

I am not able to figure out how to match the input collection instead of the elements in the collection.


Solution

  • ValidateScript derives from ValidateEnumeratedArgumentsAttribute, as it name hints, arguments are enumerated and validated. If you want to validate a collection as a whole you'd need to go one level up and derive from ValidateArgumentsAttribute however, there isn't a PowerShell built-in attribute decoration for this, you'd need to implement it yourself, here is a little example:

    using namespace System.Management.Automation
    
    class ValidateCollection : ValidateArgumentsAttribute {
        [void] Validate([Object] $arguments, [EngineIntrinsics] $engineIntrinsics) {
            # perhaps `$arguments -is [ICollection]` might do here
            if ($arguments -is [array] -or $arguments -is [hashtable]) {
                return
            }
    
            throw [MetadataException]::new(
                "The item '$arguments' of type '$($arguments.GetType())' did not pass validation.")
        }
    }
    

    Then for the usage you decorate your parameter like so:

    function testme {
        param(
            [ValidateCollection()]
            [psobject] $MyParameter
        )
    }
    
    testme 1, 2, 3, 4       # passes test
    testme @{ foo = 'bar' } # passes test
    testme 1                # fails