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
).
This post is from 2014, but ranks highly on search engine results pages. Is there any guidance for extending this, in order to get a validation approach that returns `true` or `false` instead of terminating the process upon validation failure?
Hi Tymac, I am not sure if I understand you correctly. Currently the function does return `$true`. By specifying `-ErrorAction:Stop` we are instructing PowerShell to terminate the process in case one of the validation attributes fail. If you want to ignore validation errors, you might want to try with a different `-ErrorAction` value. In addition you can add a `break` inside the `for` loop to return a `$false` if this is desired. I hope this answers your question. Regards, Ronald