将 ValidateScript 与稍后在脚本中定义的自定义函数一起使用

Using ValidateScript with custom functions defined later in script

是否可以将自定义函数与 ValidateScript 一起使用,其中函数稍后在脚本中定义。 另外,调用这个函数时是否可以引用其他参数(即假设没有循环依赖)?

我明白为什么这不可能,但因为它很有用,我希望 MS 实施一些特殊规则以允许在验证参数之前读取脚本和提供函数定义正在发生。

例如

#Run-DemoScript.ps1
param (
    [Parameter(Mandatory = $true)]
    [string]$DbInstance 
    ,
    [Parameter(Mandatory = $true)]
    [string]$DbCatalog 
    ,
    [Parameter(Mandatory = $true)]

    #
    # Is this possible; i.e.
    # - Validate-Country is not defined until later in this script
    # - DbInstance and DbCatalog parameters are defined alongside Country
    [ValidateScript({Validate-Country -Country $_ -DbInstance $DbInstance -DbCatalog $DbCatalog})] 
    #


    [string]$Country
)

#returns $true if the country is in the database's country table; otherwise false
function Validate-Country {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$DbInstance
        ,
        [Parameter(Mandatory = $true)]
        [string]$DbCatalog
        ,
        [Parameter(Mandatory = $true)]
        [string]$Country
    )
    process {
        $Country = $Country -replace "'","''"
        ((Execute-SQLQuery -DbInstance $DbInstance -DbCatalog $DbCatalog -Query "select top 1 1 x from dbo.Country where Name = '$Country'") | Measure-Object | Select -ExpandProperty Count) -gt 0
    }
}

function Execute-SQLQuery {
    #...
}

"Script ran with Country $Country"

更新

您似乎可以将整个函数定义移动到 ValidateScript 属性中,并且仍然可以稍后在脚本中访问该函数;例如:

param(
    [Parameter(Mandatory = $true)]
    [ValidateScript({
        function IsValid ($test) {
            $test -eq 'test'
        }
        IsValid $_
    })]
    [string]$x
)


"Output: $x"
"Is Valid? $(IsValid $x)"

然而那是相当痛苦的。此外,它不允许引用兄弟参数(例如下面)

param(
    [Parameter(Mandatory = $true)]
    [string]$y
    ,
    [Parameter(Mandatory = $true)]
    [ValidateScript({
        function IsValid ($a,$b) {
            $a -eq $b
        }
        IsValid $_, $y
    })]
    [string]$x 
)


"X: $x"
"Y: $Y"
"Is Valid? $(IsValid $x $y)"

如果你想把它保留在一个脚本中,你可以在脚本中全部使用一个函数。我的意思是这样的:

    Function fun1 {
       <code>
       }

    Function fun2 {
       <code>
       }

    Function fun3 {
       Param (
          Validatescript({
                  fun1
                  })$Param1
           )
        <code>
        }

    #start executing code
    fun3

这将使您能够从单个文件 运行 您的脚本,而且还可以将您的功能作为脚本验证的一部分。漂亮吗?不,它有效吗?是的。我过去做过几次,效果很好。您唯一需要记住的是您的脚本变量范围。

从目前的反馈来看,PowerShell v4 目前似乎无法完全按照我的意愿行事。

我最终使用了一个简单的解决方法来实现这个 objective。它会增加一些开销,但不会太痛苦。

  1. 从文件参数中删除了 ValidateScript 验证。
  2. 创建了一个新函数,RUN,并复制了文件的参数作为这个函数的参数。这个函数出现在脚本中的什么位置并不重要,只要它出现在它被调用之前(参见第 4 步)。
  3. 向该函数的定义中添加了 ValidateScript 部分。
  4. 因为脚本的最后一行调用了这个传递所有参数的新 RUN 函数(为简单起见/减少维护,使用 @PSBoundParameters)。
  5. 将本可以放在主文件中的所有其他脚本逻辑(不包括函数定义)移动到 RUN 函数的 process 块。
  6. 当心陷阱:如果您使用默认参数,则需要处理它们,因为默认情况下它们不会包含在步骤 4 中提到的 @PSBoundParameters 中。有关详细信息,请参阅 Parameters with default value not in PsBoundParameters?.如果您将默认逻辑连同其他参数信息复制到函数定义中,这不是问题。

.

#Run-DemoScript.ps1
param (
    [Parameter(Mandatory = $true)]
    [string]$DbInstance 
    ,
    [Parameter(Mandatory = $true)]
    [string]$DbCatalog 
    ,
    [Parameter(Mandatory = $true)]
    #[ValidateScript({Validate-Country -Country $_ -DbInstance $DbInstance -DbCatalog $DbCatalog})] 
    [string]$Country
)

#move all logic from main script into here
#copy parameters from file's param definition, only add in validation
function RUN {
    param (
        [Parameter(Mandatory = $true)]
        [string]$DbInstance 
        ,
        [Parameter(Mandatory = $true)]
        [string]$DbCatalog 
        ,
        [Parameter(Mandatory = $true)]
        [ValidateScript({Validate-Country -Country $_ -DbInstance $DbInstance -DbCatalog $DbCatalog})] 
        [string]$Country
    )
    process {
        "Script ran with Country $Country"
    }
}

#returns $true if the country is in the database's country table; otherwise false
function Validate-Country {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$DbInstance
        ,
        [Parameter(Mandatory = $true)]
        [string]$DbCatalog
        ,
        [Parameter(Mandatory = $true)]
        [string]$Country
    )
    process {
        $Country = $Country -replace "'","''"
        ((Execute-SQLQuery -DbInstance $DbInstance -DbCatalog $DbCatalog -Query "select top 1 1 x from dbo.Country where Name = '$Country'") | Measure-Object | Select -ExpandProperty Count) -gt 0
    }
}

function Execute-SQLQuery {
    #...
}

RUN @PSBoundParameters #remember to handle default parameters: