最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

powershell - Function parameter ValidateScript called multiple times - Stack Overflow

programmeradmin1浏览0评论

In this function, there are two odd behaviors (tested on Windows PS 5.1/7.4.6):

  1. The validation script is run more than once
  2. $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):

  1. The validation script is run more than once
  2. $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.

Share Improve this question edited 2 days ago Andy Arismendi asked 2 days ago Andy ArismendiAndy Arismendi 52.6k17 gold badges112 silver badges128 bronze badges 5
  • Try : [IO.FileInfo]::New((Resolve-Path -Path $FilePath).Path) – jdweng Commented 2 days ago
  • 1 It has to do with the [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
  • Interesting it works to omit parameter object type or use [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
  • Well, the [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
  • Yea the write-host is there just for debugging, it doesn't return output from scriptblock, the next line is what returns the required true/false. – Andy Arismendi Commented 2 days ago
Add a comment  | 

1 Answer 1

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).

发布评论

评论列表(0)

  1. 暂无评论