简体   繁体   English

根据 PowerShell 中另一个参数的值生成动态验证集

[英]Generating dynamic validate set based on value of another parameter in PowerShell

A little background: We are working on a function that goes through hundreds of entries, similar to the following:一点背景知识:我们正在开发一个经过数百个条目的函数,类似于以下内容:

City城市 State状态 Population人口
New York纽约 New York纽约 8467513 8467513
Los Angeles洛杉矶 California加利福尼亚 3849297 3849297
Chicago芝加哥 Illinois伊利诺伊州 2696555 2696555
Houston休斯顿 Texas德克萨斯州 2288250 2288250
Phoenix凤凰 Arizona亚利桑那 1624569 1624569
Philadelphia费城 Pennsylvania宾夕法尼亚州 1576251 1576251
San Antonio圣安东尼奥 Texas德克萨斯州 1451853 1451853
San Diego圣地亚哥 California加利福尼亚 1381611 1381611
Dallas达拉斯 Texas德克萨斯州 1288457 1288457
San Jose圣荷西 California加利福尼亚 983489 983489

The raw data will be gotten using an Import-Csv .原始数据将使用Import-Csv获取。 The CSV is updated on a regular basis. CSV 会定期更新。

We are trying to use PowerShell classes to enable people to select the City based on the State they select.我们正在尝试使用 PowerShell 类使人们能够根据他们选择的State来选择City Here is the MWE we have gotten so far:这是到目前为止我们得到的 MWE:

$Global:StateData = Import-Csv \\path\to\city-state-population.csv

class State : System.Management.Automation.IValidateSetValuesGenerator {
    [string[]] GetValidValues() {
        return (($Global:StateData).State | Select-Object -Unique)
    }
}
class City : System.Management.Automation.IValidateSetValuesGenerator {
    [string[]] GetValidValues($State) {
        return ($Global:StateData | Where-Object State -eq $State).City
    }
}
function Get-Population {
    param (
        # Name of the state
        [Parameter(Mandatory, Position = 0)]
        [ValidateSet([State])]
        $State,

        # Name of the city
        [Parameter(Mandatory, Position = 1)]
        [ValidateSet([City])]
        $City
    )
    
    $City | ForEach-Object {
        $TargetCity = $City | Where-Object City -match $PSItem
        "The population of $($TargetCity.City), $($TargetCity.State) is $($TargetCity.Population)."
    }
}

Of course, according to the official documentation , GetValidValues() does not seem to accept parameter input.当然,根据官方文档GetValidValues()似乎不接受参数输入。 Is there a way to achieve this at all?有没有办法实现这一目标?

The results would need to be similar to PowerShell Function – Validating a Parameter Depending On A Previous Parameter's Value , but the approach the post takes is beyond imagination for the amount of data we have.结果需要类似于PowerShell 函数 – 根据先前参数的值验证参数,但帖子采用的方法超出了我们拥有的数据量的想象。

Note : This is on PowerShell (Core), and not Windows PowerShell.注意:这是在 PowerShell(核心)上,而不是 Windows PowerShell。 The latter does not have the IValidateSetValuesGenerator interface.后者没有IValidateSetValuesGenerator接口。

I'm honestly not sure if you can do this with two ValidateSet Attribute Declarations , however, you could make it work with one ValidateSet and a custom class that implements the IArgumentCompleter Interface since it has access to $fakeBoundParameters .老实说,我不确定您是否可以使用两个ValidateSet属性声明来做到这一点,但是,您可以使用一个ValidateSet和一个实现IArgumentCompleter接口的自定义类,因为它可以访问$fakeBoundParameters Here is an example, that for sure needs refinement but hopefully can get you on track.这是一个例子,它肯定需要改进,但希望能让你走上正轨。

using namespace System.Management.Automation
using namespace System.Management.Automation.Language
using namespace System.Collections
using namespace System.Collections.Generic

class State : IValidateSetValuesGenerator {
    [string[]] GetValidValues() {
        return $script:StateData.State | Select-Object -Unique
    }
}

class Completer : IArgumentCompleter {
    [IEnumerable[CompletionResult]] CompleteArgument(
        [string] $CommandName,
        [string] $ParameterName,
        [string] $WordToComplete,
        [CommandAst] $CommandAst,
        [IDictionary] $FakeBoundParameters
    ) {
        [List[CompletionResult]] $result = foreach($line in $script:StateData) {
            if($line.State -ne $FakeBoundParameters['State'] -or $line.City -notlike "*$wordToComplete*") {
                continue
            }
            $city = $line.City
            [CompletionResult]::new("'$city'", $city, [CompletionResultType]::ParameterValue, $city)
        }
        return $result
    }
}

function Get-Population {
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateSet([State])]
        [string] $State,

        [Parameter(Mandatory, Position = 1)]
        [ArgumentCompleter([Completer])]
        [string] $City
    )

    "State: $State \\ City: $City"
}

