Powershell Hashtable values using variable key - powershell

I am trying to let a user make a selection and then return a value from a hashtable based on that selection.
My hashtable looks like this:
$choices = #{ 0 = "SelectionA";
1 = "SelectionB";
99 = "SelectionC"}
My selection looks like this:
$selection = Read-Host -Prompt "
Please make a selection
0 - Selection A
1 - Selection B
99 - Selection C "
Then I'm trying to bring back the value based on the selection like this:
$choices.$selection
or
$choices.{$selection}
This isn't working. Is it possible to call a hashtable value using a variable as the key?
Thanks for any help you can offer!

You can use the Get_Item method.
$myChoice = $choices.Get_Item($selection)
You may have to convert the $selection variable into an integer first, since I believe it will come in as a string.
More info on hash tables: https://technet.microsoft.com/en-us/library/ee692803.aspx

The comments above are both correct answers. I'm adding them as answers to close the question.
By PetSerAl:
$choices.[int]$selection
Or by beatcracker:
$choices = #{ '0' = 'SelectionA'}
This is one of the PowerShell's automatic type conversion blindspots. Keys in your hashtable are integers, but Read-Host returns strings.

The simple works in PSVersion 7
$ha=#{0='aa';1='bb'}
$sel=0
$ha.$sel // 'aa'

Related

Is it possible to use a variable to store input value(s) and then use the variable to invoke hash table value(s) using PowerShell?

I'm not sure how to explain what I'm trying to do but maybe the code will help you understand.
I might get a few terms wrong, I'm still learning.
I was not unable to find any documentation that would help my case.
I feel like I'm complicating things and I could just use a lot of "if" statements but this seemed to be a way more efficient way (if it's possible) for what I'm trying to accomplish.
I am trying to invoke multiple Hash values with the "foreach" statement using the "read-host" inputs.
#Hash
$AppGroupsHash = #{
1 = "AppADGroup1"
2 = "AppADGroup2"
3 = "AppADGroup3"
4 = "AppADGroup4"
}
$AppGroups = $AppGroupsHash.Values -as [string[]]
#write-host for flair and choices to choose from.
Write-Host "`n1 = AppADGroup1`n2 = AppADGroup1`n3 = AppADGroup1`n4 = AppADGroup1`n " -ForegroundColor Yellow
#User input value(s) for the required task
#Not sure if [string] is necessary in front of $AppGroupsKeys
[string]$AppGroupsKeys = Read-Host "Enter App Hash values (1 - 2 - 3 - 4) (No space, delimit with ';') "
foreach ($AppGroupsKey in $AppGroupsKeys.split(';'))
{
if ($AppGroupsHash.Keys -ccontains $AppGroupsKey)
{
#To test if it will print the values I'm trying to invoke
echo $AppGroupsKey
#This is where I'm getting into trouble
$AppGroupsHash.($AppGroupsKey)
}
}
When I run it. This is what I get, Read-Host Values: 1;3;4 :
#These are the values being echoed but the "$AppGroupsHash.($AppGroupsKey)" does not work
1
3
4
What it should print out:
#Test "echo values"
1
3
4
#The "values" I want
AppADGroup1
AppADGroup3
AppADGroup4
I want to use the variables I've inputted as the Keys that are stored in the hash so that I can invoke those values, these will be eventually used to add users to those groups based on their requirements.
This is what I tried:
foreach ($AppGroupsKey in $AppGroupsKeys.split(';'))
{
if ($AppGroupsHash.Keys -ccontains $AppGroupsKey)
{
#To test if it will print the values I'm trying to invoke
echo $AppGroupsKey
#This is where I'm getting into trouble
write-host "$AppGroupsHash.$AppGroupsKey;"
}
}
Output:
1
System.Collections.Hashtable.1;
2
System.Collections.Hashtable.2;
4
System.Collections.Hashtable.4;
I feel like it's possible but I also feel like I'm missing some sort of secret syntax for this to work.
Is it possible what I want to accomplish or should I try Arrays or just a bunch of "if" statements?

