How to output multiple hash tables in Powershell - powershell

I have a hashtable of hashtables of key/value pairs (from a .ini file). It looks like this:
Name Value
---- -----
global {}
Variables {event_log, software_repo}
Copy Files {files.mssql.source, files.utils.source, files.utils.destination, fil...
How can I output all of the key/value pairs in one hash table, instead of doing this?
$ini.global; $ini.variables; $ini."Copy Files"

Given that $hh is you hashtable of hashtables you can use a loop :
foreach ($key in $hh.keys)
{
Write-Host $hh[$key]
}

You can easily write a function to recurse through an hash:
$hashOfHash = #{
"outerhash" = #{
"inner" = "value1"
"innerHash" = #{
"innermost" = "value2"
}
}
"outer" = "value3"
}
function Recurse-Hash($hash){
$hash.keys | %{
if($hash[$_] -is [HashTable]){ Recurse-Hash $hash[$_] }
else{
write-host "$_ : $($hash[$_])"
}
}
}
Recurse-Hash $hashOfHash
The above is just write-hosting the key values, but you get the idea.

Related

Convert hashtable back to string data in efficient way

I am trying to convert a hashtable back to key-value pair in an efficient way. Currently I am using this:
$kv = ""
$hash.GetEnumerator() | ForEach {
$kv += "$($_.Name)=$($_.Value)"
}
Isn't there any way to directly convert hash table to key value pairs, or I mean string data. There is ConvertFrom-StringData to convert key value pairs to hash table. Isn't there any way to do the opposite, convert hash tables to key value pairs directly?
E.G(Key-Value pair)
a=1
b=2
c=3
I suggested this script cmdlet for this similar question:
function ConvertTo-StringData {
[CmdletBinding()]
param(
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
[HashTable[]]$HashTable
)
process {
foreach ($item in $HashTable) {
foreach ($entry in $item.GetEnumerator()) {
"{0}={1}" -f $entry.Key, $entry.Value
}
}
}
}
Example:
ConvertTo-StringData $hash
# or
$hash | ConvertTo-StringData

Append dictionary to a dictionary in powershell (hashtable)

