Limitations of PowerShell 2.0 Parameter Validation Attributes

by Klaus Graefensteiner 18. June 2009 15:30

Introduction

While working on my Weather Data Download script I learned more than I wanted about the parameter validation attributes that ship with PowerShell 2.0. I was especially disappointed with the ValidateRange attribute. It doesn't support dynamic values for min and max range. Here is a list of script examples that show what works and what doesn’t work.

Crack in Ice

Figure 1: Crack in Ice

In my example I wanted to validate a .NET DateTime object. The range validation should only allow values that are between now and 7 days ago. This range is of course dynamic.

Test driving ValidateRange

The following section discusses the different approaches I took to make the dynamic range validation work.

Constant Min and Max values

First I was using strings as dates for a parameter of type [DateTime] assuming that the ValidateRange attribute would construct [DateTime] objects from these constant strings. I seems that could get it to work.

   1: function Test-DateIsLessThanNowAndGreaterThan7DaysAgo
   2: {
   3:     
   4:     [CmdletBinding()]
   5:     PARAM(
   6:         
   7:         [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$false, ParameterSetName="PreviousDays")]
   8:         [ValidateRange( "06/10/2009 01:00:00 PM", "06/17/2009 01:00:00 PM")]
   9:         [DateTime] $DateInQuestion
  10:         
  11:         )
  12:         
  13:     process
  14:     {
  15:         $DateInQuestion
  16:     }
  17: }
  18:  
  19:  
  20:  
  21: #Test 1 Value greater than maximum range
  22: $Yesterday = "06/21/2009 01:00:00 PM"
  23: $Yesterday
  24:  
  25: try 
  26: { 
  27:     Test-DateIsLessThanNowAndGreaterThan7DaysAgo -DateInQuestion $Yesterday; 
  28: }
  29: catch [System.Management.Automation.ParameterBindingException] 
  30: { 
  31:     $_.Exception
  32:     if ($_.Exception.ToString().IndexOf("was greater than the maximum range") -le 0 )
  33:     {
  34:         throw
  35:     }
  36: }
  37: catch
  38: {
  39:     "Unexpected Exception!"
  40:     throw
  41: }
  42: finally
  43: {
  44:     "Finally"
  45: }
  46:  
  47:  
  48: #Test 2 Value smaller than minimum range
  49: $Yesterday = "06/01/2009 01:00:00 PM"
  50: $Yesterday
  51:  
  52: try 
  53: { 
  54:     Test-DateIsLessThanNowAndGreaterThan7DaysAgo -DateInQuestion $Yesterday; 
  55: }
  56: catch [System.Management.Automation.ParameterBindingException] 
  57: { 
  58:     $_.Exception
  59:     if ($_.Exception.ToString().IndexOf("was smaller than the minimum range") -le 0 )
  60:     {
  61:         throw
  62:     }
  63: }
  64: catch
  65: {
  66:     "Unexpected Exception!"
  67:     throw
  68: }
  69: finally
  70: {
  71:     "Finally"
  72: }
  73:  
  74:  
  75: #Test 3 Value in range
  76: $Yesterday = "06/16/2009 01:00:00 PM"
  77: $Yesterday
  78:  
  79: try 
  80: { 
  81:     Test-DateIsLessThanNowAndGreaterThan7DaysAgo -DateInQuestion $Yesterday; 
  82: }
  83: catch [System.Management.Automation.ParameterBindingException] 
  84: { 
  85:     $_.Exception
  86:     if ($_.Exception.ToString().IndexOf("was smaller than the minimum range") -le 0 )
  87:     {
  88:         throw
  89:     }
  90: }
  91: catch
  92: {
  93:     "Unexpected Exception!"
  94:     throw
  95: }
  96: finally
  97: {
  98:     "Finally"
  99: }

Using expressions as Min and Max values

This is how I tried it initially. Using expressions as parameters for the ValidateRange attribute. The compilation failed and gave me the message that the values for min and max need to be constants or script blocks. I thought that makes sense.

TheArgumentNeedsToBeAConstantOrAScriptBlock