Running a for loop in Powershell

I am new o scripting in powershell and am from a Python background. I want to know if I'm doing this right.
I created this array and want to extract each item one by one
$M365_E3_Grps = ("O365-CHN-DomainUser,O365-Vendor-Exchange-User")
ForEach ($Indiv_Grp in $M365_E3_Grps) {
ForEach ($Indiv_Grp in $M365_E3_Grps) {
`$ADGroup = $Indiv_Grp$ADGroup = $Indiv_Grp`
I want to know if we can extract vals with a for loop like this and assign it to a variable like this.
Construct of your array
Your array is not quite correct and will be populated as a string. To create a string array you will need to quote each item in comma separated list. The parentheses are also not required.
$M365_E3_Grps = "O365-CHN-DomainUser","O365-Vendor-Exchange-User"
Your foreach keyword syntax is however correct, even if the formatting in your question was slightly off.
foreach ($Indiv_Grp in $M365_E3_Grps) {
# Assigning $Indiv_Grp to $ADGroup here is kind of redundant since
# the value is already assinged to $Indiv_Grp
$Indiv_Grp
}

Get unique values

We're trying to optimize some code that removes duplicates from an Array as fast as possible. Normally this can be easily done by piping the input to Group-Object and then using only the Name property. But we would like to avoid the pipeline, as it is slower.
However, we tried the following code:
[System.Collections.ArrayList]$uniqueFrom = #()
$From = #('A', 'A', 'B')
$From.Where({-not ($uniqueFrom.Contains($_))}).ForEach({
$uniqueFrom.Add($_)
})
$uniqueFrom
In theory, this should work. But for one reason or another the output is not the expected #('A', 'B'). Why is it not reevaluating the ArrayList in the .where clause?
In my experience reducing the 'pipe filtering' to get the unique values can be achieve by using DataView. If you are processing an array you need to convert this to a DataTable first before you get the values using the DataView.
e.g.
$arr = #('val1','val1','val1','val2','val1','val3'....)
$newDatatable = New-Object System.Data.Datatable
[void]$newDatatable.Columns.Add("FetchUniqueColumn")
foreach($e in $arr)
{
$row = $newDatatable.NewRow()
$row.Item('FetchUniqueColumn') = $e
$newDatatable.Rows.Add($row)
}
$filterDataView = New-Object System.Data.Dataview($newDatatable)
$UniqueDT = $filterDataView.ToTable($true,'FetchUniqueColumn')
$UniqueValues_array = $UniqueDT.Rows.FetchUniqueColumn
Note this is a whole lot faster if your input is a DataTable since you don't have to convert it anymore prior to setting the DataView filter for unique values to $true in creating the $UniqueDT datatable from the dataview:
$UniqueDT = $filterDataView.ToTable($true,'FetchUniqueColumn')
Tested by querying 1 column with 3000 rows datatable from SQL.
My results are as follows:
**With 1 column Data Table as input
Select -Unique - 300 ms
Using DataView - 21 ms
**With #() array as input (converted SQL results to array prior to benchmarking)
Select Unique - 262 ms
Using DataView - 106 ms
Disclaimer: in this answer I'm just explaining why the current code isn't working, not attempting to give alternative solution. For solution check the accepted answer.
Why is it not reevaluating the ArrayList in the .where clause?
It's not supposed to do this. What it is actually doing is filtering here:
$From.Where({-not ($uniqueFrom.Contains($_))})
and then executing
$uniqueFrom.Add($_)
for each element. As you did
[System.Collections.ArrayList]$uniqueFrom = #()
this array is empty and therefore will return $false for any $uniqueFrom.Contains($_)
Proof:
To verify that what I've written above is true you can do the following:
[System.Collections.ArrayList]$uniqueFrom = #()
$uniqueFrom.add("A")
$From.Where({-not ($uniqueFrom.Contains($_))}).ForEach({
$uniqueFrom.Add($_)
})
Output is A, B (A was added manually, two A were skipped as this entry already exists in $uniqueFrom, B was added inside ForEach) as expected.

finding index of key in an ordered dictionary in powershell

I am having a little bit of trouble with hashtables/dictionaries in powershell. The most recent roadblock is the ability to find the index of a key in an ordered dictionary.
I am looking for a solution that isn't simply iterating through the object.
(I already know how to do that)
Consider the following example:
$dictionary = [Ordered]#{
'a' = 'blue';
'b'='green';
'c'='red'
}
If this were a normal array I'd be able to look up the index of an entry by using IndexOf().
[array]::IndexOf($dictionary,'c').
That would return 2 under normal circumstances.
If I try that with an ordered dictionary, though, I get -1.
Any solutions?
Edit:
In case anyone reading over this is wondering what I'm talking about. What I was trying to use this for was to create an object to normalize property entries in a way that also has a numerical order.
I was trying to use this for the status of a process, for example:
$_processState = [Ordered]#{
'error' = 'error'
'none' = 'none'
'started' = 'started'
'paused' = 'paused'
'cleanup' = 'cleanup'
'complete' = 'complete'
}
If you were able to easily do this, the above object would give $_processState.error an index value of 0 and ascend through each entry, finally giving $_processState.complete an index value of 5. Then if you compared two properties, by "index value", you could see which one is further along by simple operators. For instance:
$thisObject.Status = $_processState.complete
If ($thisObject.Status -ge $_processState.cleanup) {Write-Host 'All done!'}
PS > All done!
^^that doesn't work as is, but that's the idea. It's what I was aiming for. Or maybe to find something like $_processState.complete.IndexNumber()
Having an object like this also lets you assign values by the index name, itself, while standardizing the options...
$thisObject.Status = $_processState.paused
$thisObject.Status
PS > paused
Not really sure this was the best approach at the time or if it still is the best approach with all the custom class options there are available in PS v5.
It can be simpler
It may not be any more efficient than the answer from Frode F., but perhaps more concise (inline) would be simply putting the hash table's keys collection in a sub expression ($()) then calling indexOf on the result.
For your hash table...
Your particular expression would be simply:
$($dictionary.keys).indexOf('c')
...which gives the value 2 as you expected. This also works just as well on a regular hashtable... unless the hashtable is modified in pretty much any way, of course... so it's probably not very useful in that case.
In other words
Using this hash table (which also shows many of the ways to encode 4...):
$hashtable = [ordered]#{
sample = 'hash table'
0 = 'hello'
1 = 'goodbye'
[char]'4' = 'the ansi character 4 (code 52)'
[char]4 = 'the ansi character code 4'
[int]4 = 'the integer 4'
'4' = 'a string containing only the character 4'
5 = "nothing of importance"
}
would yield the following expression/results pairs:
# Expression Result
#------------------------------------- -------------
$($hashtable.keys).indexof('5') -1
$($hashtable.keys).indexof(5) 7
$($hashtable.keys).indexof('4') 6
$($hashtable.keys).indexof([char]4) 4
$($hashtable.keys).indexof([int]4) 5
$($hashtable.keys).indexof([char]'4') 3
$($hashtable.keys).indexof([int][char]'4') -1
$($hashtable.keys).indexof('sample') 0
by the way:
[int][char]'4' equals [int]52
[char]'4' has a "value" (magnitude?) of 52, but is a character, so it's used as such
...gotta love the typing system, which, while flexible, can get really really bad at times, if you're not careful.
Dictionaries uses keys and not indexes. OrderedDictionary combines a hashtable and ArrayList to give you order/index-support in a dictionary, however it's still a dictionary (key-based) collection.
If you need to get the index of an object in a OrderedDictionary (or a hasthable) you need to use foreach-loop and a counter. Example (should be created as a function):
$hashTable = [Ordered]#{
'a' = 'blue';
'b'='green';
'c'='red'
}
$i = 0
foreach($key in $hashTable.Keys) {
if($key -eq "c") { $i; break }
else { $i++ }
}
That's how it works internaly too. You can verify this by reading the source code for OrderedDictionary's IndexOfKey method in .NET Reference Source
For the initial problem I was attempting to solve, a comparable process state, you can now use Enumerations starting with PowerShell v5.
You use the Enum keyword, set the Enumerators by name, and give them an integer value. The value can be anything, but I'm using ascending values starting with 0 in this example:
Enum _ProcessState{
Error = 0
None = 1
Started = 2
Paused = 3
Cleanup = 4
Complete = 5
Verified = 6
}
#the leading _ for the Enum is just cosmetic & not required
Once you've created the Enum, you can assign it to variables. The contents of the variable will return the text name of the Enum, and you can compare them as if they were integers.
$Item1_State = [_ProcessState]::Started
$Item2_State = [_ProcessState]::Cleanup
#return state of second variable
$Item2_state
#comparison
$Item1_State -gt $Item2_State
Will return:
Cleanup
False
If you wanted to compare and return the highest:
#sort the two objects, then return the first result (should return the item with the largest enum int)
$results = ($Item1_State,$Item2_State | Sort-Object -Descending)
$results[0]
Fun fact, you can also use arithmetic on them, for example:
$Item1_State + 1
$Item1_State + $Item2_State
Will return:
Paused
Verified
More info on Enum here:
https://blogs.technet.microsoft.com/heyscriptingguy/2015/08/26/new-powershell-5-feature-enumerations/
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_enum?view=powershell-6
https://psdevopsug.scot/post/working-with-enums-in-powershell/

