Ah! Hash tables!
Ever since I saw a presentation by Anthony Howell ( Blog ), aka PoshWolf, about them, read the blog post by Kevin Marquette ( Blog ) about them, and was enlightened by multiple PowerShell community members (e.g. Chris Dent, Mathias “IISResetMe” Jessen, etc.) about them, I’ve loved hash tables.
However, there is one drawback I have with hash tables: they don’t default to the order in which they are inserted. I’m OK with this since I come from a DB background, and I’m used to order not being enforced unless I specify an ORDER BY. Not everyone is as lenient as we are, though, and the vast majority of the louder masses expect this ordering.
Now, there are ways around this unordered aspect of hash tables. The main way is the [ordered] type accelerator; you can add this to the start of your hash table to keep the default orderings.
I use this when I want hash tables, but also want guaranteed orderings.
Well, that was a short blog post. Thanks!
Sacrificing Idiomatic for Changeable
I know about using [ordered], and now you’ve learned about using [ordered], but what about people using my scripts that aren’t part of either knowledge group?
Oh, I could add a comment, and I could train the people, but what happens if I get hit by a bus win the lottery in the morning? There will eventually be a time when I’m not there, and people will have to use my scripts.
So, [ordered] seems a bit too unknown at the moment for the people who regularly use my scripts.
Which is a pity, cause the current version uses [ordered] hash tables pretty extensively.
Is there something else that we can use instead?
Yes, Arrays
Ah, the humble array. They’re not that bad once you get to know them.
Once I started thinking about these little data types, I realised that they could be a potential answer to this problem.
$pscustomobjectArray = @(
[PSCustomObject] @{ ID = 0; Name = 'Test'; Type = 'Dev' }
[PSCustomObject] @{ ID = 1; Name = 'Test2'; Type = 'Dev' }
[PSCustomObject] @{ ID = 2; Name = 'PreProd_1'; Type = 'PreProd' }
[PSCustomObject] @{ ID = 3; Name = 'PreProd_2'; Type = 'PreProd' }
[PSCustomObject] @{ ID = 4; Name = 'Prod_1'; Type = 'Prod' }
[PSCustomObject] @{ ID = 5; Name = 'Prod_2'; Type = 'Prod' }
[PSCustomObject] @{ ID = 6; Name = 'Prod_3'; Type = 'Prod' }
)
for ($i = 0; $i -lt $pscustomobjectArray.Count; $i++) {
"The $i-th object is $($pscustomobjectArray[$i])"
}

It would just need a bit of a rewrite…
Script

The default is because there’s always one person who just runs these things as is…
Thankfully, arrays keep their order, so I can rely on them in the script.
In Use
What does this mean? This means that you can use the standard input that people are accustomed to in PowerShell, such as PSCustomObject, CSV, JSON, etc.
As long as we can select the property, we can then filter it down to this specific property.
Example
For each of these examples, we’re passing in a list of choices. Then we’re filtering down the list to the option we chose.
We can do this multiple times, if we want.
Here’s our list of choices to… aha… choose from.

Example PSCustomObject
$ChoicesCustomObject = [PSCustomObject]@{
Name = 'Test01'
Value = '1a'
}, [PSCustomObject]@{
Name = 'Test01'
Value = '1b'
}, [PSCustomObject]@{
Name = 'Test02'
Value = '2a'
}, [PSCustomObject]@{
Name = 'Test02'
Value = '2b'
}, [PSCustomObject]@{
Name = 'Test03'
Value = '3a'
}, [PSCustomObject]@{
Name = 'Test03'
Value = '3b'
}
<# Get a unique list of available choices #>
$AvailableChoices = $ChoicesCustomObject | Select-Object -ExpandProperty Name -Unique
<# Get the chosen index from the user #>
$ChosenIndex = Initialize-Choice_v2 -Title "Test Get-Choice" -Caption "Select an option" -Choices $AvailableChoices
<# Filter down to the chosen option #>
$ChosenOption = $ChoicesCustomObject | Where-Object Name -eq $AvailableChoices[$ChosenIndex]
$ChosenOption

Example Csv
$ChoicesCSV = @'
"Name","Value"
"Test01","1a"
"Test01","1b"
"Test02","2a"
"Test02","2b"
"Test03","3a"
"Test03","3b"
'@ | ConvertFrom-Csv
<# Get a unique list of available choices #>
$AvailableChoices = $ChoicesCSV | Select-Object -ExpandProperty Name -Unique
<# Get the chosen index from the user #>
$ChosenIndex = Initialize-Choice_v2 -Title "Test Get-Choice" -Caption "Select an option" -Choices $AvailableChoices
<# Filter down to the chosen option #>
$ChosenOption = $ChoicesCSV | Where-Object Name -eq $AvailableChoices[$ChosenIndex]
$ChosenOption

Example JSON
$ChoicesJSON = @"
[
{
"Name": "Test01",
"Value": "1a"
},
{
"Name": "Test01",
"Value": "1b"
},
{
"Name": "Test02",
"Value": "2a"
},
{
"Name": "Test02",
"Value": "2b"
},
{
"Name": "Test03",
"Value": "3a"
},
{
"Name": "Test03",
"Value": "3b"
}
]
"@ | ConvertFrom-Json
<# Get a unique list of available choices #>
$AvailableChoices = $ChoicesJSON | Select-Object -ExpandProperty Name -Unique
<# Get the chosen index from the user #>
$ChosenIndex = Initialize-Choice_v2 -Title "Test Get-Choice" -Caption "Select an option" -Choices $AvailableChoices
<# Filter down to the chosen option #>
$ChosenOption = $ChoicesJSON | Where-Object Name -eq $AvailableChoices[$ChosenIndex]
$ChosenOption

Example Multiple
$ChoicesCustomObject = [PSCustomObject]@{
Name = 'Test01'
Value = '1a'
}, [PSCustomObject]@{
Name = 'Test01'
Value = '1b'
}, [PSCustomObject]@{
Name = 'Test02'
Value = '2a'
}, [PSCustomObject]@{
Name = 'Test02'
Value = '2b'
}, [PSCustomObject]@{
Name = 'Test03'
Value = '3a'
}, [PSCustomObject]@{
Name = 'Test03'
Value = '3b'
}
<# Get a unique list of available choices #>
$AvailableChoices = $ChoicesCustomObject | Select-Object -ExpandProperty Name -Unique
<# Get the chosen index from the user #>
$ChosenIndex = Initialize-Choice_v2 -Title "Test Get-Choice" -Caption "Select an option" -Choices $AvailableChoices
<# Filter down to the chosen option #>
$ChosenOption = $ChoicesCustomObject | Where-Object Name -eq $AvailableChoices[$ChosenIndex]
<# Filter down to the value #>
$ChosenValue = Initialize-Choice_v2 -Title "Choose Value" -Caption "Select a value for $($ChosenOption.Name)" -Choices $ChosenOption.Value
<# Return the chosen option #>
$ChosenOption[$ChosenValue]

Caveat
Yes, I am aware that the values are off by one; for example, the first value shows 1 on the confirmation screen but returns a 0. I bowed to requests from people who would be using the code but were not comfortable with indexes starting at 0.
Feel free to change that in your script.
Overall
This is a case of “exception making the rule”. I’ve said before that I prefer not to have user prompting in my scripts, since a script that requires human intervention is not something that can be fully automated.
However, whether it’s risk-averse business leaders or novices to scripting, there are times when an expectation of human interaction is present.
If there is no getting away from this, then I can make the process easier. Here’s hoping that making it easier and easier to use will lead to more automation… or at least more.
Hopefully, this script can help.


