Demo演示

演示

I like to use the Register-ArgumentCompleter cmdlet for that kind of things.我喜欢使用Register-ArgumentCompleter cmdlet 来处理这类事情。 If you are looking just for argument completion, then it will work perfectly.如果您只是在寻找参数完成,那么它将完美地工作。 You'll have to do validation yourself within the function though as it won't prevent incorrect entry to be typed in.您必须在函数中自己进行验证,因为它不会阻止输入错误的条目。

It will however, provide a list of possible argument and the cities displayed will be only the cities associated to the State chosen.但是,它将提供可能参数的列表,并且显示的城市将仅是与所选州相关的城市。

Here's an example.这是一个例子。

$Data = @'
City|State|Population
New York|New York|8467513
Los Angeles|California|3849297
Chicago|Illinois|2696555
Houston|Texas|2288250
Phoenix|Arizona|1624569
Philadelphia|Pennsylvania|1576251
San Antonio|Texas|1451853
San Diego|California|1381611
Dallas|Texas|1288457
San Jose|California|983489
'@| ConvertFrom-Csv -Delimiter '|'


Function Get-Population {
    Param(
        [Parameter(Mandatory = $true)]
        $State, 
        [Parameter(Mandatory = $true)]
        $City
    )
    return $Data | Where-Object { $_.State -eq $State -and $_.City -eq $City } | Select-Object -ExpandProperty Population 

} 

$PopulationArgCompletion = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    

    Filter CompleteWordExpand($Property) { if ($_.$Property -like "*$wordToComplete*") { return $_.$Property } }
    

    $ReturnData = $null

    switch ($ParameterName) {
        'State' { 
            $ReturnData = $Data | CompleteWordExpand -Property State  
        }
        'City' {
            if ($fakeBoundParameters.ContainsKey('State')) {
                $ReturnData = $Data |  Where-Object -Property State -eq $fakeBoundParameters.Item('State') | CompleteWordExpand -Property City 
            }
        }
        else {
            $ReturnData = $Data | CompleteWordExpand -Property City 
        }

    }

    if ($null -ne $ReturnData) {
        return $ReturnData | Select -Unique | ForEach-Object {
            $ctText = [System.Management.Automation.CompletionResultType]::Text
            $CompletionText = $_
            if ($_.indexof(" ") -gt -1) { $CompletionText = "'$_'" }
            [System.Management.Automation.CompletionResult]::new($CompletionText, $_, $ctText, $_)
        }
    }

}


Register-ArgumentCompleter -CommandName Get-Population -ParameterName State -ScriptBlock $PopulationArgCompletion
Register-ArgumentCompleter -CommandName Get-Population -ParameterName City -ScriptBlock $PopulationArgCompletion

Additional note附加说明

If you do test this, make sure to try it out in a different file than where you executed the script above.如果您确实对此进行了测试,请确保在与执行上述脚本的位置不同的文件中进行尝试。 For some reason, VSCode and/or the PS extension do not show the argument completion if you try to do your testing (eg: Calling Get-Population to see the argument completion) in the same file you ran the script above.出于某种原因,如果您尝试在运行上述脚本的同一文件中进行测试(例如:调用Get-Population以查看参数完成),则 VSCode 和/或 PS 扩展不会显示参数完成。

Additional additional note附加说明

I edited my answer to include a CompleteWord filter that make it work properly with console tab completion, which was ommited from my initial answer.我编辑了我的答案以包含一个CompleteWord过滤器,该过滤器使其与控制台选项卡完成一起正常工作,这是我最初的答案中省略的。 Since this is not really an in the code editor and I mostly never use the console directly, I had never took that into consideration.由于这在代码编辑器中并不是真正的,而且我大多从不直接使用控制台,因此我从未考虑过这一点。

Also, I added an additional check so that any state or cities that are multiple words get automatically surrounded by single quotes during the tab completion to avoid issues.此外,我添加了一个额外的检查,以便在选项卡完成期间,任何包含多个单词的州或城市都会自动被单引号包围,以避免出现问题。

Bonus VSCode Snippet奖金 VSCode 代码段

Here is the snippet I use to generate quickly a template for the basis of doing argument completion everywhere when needed.这是我用来快速生成模板的片段,作为在需要时在任何地方进行参数完成的基础。

    "ArgumentCompletion": {
        "prefix": "ArgComplete",
        "body": [
            "$${1:MyArgumentCompletion} = {",
            "    param(\\$commandName, \\$parameterName, \\$wordToComplete, \\$commandAst, \\$fakeBoundParameters)",
            "",
            "      # [System.Management.Automation.CompletionResult]::new(\\$_)",
            "}",
            "",
            "Register-ArgumentCompleter -CommandName ${2:Command-Name} -ParameterName ${3:Name} -ScriptBlock $${1:MyArgumentCompletion}"
        ]
    }

References参考

Register-ArgumentCompleter 注册参数完成者

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM