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 official documentation, GetValidValues()
does not seem to accept parameter input. 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.
Note: This is on PowerShell (Core), and not Windows PowerShell. The latter does not have the IValidateSetValuesGenerator
interface.
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
. 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