I have two dictionaries like this:
$first = #{}
$first.Add('John', 'Doe')
$first.Add('Johnny', 'Doe')
$second = #{}
$second.Add('Jack', 'Test')
$second.Add('Jacky', 'Test')
And I have a general $all = #{} dictionary, that stands for all dictionaries combined.
Ex. when I want to see all keys that $all contains:
foreach($key in $all){
Write-Host $key
}
It will show this:
John
Johnny
Jack
Jacky
p.s. I have this one:
$all = #{}
$all_dict = #{}
$all_dict += $first
$all_dict += $second
foreach($dict in $all_dict){
foreach($key in $dict.Key){
$all.Add($key, $dict[$key])
}
}
But I was wondering if there is another way to do it without the need to add all dictionaries to an array and then iterate through them
I wouldn't do the += addition to hashtables, but instead use a ForEach-Object on the hashes .Keys. That way, the code can be shortened, but also it will leave you an easier choice whether you would want the possible duplicates from Hashtable 1 ($first) to be overwritten by the values from the second Hashtable ($second).
Something like this:
$first = #{}
$first.Add('John', 'Doe')
$first.Add('Johnny', 'Doe')
$second = #{}
$second.Add('Jack', 'Test')
$second.Add('Jacky', 'Test')
$second.Add('Johnny', 'Depp') # Duplicate key: same first name, different lastname
$all = #{}
# copy all keys and values from the $first Hashtable into $all
$first.Keys | ForEach-Object { $all[$_] = $first[$_] }
For the next part, you'll have to decide what to do with duplicate keys:
Method 1
# add the stuff from Hashtable $second to it:
# this will overwrite the value if the key already exists (i.e. $second value 'wins')
$second.Keys | ForEach-Object { $all[$_] = $second[$_] }
OR use Method 2
# make sure the value of the $first hashtable is NOT overwritten (i.e. $first value 'wins')
$second.Keys | ForEach-Object { if (!($all.ContainsKey($_))) { $all[$_] = $second[$_] }}
In case you choose to overwrite (method 1), the $all hash will contain
Name Value
---- -----
John Doe
Jacky Test
Johnny Depp
Jack Test
If you choose NOT to overwrite (method 2), $all will be
Name Value
---- -----
John Doe
Jacky Test
Johnny Doe
Jack Test
Edit
There is another approach where you rely on the fact that an exception is thrown if you try to add an entry that already exists. In that case, use the .Add(key, value) method and wrap it inside a try{..} catch{..} block.
Without that catch, the error prevents the $all Hashtable to be filled, as it stops at the first duplicate key you try to add.
$second.Keys | ForEach-Object {
try {
$all.Add($_, $second[$_])
}
catch {
# catch the exception in order to carry on adding items
# the effect will be that the values from $first will not be overwritten
# just like with method 2
Write-Warning $_.Exception.Message
}
}
I think your $all_dict already contains what you want (i.e. a hashtable with all 4 entries), but your foreach( $dict in $all_dict ) isn't enumerating the hashtable entries like you expect it to.
The quick answer is to iterate over the Keys collection instead:
foreach( $key in $all.Keys )
{
write-host $key
}
The longer answer is that in your example PowerShell is doing some "helper" things for you with enumeration - foreach($key in $all) is only enumerating over a single object ($all), but write-host $all is evaluating an array of all of the entries in $all and serializing them into a single string:
Compare the behaviour of these two lines and you can see the difference:
PS> foreach($item in #{ "aaa"="bbb"; "ccc"="ddd" }) { write-host $item }
System.Collections.DictionaryEntry System.Collections.DictionaryEntry
PS> foreach($item in #{ "aaa"="bbb"; "ccc"="ddd" }.Keys) { write-host $item }
ccc
aaa
By the way, watch out for if your keys collide - if you try #{ "aaa"="bbb"; "ccc"="ddd" } + #{ "aaa"="eee" } for example, you'll get an error Item has already been added. Key in dictionary: 'aaa' Key being added: 'aaa'. so you might want to find a better way to merge your hashtables rather than just using +.
Am I not understanding the question? You can add them.
$first = #{John = 'Doe'; Johnny = 'Doe'} # hashtables
$second = #{Jacky = 'Test'; Jack = 'Test'}
$all = $first + $second # merge two hashtables
foreach ($i in $all.getenumerator()) { $i } # loops 4 times

What is '#{}' meaning in PowerShell

I have line of scripts for review here, I noticed variable declaration with a value:
function readConfig {
Param([string]$fileName)
$config = #{}
Get-Content $fileName | Where-Object {
$_ -like '*=*'
} | ForEach-Object {
$key, $value = $_ -split '\s*=\s*', 2
$config[$key] = $value
}
return $config
}
I wonder what #{} means in $config = #{}?
#{} in PowerShell defines a hashtable, a data structure for mapping unique keys to values (in other languages this data structure is called "dictionary" or "associative array").
#{} on its own defines an empty hashtable, that can then be filled with values, e.g. like this:
$h = #{}
$h['a'] = 'foo'
$h['b'] = 'bar'
Hashtables can also be defined with their content already present:
$h = #{
'a' = 'foo'
'b' = 'bar'
}
Note, however, that when you see similar notation in PowerShell output, e.g. like this:
abc: 23
def: #{"a"="foo";"b"="bar"}
that is usually not a hashtable, but the string representation of a custom object.
The meaning of the #{}
can be seen in diffrent ways.
If the #{} is empty, an empty hash table is defined.
But if there is something between the curly brackets it can be used in a contex of an splatting operation.
Hash Table
Splatting
I think there is no need in explaining what an hash table is.
Splatting is a method of passing a collection of parameter values to a command as unit.
$prints = #{
Name = "John Doe"
Age = 18
Haircolor = "Red"
}
Write-Host #prints
Hope it helps! BR
Edit:
Regarding the updated code from the questioner the answer is
It defines an empty hash table.
Be aware that Get-Content has its own parameters!
THE MOST IMPORTANT 1:
[-Raw]

