Validating JSON objects with PowerShell Advanced Function Parameters

PowerShell makes it really easy to convert JSON into (PSCustom) objects with the means of ConvertFrom-Json. But manually verifying every property after conversion is particular tedious and annoying work. Recently I wrote about Using typeful JSON Deserialisation and Validation in PowerShell. However, sometimes this is just not feasible as you might not have a C# class describing your data. Scripting Guy to the rescue!

Some days ago (ok, by today already 1299 days ago) Ed wrote on how to Simplify Your PowerShell Script with Parameter Validation where he described how we could use PowerShell Advanced Function Parameters. Now this very same technique (with the help of the lengthy ValueFromPipelineByPropertyName attribute) can be used to declaratively validate your JSON data structures without writing too much imperative code. All you have to do is define an advanced function that accepts all the properties of your object to validate and use the validation attributes that PowerShell provides.

Let’s try this with a simple example: below you see a ‘Request’ object that has been converted from JSON. Some fields contain text, some fields are ‘null’ and some must contain a value or a number:

PS > $RequestText = @"
{
  "Owner":  "SERVER\\Administrator",
  "OperatingSystem":  "2012STDx64",
  "Tiering":  null,
  "VCPU":  2,
  "MemoryGB":  4,
  "HardDisk0GB":  40,
  "Region":  "ACME",
  "Site":  "ABC",
  "Cluster":  "myCluster1",
  "Datastore":  "vmnfs2",
  "IaasReservation":  "defaultValue",
  "Vlan":  "56",
  "Template":  "VirtualMachineRequestTemplate",
  "Type":  "SccmProvisioning",
  "Name":  "CUMU-OSD-0011",
  "Description":  "This is a request",
  "CostCenter":  667,
  "Cname":  "CUMULUS0042",
  "DomainName":  null
}
"@

PS > $Request = $RequestText | ConvertFrom-Json;
PS > $Request.GetType().FullName
System.Management.Automation.PSCustomObject

Now we write our ‘Confirm’ or ‘Assert’ function like this (I chose Confirm as the verb of these functions to comply with the Approved Verbs for Windows PowerShell Commands):

function Confirm-RequestObject {
PARAM
(
  [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
  [ValidateNotNullOrEmpty()]
  [string] $Region
  ,
  [ValidateSet('DEV','INT','PRD')]
  [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
  [string] $Site
  ,
  [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
  [ValidateRange(1,999)] 
  [int] $Vlan
  ,
  [ValidatePattern('\d\d\?')]
  [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
  [ValidateNotNullOrEmpty()]
  [string] $Name
  ,
  [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
  [ValidateNotNullOrEmpty()]
  [string] $OperatingSystem
  ,
  [ValidateScript( { (Get-CumulusKeyNameValue 'Cumulus.IaasParameters' -ValueOnly) -contains $_; } )]
  [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
  [string] $Tiering
  ,
  [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
  $InputObject
)

  foreach($Object in $InputObject)
  {
    if(!$Object.DomainName) { $Object.DomainName = "acme.example.com"; }
  }
  return $true;

} # function

As you can see, we only define parameter that we want to confirm or assert. Parameters that are optional can be skipped. For every parameter you use any validation attribute you like, such as ValidateScript, ValidatePattern, ValidateRange and just plain data types.

In case you want to supply default values, you can do this as well, by having the complete ‘Request’ object bound to the function as well. Now all you have to do is call the script like this:

$Request | Confirm-RequestObject -ErrorAction:Stop;
$true

By specifying -ErrorAction:Stop the script will throw an exception. In all other cases the script will return ‘$true’.

That’s all it takes to validate an object and keep it readable. If you have more of these objects to validate these scripts could be grouped into a module that for easy access and maintainability.

Just as a side note:
In case you ask yourself, “Why Doesn’t My ValidateScript() work correctly?” – you’re not alone. ValidateScript is only fired when you explicitly assign a value for it (so it does not work with default parameters that grab their values from some other variable – like this [int] $myIntParameter = $Host.Version.Major).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: