Home > Enterprise >  Generating dynamic validate set based on value of another parameter in PowerShell
Generating dynamic validate set based on value of another parameter in PowerShell

Time:06-25

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

The raw data will be gotten using an Import-Csv. The CSV is updated on a regular basis.

We are trying to use PowerShell classes to enable people to select the City based on the State they select. Here is the MWE we have gotten so far:

$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 demo

CodePudding user response:

I like to use the Register-ArgumentCompleter cmdlet for that kind of things. 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)

  switch ($ParameterName) {
    'State' { $Data | Select-Object -ExpandProperty State -Unique | Sort-Object | % { [System.Management.Automation.CompletionResult]::new($_) } }
    'City' {
      if ($fakeBoundParameters.ContainsKey('State')) {
        $Data | Where-Object -Property State -eq $fakeBoundParameters.Item('State') | Select-Object -ExpandProperty City -Unique | Sort-Object | % { [System.Management.Automation.CompletionResult]::new($_) } 
      }
      else {
        $Data | Select-Object -ExpandProperty City -Unique | Sort-Object | % { [System.Management.Automation.CompletionResult]::new($_) } 
      }

    }
  }
}

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.

Bonus VSCode Snippet

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

  • Related