Figure 2: Parameter attributes need to be a constant or a script block

   1: function Test-DateIsLessThanNowAndGreaterThan7DaysAgo
   2: {
   3:     
   4:     [CmdletBinding()]
   5:     PARAM(
   6:         
   7:         [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$false, ParameterSetName="PreviousDays")]
   8:         [ValidateRange( [DateTime]::Now.AddDays(-7), [DateTime]::Now)]
   9:         [DateTime] $DateInQuestion
  10:         
  11:         )
  12:         
  13:     process
  14:     {
  15:         $DateInQuestion
  16:     }
  17: }
  18:  
  19:  
  20:  
  21: #Test 1 Value greater than maximum range
  22: $Yesterday = "06/21/2009 01:00:00 PM"
  23: $Yesterday
  24:  
  25: try 
  26: { 
  27:     Test-DateIsLessThanNowAndGreaterThan7DaysAgo -DateInQuestion $Yesterday; 
  28: }
  29: catch [System.Management.Automation.ParameterBindingException] 
  30: { 
  31:     $_.Exception
  32:     if ($_.Exception.ToString().IndexOf("was greater than the maximum range") -le 0 )
  33:     {
  34:         throw
  35:     }
  36: }
  37: catch
  38: {
  39:     "Unexpected Exception!"
  40:     throw
  41: }
  42: finally
  43: {
  44:     "Finally"
  45: }
  46:  
  47:  
  48: #Test 2 Value smaller than minimum range
  49: $Yesterday = "06/01/2009 01:00:00 PM"
  50: $Yesterday
  51:  
  52: try 
  53: { 
  54:     Test-DateIsLessThanNowAndGreaterThan7DaysAgo -DateInQuestion $Yesterday; 
  55: }
  56: catch [System.Management.Automation.ParameterBindingException] 
  57: { 
  58:     $_.Exception
  59:     if ($_.Exception.ToString().IndexOf("was smaller than the minimum range") -le 0 )
  60:     {
  61:         throw
  62:     }
  63: }
  64: catch
  65: {
  66:     "Unexpected Exception!"
  67:     throw
  68: }
  69: finally
  70: {
  71:     "Finally"
  72: }
  73:  
  74:  
  75: #Test 3 Value in range
  76: $Yesterday = "06/16/2009 01:00:00 PM"
  77: $Yesterday
  78:  
  79: try 
  80: { 
  81:     Test-DateIsLessThanNowAndGreaterThan7DaysAgo -DateInQuestion $Yesterday; 
  82: }
  83: catch [System.Management.Automation.ParameterBindingException] 
  84: { 
  85:     $_.Exception
  86:     if ($_.Exception.ToString().IndexOf("was smaller than the minimum range") -le 0 )
  87:     {
  88:         throw
  89:     }
  90: }
  91: catch
  92: {
  93:     "Unexpected Exception!"
  94:     throw
  95: }
  96: finally
  97: {
  98:     "Finally"
  99: }

Using script blocks as Min and Max values

How nice! The exception message in the expression case told me to use script blocks. So I did, but it didn’t like it. Now it told me that the return values of the script blocks need to implement the IComparable interface. I thought DateTime objects can be compared.

CannotAcceptRangeBecauseNotIComparable

Figure 3: Cannot accpet MaxRange and MinRange because they are not IComparable

   1: function Test-DateIsLessThanNowAndGreaterThan7DaysAgo
   2: {
   3:     
   4:     [CmdletBinding()]
   5:     PARAM(
   6:         
   7:         [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$false, ParameterSetName="PreviousDays")]
   8:         [ValidateRange( {[DateTime]::Now.AddDays(-7)}, {[DateTime]::Now})]
   9:         [DateTime] $DateInQuestion
  10:         
  11:         )
  12:         
  13:     process
  14:     {
  15:         $DateInQuestion
  16:     }
  17: }
  18:  
  19:  
  20:  
  21: #Test 1 Value greater than maximum range
  22: $Yesterday = "06/21/2009 01:00:00 PM"
  23: $Yesterday
  24:  
  25: try 
  26: { 
  27:     Test-DateIsLessThanNowAndGreaterThan7DaysAgo -DateInQuestion $Yesterday; 
  28: }
  29: catch [System.Management.Automation.ParameterBindingException] 
  30: { 
  31:     $_.Exception
  32:     if ($_.Exception.ToString().IndexOf("was greater than the maximum range") -le 0 )
  33:     {
  34:         throw
  35:     }
  36: }
  37: catch
  38: {
  39:     "Unexpected Exception!"
  40:     throw
  41: }
  42: finally
  43: {
  44:     "Finally"
  45: }
  46:  
  47:  
  48: #Test 2 Value smaller than minimum range
  49: $Yesterday = "06/01/2009 01:00:00 PM"
  50: $Yesterday
  51:  
  52: try 
  53: { 
  54:     Test-DateIsLessThanNowAndGreaterThan7DaysAgo -DateInQuestion $Yesterday; 
  55: }
  56: catch [System.Management.Automation.ParameterBindingException] 
  57: { 
  58:     $_.Exception
  59:     if ($_.Exception.ToString().IndexOf("was smaller than the minimum range") -le 0 )
  60:     {
  61:         throw
  62:     }
  63: }
  64: catch
  65: {
  66:     "Unexpected Exception!"
  67:     throw
  68: }
  69: finally
  70: {
  71:     "Finally"
  72: }
  73:  
  74:  
  75: #Test 3 Value in range
  76: $Yesterday = "06/16/2009 01:00:00 PM"
  77: $Yesterday
  78:  
  79: try 
  80: { 
  81:     Test-DateIsLessThanNowAndGreaterThan7DaysAgo -DateInQuestion $Yesterday; 
  82: }
  83: catch [System.Management.Automation.ParameterBindingException] 
  84: { 
  85:     $_.Exception
  86:     if ($_.Exception.ToString().IndexOf("was smaller than the minimum range") -le 0 )
  87:     {
  88:         throw
  89:     }
  90: }
  91: catch
  92: {
  93:     "Unexpected Exception!"
  94:     throw
  95: }
  96: finally
  97: {
  98:     "Finally"
  99: }

