Powershell: Passing a `Read-Host` as the key to HashTable - powershell

I am trying to pass user input to hashTable, which I am attempting to do.
$someHash =
#{
1 = "one";
2 = "two";
3 = "three"
}
$x = Read-Host "enter 1,2,3"
Write-Output($someHash.$x)
For some reason $someHash.$x and $someHash[$x] return null, but $x has it value.
I also can get values out of the map when I hard code keys. Not sure what I am doing incorrectly.
Adding to address comments.
I have also tried casting with the same result.
$someHash.[int]$x
$someHash[[int]$x]
[int]$x = Read-Host "enter 1,2,3"
I have also used strings as keys using single and double-quotes. Same outcome.

Using string keys you won't have a need to convert the result of Read-Host to int.
Note, if below doesn't work, it's most likely because $x was type constrained before ([int]$x), use Remove-Variable x or restart your PowerShell session.
$someHash =
#{
'1' = "one";
'2' = "two";
'3' = "three"
}
$x = Read-Host "enter 1,2,3"
$someHash[$x] # => Works (Recommended)
$someHash.$x # => Works
Using int keys you would need to type constrain the result of Read-Host to int:
$someHash =
#{
1 = "one";
2 = "two";
3 = "three"
}
[int]$x = Read-Host "enter 1,2,3"
$someHash[$x] # => Works (Recommended)
$someHash.$x # => Works

Related

Missing property name after reference operator

I have a map, that is originally c++ code, file read, and parsed into a map. The original code was an enum, and didn't have values for all items. $fileContent:
enum{
Error_A = 110,
Error_B,
Error_C,
Error_D,
Error_E,
Error_F,
Error_G = 118,
...
};
I have read the file contents and put it in a map like this (works fine):
function Get-Contents_b{
[cmdletbinding()]
Param ([string]$fileContent)
#Error_AA = 20
# create an ordered hashtable to store the results
$errorMap = [ordered]#{}
# process the lines one-by-one
switch -Regex ($fileContent -split '\r?\n') {
'^[\s]*([\w]+)[\s=]*([-\d]*)' { # Error...=12345
$key,$value = ($matches[1,2])|ForEach-Object Trim
$errorMap[$key] = $value
}
}
...
Then I want to iterate over the map, and for the ones with enum values dependent on single digit increase from the previous, I want to assign the value of the previous value plus one. I'm trying to do that below, but getting the $previousKey, using $key-1, and then getting the value from that, is giving the error shown in the comment.
foreach ($key in $errorMap.$keys)
{
$previousKey = $errorMap.[($key-1)] #missing property name after the reference operator
Write-Host $errorMap.$previousKey
if(($errorMap.$key).Value = "")
{
$errorMap.$key.Value = $errorMap.$previousKey.Value + 1
}
}
Any ideas how to fix this or get the previous value and assign the next empty value the previous value plus one?
This is with powershell 5.1 and VSCode.
You're mistakenly mixing member (property) access via the . operator with indexed access via [...] - you must use one or the other.
However, what you want is positional access to your keys (which only works with an ordered hashtable):
foreach ($keyIndex in 0..($errorMap.Count-1))
{
if ('' -eq $errorMap[$keyIndex]) {
$previousValue = $errorMap[$keyIndex - 1]
Write-Host $previousValue
$errorMap[$keyIndex] = 1 + $previousValue
}
}
Why not create the values in your Hashtable straight away instead of filling the empties afterwards?
function Get-Contents_b{
[cmdletbinding()]
Param ([string]$fileContent)
# create an ordered hashtable to store the results
$errorMap = [ordered]#{}
$currentValue = 0
# process the lines one-by-one
switch -Regex ($fileContent -split '\r?\n') {
'^\s+(\w+)\s*[,=]'{
$key, $value = ($_ -split '[,=]', 2).Trim()
if ([string]::IsNullOrWhiteSpace($value)) { $value = $currentValue }
$errorMap[$key] = [int]$value
$currentValue = [int]$value + 1
}
}
# return the map
$errorMap
}
Get-Contents_b $enum
Output:
Name Value
---- -----
Error_A 110
Error_B 111
Error_C 112
Error_D 113
Error_E 114
Error_F 115
Error_G 118

Processing items within a powershell array - multiple items

I have been reading about arrays in powershell and hash tables, I know the basic workings of an array and how to use foreach loop to get items within the array, my challange here is slightly different. I would like to pay what I call a multi dimension array to a script, and process the items contained within the array.
What is my setup.
$x = (1,"Server1",3,1),(4,"Server2",6,2),(3,"Server3",4,3)
$k = 'serverid','servername','locationid','appid' # key names correspond to data positions in each array in $x
$h = #{}
For($i=0;$i -lt $x[0].length; $i++){
$x |
ForEach-Object{
[array]$h.($k[$i]) += [string]$_[$i]
}
}
What am i trying to achieve ?
I am trying to achieve the structure of a database table within powershell. So literally treating each array item as a row.
So for example
(1,"Server1",3,1),(4,"Server2",6,2),(3,"Server3",4,3)
could be thought of as a table like below
enter image description here
I then want to loop for each item in the array to get the values, similar to the below example
[0].serverid = 1, [0].servername = server1, [0].locationid = 3, [0].applicationID = 1
[1].serverid = 4, [1].servername = server2, [1].locationid = 6, [1].applicationID = 2
what have I done ?
$x = (1,"Server1",3,1),(4,"Server2",6,2),(3,"Server3",4,3)
$k = 'serverid','servername','locationid','appid' # key names correspond to data positions in each array in $x
$h = #{}
For($i=0;$i -lt $x[0].length; $i++){
$x |
ForEach-Object{
[array]$h.($k[$i]) += [string]$_[$i]
}
}
$x
for ($i = 0; $i -lt $x.Count; $i++)
{
$myserverid = $x[$i][0]
$myservername = $x[$i][1]
$mylocationid = $x[$i][2]
$myappid = $x[$i][3]
write-host $myserverid
}
The Issues
If I set the following $x = (1,"Server1",3,1), then the loop is somewhat incorrect which is why I think the approach is wrong (more than one item works i.e $x = (1,"Server1",3,1),(4,"Server2",6,2),(3,"Server3",4,3)). The loop only works if you have more than one item within the array, hence why I want to re-examine the way the loop works.
Thanks in advance
Your approach relies on a nested (jagged) array: That is, you have an array of subarrays, each of which represents the tuple of values you want to assign to properties.
If there's only one subarray, you must create the nested array explicitly, using the unary form of , the array constructor operator:
# Construct a 1-element array that contains the 4-element subarray.
$x = , (1,"Server1",3,1)
With two or more, subarrays, you implicitly get a nested array:
# Construct a 3-element array, each element of which contains a 4-element subarray.
$x = (1,"Server1",3,1), (4,"Server2",6,2), (3,"Server3",4,3)
Note that in PSv5+ you could use a custom class to solve your problem:
class Custom {
[int] $serverid; [string] $servername;[int] $locationid; [int] $appid
Custom($propValueArray) {
$this.serverid = $propValueArray[0]; $this.servername = $propValueArray[1]; $this.locationid = $propValueArray[2]; $this.appid = $propValueArray[3]
}
}
# Use an array cast to construct [Custom] instances.
# Note the need for (...) around the array, because casts have high
# precedence in PowerShell.
[Custom[]] ((1,"Server1",3,1), (4,"Server2",6,2), (3,"Server3",4,3))
This would allow for processing such as:
# Construct all objects
$objects = [Custom[]] ((1,"Server1",3,1), (4,"Server2",6,2), (3,"Server3",4,3))
# Process each object.
foreach ($object in $objects) {
($myserverid = $object.serverid) # assign a property to a var; `()` also outputs
# ...
}

Get key name in one member hashtable

I have a data structure for a multi-level lookup table that looks something like this
$lookupTable = #{
'a' = #{
'a1' = 'A One'
'a2' = 'A Two'
'a3' = 'A Three'
}
'b' = #{
'b1' = 'B One'
'b2' = 'B Two'
'b3' = 'B Three'
}
}
I use a data structure like this to pass an arbitrary number of new and revised values to update the table, like this.
$hash=#{'a'=#{'a4'='A Four'}
'b'=#{'b4'='B Four'}
'c'=#{'c1'='C One'
'c2'='C Two'
}
}
I can then use nested foreach loops to get the key names to add or update.
However, I also need to extract just a single value, and I want to keep a similar data structure so I can pass just one hash table to specify exactly what value I am looking for, such as $getValue = #{'b'='b1'}.
But to do that I need to get the key name at the first(only) index. I have tried
Write-Host "$($getValue.GetEnumerator[0])"
Write-Host "$($getValue.keys.GetEnumerator[0])"
Write-Host "$($getValue.GetEnumerator[1])"
Write-Host "$($getValue.keys.GetEnumerator[1])"
and none work. I could use a foreach again, which will only look once, but that seems inelegant. So, what am I doing wrong?
EDIT: Just to verify the loop based approach works, I did this
foreach ($key in $getValue.keys) {
if ($key) {
Write-Host "$key $($getValue.$key) = $($lookupTable.$key.($getValue.$key))"
}
break
}
and it does indeed come back with b b1 = B one. but what an ugly way to do it.
Dang, getting closer!
$key = $getValue.keys[0] does provide a key of b. But $value = $getValue.$key doesn't return b1 as expected. However, $value = $getValue[$key] does! however, then trying
$key = $getValue.keys[0]
$value = $getValue[$key]
Write-Host "$key $($getValue[$key]) = $($lookupTable.$key.$value)"
doesn't come back with the actual final value. Grr. More coffee and try again.
AHA! $getValue.keys[0] doesn't return a string. But if you cast to a string then everything is dandy. So
$key = [string]$getValue.keys[0]
$value = $getValue.$key
Write-Host "$key $value = $($lookupTable.$key.$value)"
works a treat. So I answered my own question. I guess I'll let the mods decide if this might be educational for others, or should just be deleted.
I think you can simplify this by declaring the $getValue not as a Hashtable, but rather as a simple array:
# define the value to get as array of keys.
# Element 0 => the key for $lookupTable,
# Element 1 => the key for the first nested hash
$getValue = 'b','b1'
# now, simply get the value you are looking for like this:
Write-Host $lookupTable[$getvalue[0]][$getValue[1]]
# or like so:
Write-Host $lookupTable.$($getvalue[0]).$($getvalue[1])
# or by using helper variables:
$lookupKey, $nestedKey = $getValue
Write-Host $lookupTable.$lookupKey.$nestedKey
All of the above will return the value B One

Adding a hash-table to a hash-table

I'm trying to add a hash-table to a hash-table using powershell. However, Im getting the following error:
Item has already been added. Key in dictionary: 'Dev' Key being added: 'Dev'
Here's my code:
$colors = #("black","white","yellow","blue")
$Applications=#{}
Foreach ($i in $colors)
{
$Applications += #{
Colour = $i
Prod = 'SrvProd05'
QA = 'SrvQA02'
Dev = 'SrvDev12'
}
}
What am I doing wrong?
I think what you want is something more like this:
$colors = #("black","white","yellow","blue")
$Applications=#{}
Foreach ($i in $colors)
{
$Applications[$i] = #{
Colour = $i
Prod = 'SrvProd05'
QA = 'SrvQA02'
Dev = 'SrvDev12'
}
}
I will also point out that Hashtables often need to be handled defensively. Each key must be unique but values do not need to be. Here is the typical method of handling that:
$colors = #("black","white","yellow","blue")
$Applications=#{}
Foreach ($i in $colors)
{
if($Applications.ContainsKey($i)){
#Do things here if there is already an entry for this key
}else{
$Applications[$i] = #{
Colour = $i
Prod = 'SrvProd05'
QA = 'SrvQA02'
Dev = 'SrvDev12'
}
}
}
EBGreen's helpful answer offers a solution for what you likely meant to do.
To complement that with an explanation of why your code failed:
When you use + to "add" two hashtables, their entries are merged: in other words: the entries of the RHS are added to the LHS hashtable.
(Technically, a new instance is created with the merged entries.)
However - by sensible design - merging is only performed if the hashtables have no keys in common; otherwise, you'll get the error message you saw, complaining about duplicate keys.
If this safeguard weren't in place, you would lose data if the values associated with duplicate entries differ.
Since your loop repeatedly tried to merge a hashtable with the same keys directly into an existing hashtable, your 2nd loop iteration invariably failed.
You can verify this more simply:
$Applications = #{} # create empty hashtable.
# Merge a hashtable literal into $Applications.
# This works fine, because the two hashtables have no keys in common.
$Applications += #{ first = 1; second = 2 }
# $Application now contains the following: #{ first = 1; second = 2 }
# If you now try to add a hashtable with the same set of keys again,
# the operation invariably fails due to duplicate keys.
$Applications += #{ first = 10; second = 20 } # FAILS
# By contrast, adding a hashtable with unique keys works fine:
$Applications += #{ third = 3; fourth = 4 } # OK
# $Application now contains: #{ first = 1; second = 2; third = 3; fourth = 4 }
I was looking for a way to do some sort of "list of enum" and since I'm fairly new to PowerShell, the only thing I could think of is "HashTable of HashTables!".
Could not find a way to do it all "inline", and I got some "can't add a null key to a hashtable" error. After some experimentation I ended up with this, working exactly as I wanted :
$Enums = #{}
$Enums.Frequency = #{
Daily = "daily"
Weekly = "weekly"
Monthly = "monthly"
}
$Enums.SomethinElse = #{
Thing1 = "dflskdjf"
Thing2 = 12
Thing3 = $null
}
You can then use it simply
$Enums.SomethinElse.Thing2
or (if you add it to a Global Variable)
$Global:Enums.Frequency.Daily
It's not exactly the same as the example provided but directly in line with the topic, so I hope it helps someone ending up here :)