Convert Colon Separated String to a PowerShell Dictionary

I'm trying to convert Colon Separated String to a PowerShell Dictionary. Following are the strings.
$inputkeyvalues = "Appsetting:true|environment:prod"
I have two key value pairs's in the $inputkeyvalues variable and those are separated by pipe delimiter.
first one is: Appsetting:true
second one is: environment:prod
and I'm trying to convert to PowerShell dictionary. The final output should be something like,
Key Value
----- -----
Appsetting true
environment prod
$Dictionary= New-Object "System.Collections.Generic.Dictionary``2[System.String,System.String]"
Can someone please suggest me possible solution for this. Thanks in advance.
Use a hashtable:
$inputkeyvalues = "Appsetting:true|environment:prod"
# Create hashtable
$Dictionary = #{}
# Split input string into pairs
$inputkeyvalues.Split('|') |ForEach-Object {
# Split each pair into key and value
$key,$value = $_.Split(':')
# Populate $Dictionary
$Dictionary[$key] = $value
}
I stripped the #{ and } characters of the string then created the dictionary key value pairs.
$totalDict=#{}
Foreach ($itemDictString in $tempObj.tag_instances)
{
$Dictionary=#{}
$itemDictString.Replace('#{','').Replace('}','').Split(';') | ForEach-Object{
$key,$value=$_.Split('=').Trim()
$Dictionary[$key]=$value
}
$key="tag_id"
$composite_key="$($tempObj.first_name) $($tempObj.last_name) $($tempObj.id) $($Dictionary[$key])"
Write-Host $composite_key
if($totalDict.ContainsKey($composite_key) -eq $true)
{
$totalDict[$composite_key]=$totalDict[$composite_key]+1
}
else
{
$totalDict.Add($composite_key,1)
}
}

Compare objects based on subset of properties

Say I have 2 powershell hashtables one big and one small and, for a specific purpose I want to say they are equal if for the keys in the small one, the keys on the big hastable are the same.
Also I don't know the names of the keys in advance. I can use the following function that uses Invoke-Expression but I am looking for nicer solutions, that don't rely on this.
Function Compare-Subset {
Param(
[hashtable] $big,
[hashtable] $small
)
$keys = $small.keys
Foreach($k in $keys) {
$expression = '$val = $big.' + "$k" + ' -eq ' + '$small.' + "$k"
Invoke-Expression $expression
If(-not $val) {return $False}
}
return $True
}
$big = #{name='Jon'; car='Honda'; age='30'}
$small = #{name = 'Jon'; car='Honda'}
Compare-Subset $big $small
A simple $true/$false can easily be gotten. This will return $true if there are no differences:
[string]::IsNullOrWhiteSpace($($small|Select -Expand Keys|Where{$Small[$_] -ne $big[$_]}))
It checks for all keys in $small to see if the value of that key in $small is the same of the value for that key in $big. It will only output any values that are different. It's wrapped in a IsNullOrWhitespace() method from the [String] type, so if any differences are found it returns false. If you want to list differences just remove that method.
This could be the start of something. Not sure what output you are looking for but this will output the differences between the two groups. Using the same sample data that you provided:
$results = Compare-Object ($big.GetEnumerator() | % { $_.Name }) ($small.GetEnumerator() | % { $_.Name })
$results | ForEach-Object{
$key = $_.InputObject
Switch($_.SideIndicator){
"<="{"Only reference object has the key: '$key'"}
"=>"{"Only difference object has the key: '$key'"}
}
}
In primetime you would want something different but just to show you the above would yield the following output:
Only reference object has the key: 'age'