Abandoning ValidateRange and using ValidateScript

So much for getting ValidateRange to work. Without success. Now I am looking at ValidateScript. I got it to work finally.

   1: function Test-DateIsLessThanNowAndGreaterThan7DaysAgo
   2: {
   3:     
   4:     [CmdletBinding()]
   5:     PARAM(
   6:         
   7:         [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$false, ParameterSetName="PreviousDays")]
   8:         [ValidateScript({($_) -le [DateTime]::Now -and ($_) -ge [DateTime]::Now.AddDays(-7)})
   9:         [DateTime] $DateInQuestion
  10:         
  11:         )
  12:         
  13:     process
  14:     {
  15:         $DateInQuestion
  16:     }
  17: }
  18:  
  19:  
  20:  
  21: #Test 1 Value greater than maximum range
  22: $Yesterday = [DateTime]::Now.AddDays(2)
  23: $Yesterday
  24:  
  25: try 
  26: { 
  27:     Test-DateIsLessThanNowAndGreaterThan7DaysAgo -DateInQuestion $Yesterday; 
  28: }
  29: catch [System.Management.Automation.ParameterBindingException] 
  30: { 
  31:     $_.Exception
  32:     if ($_.Exception.ToString().IndexOf("did not return true") -le 0 )
  33:     {
  34:         throw
  35:     }
  36: }
  37: catch
  38: {
  39:     "Unexpected Exception!"
  40:     throw
  41: }
  42: finally
  43: {
  44:     "Finally"
  45: }
  46:  
  47:  
  48: #Test 2 Value smaller than minimum range
  49: $Yesterday = [DateTime]::Now.AddDays(-8)
  50: $Yesterday
  51:  
  52: try 
  53: { 
  54:     Test-DateIsLessThanNowAndGreaterThan7DaysAgo -DateInQuestion $Yesterday; 
  55: }
  56: catch [System.Management.Automation.ParameterBindingException] 
  57: { 
  58:     $_.Exception
  59:     if ($_.Exception.ToString().IndexOf("did not return true") -le 0 )
  60:     {
  61:         throw
  62:     }
  63: }
  64: catch
  65: {
  66:     "Unexpected Exception!"
  67:     throw
  68: }
  69: finally
  70: {
  71:     "Finally"
  72: }
  73:  
  74:  
  75: #Test 3 Value in range
  76: $Yesterday = [DateTime]::Now.AddDays(-1)
  77: $Yesterday
  78:  
  79: try 
  80: { 
  81:     Test-DateIsLessThanNowAndGreaterThan7DaysAgo -DateInQuestion $Yesterday; 
  82: }
  83: catch [System.Management.Automation.ParameterBindingException] 
  84: { 
  85:     $_.Exception
  86:     if ($_.Exception.ToString().IndexOf("did not return true") -le 0 )
  87:     {
  88:         throw
  89:     }
  90: }
  91: catch
  92: {
  93:     "Unexpected Exception!"
  94:     throw
  95: }
  96: finally
  97: {
  98:     "Finally"
  99: }

 

More complex validation scenarios