Powershell - Use multidimention arrays for storing data - Need suggestion

I'm googling since a while, but I didn't find a solution to my problem.
I have to say I'm newbie in Powershell.
I would like to create the following array
$a = (A,B,C,D) where
A = 1 string (always)
B = 1 string (always)
C = undefined number of strings. I need to be able to add elements dynamically
D = undefined number of strings. I need to be able to add elements dynamically (same number as C)
Is this possible?
Example of 2 elements of the array
("WSTM0123456", "192.168.10.155",("WSTM8765421","WSTM9856454","WSTM1289765"),("192.36.36.36", "187.25.25.25","192.69.89.65"))
("WLDN1251254", "156.25.36.54", ("WLDN1234512", "WLDN9865323"), ("187.154.12.12","163.136.25.98"))
I don't know a priori how many elements will be in C and D and I'll have to append strings in position C and D with a for cycle.
Scope: group many strings (C & D) under the same string (A/B) which are in common.
Any help would be appreciated
Thanks,
Marco
You can do this, but it's probably quite painful as dealing with arrays is sometimes cumbersome in PowerShell due to lots of implicit flattening.
I'd suggest creating a custom type for this. Then you can also give the individual parts useful names (I don't know the purpose of what you're doing here, so I'm making up names here. Feel free to change):
$properties = #{
Name = 'WSTM0123456';
IP = [ipaddress]'192.168.10.155';
ListOfNames = #("WSTM8765421","WSTM9856454","WSTM1289765");
ListOfIPs = [ipaddress[]]#("192.36.36.36", "187.25.25.25","192.69.89.65")
}
$foo = New-Object PSObject -Property #properties
Then you can simply append new items like so:
$foo.ListOfNames += 'AnotherName'
I think this is pretty much the same idea. Use a hash table, and make two of the elements arrays. This is how you would create the arrays "on the fly" at runtime, without knowing what any of the contents were going to be in advance, taking $x and putting any item that starts with "t" in "C" , and everything else in "D":
$a = #{A = "Some string";B = "Some other string"}
$x = "one","two","three","four","five"
$x |% {
if ($_ -match "^t"){$a["C"] += #($_)}
else {$a["D"] += #($_)}
}
$a.a
Some string
$a.b
Some other string
$a.c
two
three
$a.d
one
four
five
$obj = new-object psobject -property $a