PowerShell supports validations scripts for variables. This is most commonly used to validate function parameters, but can be applied to any variable. These validation scripts run every time the variable's value is changed.
# create a variable with a validation script
[ValidateScript({
if (-not ($_.Length -in @(8, 16, 24))) {
Write-Host 'Validate Failed'
throw "Value '$_' has invalid length: $($_.Length)"
} else {
Write-Host 'Validate Success'
}
$true
})][System.String]$MyValidatedArg = "a" * 8 # prints "Validate Success"
# Each one of these will trigger the validation script
Write-Host "Several valid assignments..."
$MyValidatedArg = "b" * 8 # prints "Validate Success"
$MyValidatedArg = "b" * 8 # prints "Validate Success"
$MyValidatedArg = "b" * 16 # prints "Validate Success"
$MyValidatedArg = "b" * 24 # prints "Validate Success"
If I were to assign an invalid value to the variable, it would throw an exception
$MyValidatedArg = "foo" # throws an exception
Oddly, I can bypass the validation script if I re-specify the type-constraint during assignment. I know "just don't do that" is an option, but the main point of my question is: why does this work?
[System.String]$MyValidatedArg = "123456789" # works even though this value is invalid
At first I thought this was just a variable shadowing thing. Like, maybe I was creating a second variable or object or something by specifying the type constraint a second time. I tried capturing a reference to the variable to see if the reference would retain the validation script. It does not, but I'm not too acquainted with the internals of PowerShell to be able to know if this is a valid test.
# create a reference and assign through the reference
Write-Host "Assigning through reference..."
$refToParameter = [ref] $MyValidatedArg
$refToParameter.Value = "c" * 24 # prints "Validate Success"
Write-Host " Read via variable: $MyValidatedArg"
Write-Host " Read via reference: $($refToParameter.Value)"
# the reference and the variable are linked
# Assignments to either trigger the validation script
Write-Host "Assigning through normal variable again..."
$MyValidatedArg = "d" * 24 # prints "Validate Success"
Write-Host " Read via variable: $MyValidatedArg"
Write-Host " Read via reference: $($refToParameter.Value)"
# This works, which is odd
Write-Host "Assigning with [System.String]..."
[System.String]$MyValidatedArg = "123456789" # invalid, nothing printed
Write-Host " No exceptions yet!"
# I thought this was some sort of variable shadowing thing, but the reference shows the new value too
Write-Host "The link between the variable and the reference is still there..."
$MyValidatedArg = "e" * 8 # valid, nothing printed
Write-Host " Read via variable: $MyValidatedArg"
Write-Host " Read via reference: $($refToParameter.Value)"
# and now we appear to have killed the validation script
Write-Host "Regular invalid assignment..."
$MyValidatedArg = "123456789" # invalid, nothing printed
Write-Host " It works now for some reason"
From internals perspective, when you create a new variable, this means a PSVariable object is added to the Variable: provider, little example:
$myvar = 123
# `Get-Variable myVar` also works here and yields the same output
Get-Item Variable:myVar | Select-Object *
The PSVariable object can contain .Attributes that will determine its behavior, for example on assignment it can be transformation and validation. If you wanted to see a list of the existing attributes that can get added you could do (note this list isn't complete, you could create your own attributes by inheritance):
[psobject].Assembly.GetTypes() | Where-Object {
-not $_.IsAbstract -and $_.IsPublic -and
$_.IsSubclassOf([System.Management.Automation.Internal.CmdletMetadataAttribute]) -and
$_.GetConstructors()
}
With this background info we can now tell that the reason why you can bypass the validation is because the type-contraint assignment is removing the ValidateScript from PSVariable object, and we can prove that is the case by following these steps:
# New var is added to the Provider
[ValidateScript({ $true })] [string] $MyValidatedArg = 1
# Get the PSVariable object
$psVar = Get-Variable MyValidatedArg
# We can see that the attribute is added to the Attributes collection
$psVar.Attributes | Where-Object { $_ -is [ValidateScript] }
# After a new constraint assignment
[string] $MyValidatedArg = 1
# We can see that the attribute was removed from the original object
$psVar.Attributes | Where-Object { $_ -is [ValidateScript] }
Note that the type-constraint assignment is just one of many methods to remove the attribute, for example this would work too!
[ValidateScript({ if ($_ -gt 1) { return $false }; $true })] [int] $MyValidatedArg = 1
# Check if validation works (Should fail on assignment)
$MyValidatedArg = 2
# Get the PSVariable object
$psVar = Get-Variable MyValidatedArg
# Get the Attribute to Remove
$attrib = $psVar.Attributes | Where-Object { $_ -is [ValidateScript] }
# Remove it
$psVar.Attributes.Remove($attrib)
# Then assignment should work
$MyValidatedArg = 2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With