You basically want a hash table with values that are arrays. You don't have to use $hashtable.get_item or .add
$myHashTable = @{} # creates hash table
$myHashTable.Entry1 = @() #adds an array
$myHashTable.Entry1 += "element1"
$myHashTable.Entry1 += "element2"
This results in the following output:
$myHashTable
Name Value
---- -----
Entry1 {element1, element2}
$myHashTable.Entry1
element1
element2
Answer from Jason Snell on Stack OverflowYou basically want a hash table with values that are arrays. You don't have to use $hashtable.get_item or .add
$myHashTable = @{} # creates hash table
$myHashTable.Entry1 = @() #adds an array
$myHashTable.Entry1 += "element1"
$myHashTable.Entry1 += "element2"
This results in the following output:
$myHashTable
Name Value
---- -----
Entry1 {element1, element2}
$myHashTable.Entry1
element1
element2
If you have your data in an array you can group the array and convert to a hash table:
ary = $ary + [PSCustomObject]@{RowNumber = 1; EmployeeId = 1; Value = 1 }
ary + [PSCustomObject]@{RowNumber = 2; EmployeeId = 1; Value = 2 }
ary + [PSCustomObject]@{RowNumber = 3; EmployeeId = 2; Value = 3 }
ary + [PSCustomObject]@{RowNumber = 4; EmployeeId = 2; Value = 4 }
ary + [PSCustomObject]@{RowNumber = 5; EmployeeId = 3; Value = 5 }
ary | Group-Object -Property EmployeeId -AsHashTable
$ht is then:
Name Value
---- -----
3 {@{RowNumber=5; EmployeeId=3; Value=5}}
2 {@{RowNumber=3; EmployeeId=2; Value=3}, @{RowNumber=4; EmployeeId=2; Value=4}}
1 {@{RowNumber=1; EmployeeId=1; Value=1}, @{RowNumber=2; EmployeeId=1; Value=2}}
Need assistance with a hashtable that returns multiple values for a key?
How to have multiple values for one key in hashtable from csv
Create HashTable With Multiple Values
Hashtables and Key-Pair, trying to have additional values per key.
Videos
Learning PS so please bear with me.
Assume I have a hash-table which has multiple values assigned per key, how do I get an accurate count of the number of values in total in the table?
When trying to count below I'm expecting to get an answer of 4, not 3.
PS C:\WINDOWS\system32> $TestHT = [ordered]@{1=1;2=2;3=3,4}
PS C:\WINDOWS\system32> $TestHT
Name Value
---- -----
1 1
2 2
3 {3, 4}
PS C:\WINDOWS\system32> $TestHT.Values.Count
3
PS C:\WINDOWS\system32> $TestHT.Values.GetEnumerator()
1
2
3
4
PS C:\WINDOWS\system32> $TestHT.Values.GetEnumerator().Count
1
1
2Do I really have to do something like
PS C:\WINDOWS\system32> $TestHT.Values.GetEnumerator().Count | Measure-Object -Sum | Select-Object Sum Sum --- 4
There must be a cleaner, better way? Also how do I get just the sum as an integer if that's how I must do it?
Hi everyone,
My team and I are using PS Universal and absolutely loving it! At the moment I have a couple of Pages created which make use of APIs to gather some AD user info and display it. Works great except I'm getting hung up on one part. Within a hashtable I have the following:
param(
[Parameter(Mandatory)]
$Sam
)
$User = Get-ADUser -Identity $Sam -Properties *
$userObj = @{ }
$userObj.Name = ""
$userObj.Email = ""
$userObj.firstName = ""
$userObj.lastName = ""
$userObj.Id = ""
$userObj.Groups = @()
$User | % {
$userObj.Name = $User.Name
$userObj.Email = $User.UserPrincipalName
$userObj.firstName = $User.GivenName
$userObj.lastName = $User.Surname
$userObj.Id = $User.SID.value
$userObj.Groups = $User.MemberOf | % {
@{
GroupName = $_
}
}
$importDetails = New-Object -TypeName PSObject -Property $userObj
}
$userObjNow, the issue I am running into is on the PS U page I have a table that I created and am calling one column specifically from the generated hashtable, Groups. When I do this, the data is certainly there, but it looks horrific in the table on the PS U web page! Its basically all of the AD group membership CNs for the user, but in one LONG string. I would like to list each group on a separate line in the table. Is that possible? Thanks!
I find it always helps to understand the end goal of what I'm attempting to do, so here is a summary of my task and how I'm currently attempting to accomplish it. My understanding is that hashtables will not really accomplish what I want as they are key-pair, but hopefully someone knows how to get this to work the way I want:
-
Problem: We have biweekly software deployments that require manual checking to confirm that build versions have been approved for production. We use an application called cruisecontrol to track our builds, and as part of our process we go through each individual build's notes and search for ticket numbers. On the tickets themselves, they have a check box that confirm if they are approved or not. As this is a manual process right now, and we push anywhere from 5-10 applications at a time, with as many as 20 builds on each, this is a pretty long process that can easily eat up an hour of two for one employee, if not more.
-
Goal: I am approaching this problem in segments, my first checkpoint will be getting to the point of pulling out the ticket numbers from each build and displaying it in a list with two pieces of data linked to it. I want to report the URL that I pulled the ticket number from, the name of the link within cruisecontrol I would have to click on to check manually, and any ticket numbers within the build. After this is complete, I will be making a separate function that can read this output and check the ticketing system for approvals. I create everything as a function these days so that it's reusable as possible and I don't have to hardcode many variables, if any at all.
-
Current Approach / Progress: My function is using invoke-webrequest on the builds dashboard. When I request data, I provide the application name, the build number being deployed, and the previously deployed version. On this page we have links to every build, in the following format '2016-01-01 00:00:00 (0000)' - the last (0000) being the build number. The links to builds are in descending order, with the latest build being on the top. With my webrequest I pull in all of the links and am able to narrow them down by class to get only the links I'm interested in. I place them in an array, and then use IndexOf to count through the array looking for my build numbers. After getting the positions I create 2 additional arrays with the href and outertext (contains link name) from my array of link information. I create a hash table linking the two arrays I've created, the href array being the key, and the outertext being a value. With this complete, I cycle through my hash table, going to each link, doing an additional webrequest and using select-string to search the rawcontent for a regex that pulls in the ticket numbers. Occasionally if the build ticket number is formatted differently, the regex search will not pull in the ticket number correctly. When this happens, I have it storing the value False where the ticonditioanl would be, and an additional conditional statement triggers a check that confirms if there is actually a ticket within the build. If this returns true, it will create a flag for that specific build indicating it requires a manual check. I intend to either adjust my regex to get everything in one pass as possible, but that is more of a phase two problem to work on in this segment.
-
Problem: What I want to do is have an additional value attached to each key, so that I have everything in one place. On builds that the regex fails, I want to be able to push out the values of the link name and that a manual check is required. On the builds that are successful, I want to push out the link name and the ticket numbers.
-
Solution: I'm aware that I could make the value an array, but I would prefer not to approach the problem this way. I know it's a completely viable solution, and I might end up doing that, but I'm already familiar with that concept and I would like to learn how to do this differently. I've considered making two hash tables, both of which use the href data as the key, and then holding the different values. If possible I'd like to avoid this solution as I feel it's a neater and less prone to errors if the data is all in one place. I know I could also setup an actual database to do this as well, but that seems like an extreme for something like this (I wont be doing that, I'll use an array first). If anyone has some ideas on how to approach this, or how to get hashtables (or the method I'm really looking for, as my understand hashtables wont do this) I'd really appreciate it!
Thanks for taking the time to read!
If you want to add multiple values to the same key, you need to test for whether the key is already present and act accordingly:
$keys = 1,2,3,1
$value = 'a value'
$hashtable = @{}
foreach($key in $keys){
if(-not $hashtable.ContainsKey($key)){
# First time we see this key, let's make sure the value is a resizable array
$hashtable[$key] = @()
}
# Now we can safely add values regardless of whether the key already exists
$hashtable[$key] += $value
}
To complement the answer from @Matthias:
You can't have duplicate keys in a hash table. This is by design. Hash tables contain a list of unique keys based on a binary search algorithm where each key is linked to a specific object (value).
There are two workarounds:
1. add an array of values to a specific hash table key:
$Keys = 1,2,2,3
$Values = 'a'..'d'
$HashTable = @{}
$i = 0
foreach($key in $Keys){ [array]$HashTable[$Key] += $Values[$i++] }
Iterating
The disadvantage is that you lose the order of the items (regardless if you use an ordered dictionary simply because some values share a single key).
Foreach ($Key in $Hashtable.Keys) {
Foreach ($Value in @($Hashtable[$Key])) {
Write-Host $Key '=' $Value
}
}
3 = d
2 = b
2 = c
1 = a
Searching
The advantage is that the items are binary indexed and therefor quickly found.
$SearchKey = 2
Foreach ($Value in @($Hashtable[$SearchKey])) {
Write-Host $SearchKey '=' $Value
}
2 = b
2 = c
2. or you might create a list (array) of hash tables (Key Value Pairs)
$Keys = 1,2,2,3
$Values = 'a'..'d'
$i = 0
$KeyValuePairs = foreach($key in $keys){ @{ $Key = $Values[$i++] } }
Iterating
The advantage is that the items will stay in order.
(Note that you have to invoke the GetEnumerator() method twice)
$KeyValuePairs.GetEnumerator().GetEnumerator() | Foreach-Object {
Write-Host $_.Key '=' $_.Value
}
1 = a
2 = b
2 = c
3 = d
Searching
The disadvantage is that searching for the items is slower and little more comprehensive.
$SearchKey = 2
$KeyValuePairs.GetEnumerator().GetEnumerator() |
Where-Object Key -eq $SearchKey | Foreach-Object {
Write-Host $_.Key '=' $_.Value
}
2 = b
2 = c
I have a function that takes a hashtable as input with a specific structure. For example:
$Hash = @{
Key1 = ''
Key2 = ''
HashKey1 = @{
Key3 = ''
Key4 = ''
}
}I'd like to validate that the hash that is provided matches this structure. Of course I can manually check with something like:
Compare-Object $Hash.Keys $InputHash.Keys Compare-Object $Hash.HashKey1.Keys $InputHash.HashKey1.Keys
But this would only work when I manually enter the HashKey1 key name, since it's a nested hash--including any additional nested hashes.
Is there a way to dynamically compare the input hash with my own hash and verify the entire structure, including all nested hashes, matches?
Edit: For example, if I want my function to use the values provided in input hash, I want to first check and make sure the user even provided the correct hash.
function MyFunction ($InputHash){
if ($InputHash.Fruits.Fruit1 -eq 'apples'){
return 'apples'
} else {
return 'No apples'
}
}
$Hash = @{
Fruit1 = 'apples'
}
MyFunction -InputHash $HashIn this example, my function is looking in the $InputHash hashtable for the 'Fruits' key, which itself is a hashtable containing the 'Fruit1' key. The user created a hash that only contains the 'Fruit1' key at the root, instead of inside the 'Fruits' hashtable key.
I need a way to provide my own hash with the key structure I want, and verify that what the user is providing matches that structure. The values don't need to match, just the key structure.
Edit: u/logicalmike figured out a simple way to do it that works.
https://www.reddit.com/r/PowerShell/comments/w5l84f/how_to_validate_multiple_levels_of_hashtable_keys/ih9172j/
$NoValuesA = (($HashA | ConvertTo-Xml -As stream) -split "\n" | where {$_ -notmatch 'Name="Value"'} ) -join ""
$NoValuesB = (($HashB | ConvertTo-Xml -As stream) -split "\n" | where {$_ -notmatch 'Name="Value"'} ) -join ""
$NoValuesA -eq $NoValuesB