In a previous blog post I came up with another validation scenario that demonstrated the limits of the ValidateScript validation attribute. Lets assume that you want to test two [DateTime] objects that are passed into a function as parameters. These dates represent a range themselves. Normally you would check whether each of the values is within a valid range, for example they can only be within the last 7 days. Then I would make sure that the StartDate value is less than the EndDate value. Here is where the limits of the ValidateScript validation are. If StartDate parameter is defined before the EndDate, the the validation script in the EndDate declaration can explicitly refer to the $StartDate parameter name, but the StartDate declaration can’t refer to the $EndDate parameter, because it hasn’t been discovered yet. Look at line 13 in the following example.

   1: function Get-WeatherStationHistory
   2: {
   3:     
   4:     [CmdletBinding(DefaultParameterSetName="PreviousDays")]
   5:     PARAM(
   6:         
   7:         [Parameter(Position=0, Mandatory=$false, ValueFromPipeline=$false, ParameterSetName="PreviousDays")]
   8:         [ValidateScript({([DateTime]::Now - $_) -ge "1/1/2006"})
   9:         [TimeSpan] $PreviousDays,
  10:         
  11:         [Parameter(Position=0, Mandatory=$false, ValueFromPipeline=$false)]
  12:         [ValidateScript({$_ -le [DateTime]::Now -and $_ -ge "1/1/2006"})
  13:         #[ValidateScript({$_ -le $LastDay })] Problem $LastDay is not defined yet
  14:         [DateTime] $FirstDay=[DateTime]::Now,        
  15:         
  16:         [Parameter(Position=1, Mandatory=$false, ValueFromPipeline=$false, ParameterSetName="NextDays")]
  17:         [ValidateScript({($FirstDay + $_) -le [DateTime]::Now})
  18:         [TimeSpan] $NextDays,
  19:         
  20:         [Parameter(Position=1, Mandatory=$false, ValueFromPipeline=$false, ParameterSetName="LastDay")]
  21:         [ValidateScript({$_ -le [DateTime]::Now -and $_ -ge "1/1/2006"})
  22:         [ValidateScript({$_ -ge $FirstDay })]
  23:         [DateTime] $LastDay=[DateTime]::Now,
  24:         
  25:         [Parameter(Position=2, Mandatory=$true, ValueFromPipeline=$false)]
  26:         [ValidateSet("KCARIVER16", "KCAMOREN6", "KCABEAUM3", IgnoreCase = $true)]
  27:         [String] $WeatherStationCode = "KCAMISSI5"
  28:  
  29:     
  30:     )
  31:     Process{
  32:     
  33:         switch ($PsCmdlet.ParameterSetName) 
  34:         { 
  35:             "NextDays"    
  36:             { 
  37:                 "Parameter Set NextDays"
  38:                 Process-Downloads -FirstDay $FirstDay -NumberOfDays $NextDays -Station $WeatherStationCode
  39:             } 
  40:             
  41:             "LastDay"       
  42:             { 
  43:                 "Parameter Set LastDay"
  44:                 $Days = $LastDay - $FirstDay
  45:                 Process-Downloads -FirstDay $FirstDay -NumberOfDays $Days -Station $WeatherStationCode
  46:             }
  47:             
  48:             "PreviousDays"       
  49:             { 
  50:                 "Parameter Set PreviousDays"
  51:                 $StartDay = [DateTime]::Now - $PreviousDays
  52:                 Process-Downloads -FirstDay $StartDay -NumberOfDays $PreviousDays -Station $WeatherStationCode
  53:             }
  54:         } 
  55:     
  56:     }# End Process
  57: }

Download

The script examples can be downloaded here: LimitationsOfValidateRangeAttributes.zip

Ausblick

I thought about a few other ways to do validation with dynamic ranges:

1) Create a custom Validation Attribute in C#

2) Use the normal way to do it, by using conditional statements.

3) Create the script function on the fly and passing in the ranges as constant values into the ValidateRange attribute.

Of course a more powerful range validation that ships with PowerShell would be my preference.

Tags: , , , ,

PowerShell | Tips & Tricks | Weather Station | Debugging

Comments

8/16/2009 11:36:42 AM #

pingback

Pingback from powerscripting.wordpress.com

Episode 80 – Klaus Graefensteiner «  PowerScripting Podcast

powerscripting.wordpress.com |

3/11/2010 1:28:32 AM #

pingback

Pingback from spamparty.com

Fixing PowerShell limitations with hair grow products | Spam Party

spamparty.com |

Comments are closed

About Klaus Graefensteiner

I like the programming of machines.

Add to Google Reader or Homepage

LinkedIn FacebookTwitter View Klaus Graefensteiner's profile on Technorati
Klaus Graefensteiner

Klaus Graefensteiner
works as Developer In Test and is founder of the PowerShell Unit Testing Framework PSUnit. More...

Open Source Projects

PSUnit is a Unit Testing framwork for PowerShell. It is designed for simplicity and hosted by Codeplex.
BlogShell is The tool for lazy developers who like to automate the composition of blog content during the writing of a blog post. It is hosted by CodePlex.

Administration

About

Powered by:
BlogEngine.Net
Version: 1.6.1.0

License:
Creative Commons License

Copyright:
© Copyright 2014, Klaus Graefensteiner.

Disclaimer:
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

Theme design:
This blog theme was designed and is copyrighted 2014 by Klaus Graefensteiner

Rendertime:
Page rendered at 7/23/2014 11:25:29 PM (PST Pacific Standard Time UTC DST -7)