In this function, there are two odd behaviors (tested on Windows PS 5.1/7.4.6):
- The validation script is run more than once
$FilePath
never casts to a FileInfo object and remains as a String
function Test-Param {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateScript({
Write-Host "Test $((Get-Date).Ticks)"
[IO.File]::Exists( (Resolve-Path -Path $_).Path )
})]
[string] $FilePath
)
Start-Sleep -Milliseconds 50
Write-Host "Start"
$FilePath = New-Object IO.FileInfo (Resolve-Path -Path $FilePath).Path
Write-Host "After expected object type change"
$FilePath.GetType().FullName
$FilePath
}
Test-Param -FilePath $MyInvocation.MyCommand.Path
Here is the output:
Test 638746054867610941
Start
Test 638746054868236274
After expected object type change
System.String
C:\test\test.ps1
This seems like it shouldn't happen, I'd expect parameter validation to only run once and the FilePath variable to get overwritten with a different type of object. Was thinking about posting on powershell github but figured I'd ask the SO community first for thoughts... Is this by design or a potential bug?
Update - as noted in the comments by @theo, if the parameter type is removed or changed to [object]
FilePath object type will be a FileInfo object as expected.
In this function, there are two odd behaviors (tested on Windows PS 5.1/7.4.6):
- The validation script is run more than once
$FilePath
never casts to a FileInfo object and remains as a String
function Test-Param {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateScript({
Write-Host "Test $((Get-Date).Ticks)"
[IO.File]::Exists( (Resolve-Path -Path $_).Path )
})]
[string] $FilePath
)
Start-Sleep -Milliseconds 50
Write-Host "Start"
$FilePath = New-Object IO.FileInfo (Resolve-Path -Path $FilePath).Path
Write-Host "After expected object type change"
$FilePath.GetType().FullName
$FilePath
}
Test-Param -FilePath $MyInvocation.MyCommand.Path
Here is the output:
Test 638746054867610941
Start
Test 638746054868236274
After expected object type change
System.String
C:\test\test.ps1
This seems like it shouldn't happen, I'd expect parameter validation to only run once and the FilePath variable to get overwritten with a different type of object. Was thinking about posting on powershell github but figured I'd ask the SO community first for thoughts... Is this by design or a potential bug?
Update - as noted in the comments by @theo, if the parameter type is removed or changed to [object]
FilePath object type will be a FileInfo object as expected.
1 Answer
Reset to default 1(Parameter) variables implement
type constraints (e.g., placing
[string]
before of a parameter-variable declaration)attribute decorations (e.g., placing a
[ValidateScript({...})]
attribute before a parameter-variable declaration)
as attributes that are attached to the variable object (verify with (Get-Variable FilePath).Attributes | Format-List
from your function body), which are notably evaluated every time the variable is assigned to.[1]
The above explains all behaviors you've observed:
Because you've type-constrained
$FilePath
to[string]
, the corresponding attribute also converts whatever value you assign later to a[string]
, effectively preventing a type change.Because you're assigning to
$FilePath
again in the body of your function ($FilePath = New-Object IO.FileInfo ...
), the[ValidateScript()]
attribute's script block is invoked again.
You can bypass the (potentially surprising) behavior as follows:
Treat parameter-declaration variables as read-only - that way, whatever constraints and validation is associated with them are only evaluated once, during parameter binding.
Use separate, regular variables with different names in the body of your function or script to perform transformations on the parameter values; e.g., in your case:
# $filePathInfo is a function-local variable that stores a transformation of # the $FilePath parameter-variable value. $filePathInfo = New-Object IO.FileInfo (Resolve-Path -Path $FilePath).Path
[1] Note that this also applies to regular variables, i.e. variables created in the body of a function rather than to define parameters inside the param(...)
block.
While type constraints on regular variables are not uncommon (e.g., [int] $i = 42
; see this answer), validation attributes are rare (e.g., [ValidateRange(1, 41)] [int] $i = 41
; trying to assign a value outside that range later, e.g. $i = 42
, then causes an (statement-terminating) error).
[string]
cast on the parameter variable itself. This makes PowerShell not allowing this variable to change into a different object type. If you remove the[string]
cast from the $FilePath parameter everything works as you expect – Theo Commented 2 days ago[object]
. It does seem strange to me that some part of the execution process is either re-casting it back to string or not letting the parameter value be overwritten. I assume since we see output from the parameter script twice, that it's be recast back to string on the second validation script run but not sure... Also strange that the validation script is run more than once. – Andy Arismendi Commented 2 days ago[ValidateScript(..)]
attribute needs the script to return either $true (non-zero) or $false and Write-Host doesn't do either. Why exactly then that part is tried more than once, I have no idea... – Theo Commented 2 days ago