powershellpropertiesadd-member

How to correctly validate parameter in powershell?


team!

I have variable with type PSObject[] in my advanced function.

[Parameter( Mandatory = $false, Position = 0, HelpMessage = "PsObject data." )]
[PSobject[]] $data,
...

but sometimes my input $data with type [string[]] is transforming to [PSObject[]] and i catch error while im using object property.

im trying to validate it by script

[Parameter( Mandatory = $false, Position = 0, HelpMessage = "PsObject data." )]
[ValidateScript({ ( ( $_ -is [PSobject] ) -or ( $_ -is [PSobject[]] )  -or ( $_ -is [System.Object[]] ) ) })]
 $data,

but it has no effect, i see the $data with type [string[]], i`am continue to cath errors.

Whats wrong?


Solution

  • Edit: based on the comments, it sounds like your real question is:

    How can I validate that I'm able to attach new properties to the input objects with Add-Member?

    For that, you need to exclude two kinds of input values:

    (As mklement0's excellent answer shows, properties can be added to local copies of these types - but PowerShell cannot predictably "resurrect" them when passing values between adjecent commands in a pipeline, among other quirks)

    You can validate that input objects do not fall in one of these buckets, like this:

    [ValidateScript({$null -ne $_ -and $_.GetType().IsValueType -and $_ -isnot [string]})]
    [psobject[]]$InputObject
    

    PSObject is a generic wrapper type that PowerShell uses internally to keep track of extended properties and members attached to existing objects.

    For this reason, any object can be converted to PSObject implicitly - in fact, PowerShell does so every time an object passes from one command to another across | in a pipeline statement - and it has no real effect in terms of enforcing specific input object traits.

    If you want to ensure that an object has specific properties, the best option is to define a specific datatype with the class keyword:

    class MyParameterType 
    {
      [string]$Name
      [int]$Value
    }
    
    function Test-MyParameterType
    {
      param(
        [MyParameterType[]]$InputObject
      )
    
      $InputObject |ForEach-Object {
        $_.GetType() # this will output `[MyParameterType]`
        $_.Name # now you can be sure this property exists
      }
    }
    

    You can now pass instances of the declared type to the function parameter:

    $mpt = [MyParameterType]::new()
    $mpt.Name = 'Name goes here'
    
    Test-MyParameterType -InputObject $mpt
    

    But PowerShell can also implicitly convert custom objects to the desired target type if they have matching properties:

    $arg = [pscustomobject]@{
      Name = 'A name'
      Value = Get-Random
    }
    
    # This will return [PSCustomObject]
    $arg.GetType() 
    
    # But once we reach `$_.GetType()` inside the function, it will have been converted to a proper [MyParameterType]
    Test-MyParameterType -InputObject $arg 
    

    If you want to validate the existence of specific properties and potentially their value without explicit typing, you have to access the hidden psobject memberset of the object in the validation script - note that it'll validate one item at a time:

    function Test-RequiredProperty
    {
      param(
        [ValidateScript({ $_ -is [PSObject] -and ($prop = $_.psobject.Properties['RequiredProperty']) -and $null -ne $prop.Value })]
        [PSObject[]]$InputObject
      )
    }
    

    Now, if we pass an object with a RequiredProperty property that has some value, the validation succeeds:

    $arg = [pscustomobject]@{
      RequiredProperty = "Some value"
    }
    
    # This will succeed
    Test-RequiredProperty -InputObject $arg
    
    # This will fail because the property value is $null
    $arg.RequiredProperty = $null
    Test-RequiredProperty -InputObject $arg
    
    # This will fail because the property doesn't exist
    $arg = [pscustomobject]@{ ADifferentPropertyName = "Some value" }
    Test-RequiredProperty -InputObject $arg