PowerShell - why does looking up an array in a Hashtable return an empty array?

I hit this while looking for something else, so there is no wider code or purpose beyond puzzling over this example:
$H = #{} # new, empty hashtable
# String test - lookup fails, hashtable returns $null
$H["test"].GetType() # "You cannot call a method on a null-valued expression."
# Array test - should do the same
$Key = #('a','b')
$H[$Key].GetType() # Object[] what?? Where did this come from? <--<<====<<====<<
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# Double check that ..
$H.ContainsKey($Key) # False. Doesn't contain the key(!)
$H.Values.Count # 0. It's empty. As it should be.
How (why) does a lookup on an empty hashtable return an object[] when the key is an array, but not otherwise?
NB. I'm aware that you can't/shouldn't use an array as a hashtable key at least partly because arrays are not equal unless they are the same object in memory e.g. array/object keys for hashtables in powershell; but arrays do have .GetHashCode() - shouldn't it still return $null for a failed lookup no matter what they key is?
Happens on Windows/PSv4 and Linux/PS6-alpha
Here is the CoreCLR Hashtable source for this[Object key] although I can't make anything relevant of that. Maybe this is in the higher level PowerShell hashtable handling (which I haven't found yet).
This is PowerShell feature which allows you to specify array of indexes when indexing a collection:
$Array = 10..1
$Indexes = 1, 3, 5
$Array[$Indexes] # 9, 7, 5
$Hash = #{
a = 1
b = 2
c = 3
}
$Indexes = 'c', 'f', 'a'
$Hash[$Indexes] # 3, $null, 1
# System.Collections.Hashtable designed to return null,
# when asking not existing key.
$String = 'abcdefghijklmnopqrstuvwxyz'
$Indexes = 7, 4, 11, 11, 14
$String[$Indexes] # h, e, l, l, o
If you really want to index hashtable by array as single object, then you can use Item parameterized property:
$Array = 1..10
$Hash = #{ $Array = 'Value' }
$Hash.Item($Array)