Convert hashtable back to string data in efficient way - powershell

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

Related

Casting Object to String Array Powershell

I want to create an array of strings instead of a variable object so that I can use the "contains" keyword on each index of the array.
$myArray = Get-ADDomain
The above creates an object, which is not what I want. I also tried
[string[]] $myArray = Get-ADDomain
But after that, $myArray only contains one string and it is the first non-empty property of Get-ADDomain, in my case "ComputersContainer". What should I do to receive an array of strings where each string is a different property, such as
$myArray[0] = "AllowedDNSSuffixes = {}"
PowerShell will always return objects by design of course, and specifying that [string[]], does not really change that.
For what you are trying to use, you have to force the array creation. The below is just one way, but I am sure others will have more elegant ways of doing this as well. Though I am curious why one would want to do this, this way. But, hey, that's just me.
# Create an empty array
$DomainData = #()
# Get all the data points for the utilized cmdlet, split on a common delimiter for the array
[string[]]$DomainData = (Get-ADDomain | Select *) -split ';'
# Display the array count
$DomainData.Count
34
# validate getting a value from the array by using an index number
$Item = $DomainData[17]
NetBIOSName=CONTOSO
[array]::IndexOf($DomainData, $Item)
17
# Use that element number to validate the use of the contains comparison operator
0..($DomainData.Count - 1) | %{ If($DomainData[$_] -contains $item){"Index key is $_ contains a value of $Item"} }
Index key is 17 contains a value of NetBIOSName=CONTOSO
# Use the previous with a partial string for a comparison, -contains cannot be used, like or match has to be used
# From the documentation:
# -Contains
# Description: Containment operator. Tells whether a collection of reference values includes a single test value.
$Item = '*domain*'
0..($DomainData.Count - 1) | %{ If($DomainData[$_] -like $item){"Index key is $_ like a value of $Item"} }
Index key is 1 like a value of *domain*
Index key is 6 like a value of *domain*
Index key is 7 like a value of *domain*
Index key is 8 like a value of *domain*
Index key is 18 like a value of *domain*
Index key is 20 like a value of *domain*
You cannot cast a PSObject directly to a string array like that.
However, this can be accomplished rather easily.
To get an array of string from the object
$myArray = Get-ADDomain
# You can use a standard array #() but these tends to be slower for bigger amount of data
$outArray = New-Object -TypeName System.Collections.Generic.List[String]
#To add just the value
$myArray.psobject.properties | Foreach { $outArray.Add($_.Value) }
# To add Name = {Value} instead
$myArray.psobject.properties | Foreach { $outArray.Add("$($_.Name) = {$($_.Value)}") }
Using an hasthable instead:
$myArray = Get-ADDomain
$hashtable = #{}
$myArray.psobject.properties | Foreach { $hashtable[$_.Name] = $_.Value }
# If you need to do something with the key
Foreach ($key in $hashtable.Keys) {
$Value = $hashtable[$key]
if ($value -like '*prod*') {
Write-Host $key
}
}

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)
}
}

Get ValueFromRemainingArguments as an hashtable

Using [parameter(ValueFromRemainingArguments=$true)] one can get all the remaining arguments passed to the function into a variable as a list.
How can I get the remaining arguments as a hashtable type, for example for inputs like Function -var1 value1 -var2 value2?
There are multiple ways to achieve this. The following solution supports parameters with:
Simple value (single item)
Array value
Null value (switch)
Script:
function testf {
param(
$name = "Frode",
[parameter(ValueFromRemainingArguments=$true)]
$vars
)
"Name: $name"
"Vars count: $($vars.count)"
"Vars:"
#Convert vars to hashtable
$htvars = #{}
$vars | ForEach-Object {
if($_ -match '^-') {
#New parameter
$lastvar = $_ -replace '^-'
$htvars[$lastvar] = $true
} else {
#Value
$htvars[$lastvar] = $_
}
}
#Return hashtable
$htvars
}
testf -simplepar value1 -arraypar value2,value3 -switchpar
Output:
Name: Frode
Vars count: 5
Vars:
Name Value
---- -----
arraypar {value2, value3}
switchpar
simplepar value1
Edit: Modified default value assigned to Hashtable keys: $htvars[$lastvar] = $true. Using $true as the default accounts for switch parameters and can make the resulting Hastable more "splattable".
Updated per Ansgars comment.
One possibility is to build the hash-table within the function. Here is an example:
function MyFunction
{
[CmdletBinding()]
param([parameter(ValueFromRemainingArguments=$true)] $allparams)
process
{
$myhash = #{}
for ($i = 0; $i -lt $allparams.count; $i+=2)
{
$myhash[($allparams[$i]-replace '^-+')] = $allparams[$i+1]
}
}
end
{
$myhash
}
}
We parse through the params in the parameters $allparams using a for loop, and retrieve the key/value pairs to form the hash table, then in the end block we display it.
MyFunction -var1 10 -var2 30 -var3 hello
Name Value
---- -----
var1 10
var3 hello
var2 30
This is tricky because using [Parameter(ValueFromRemainingArguments=$true)] means this is an advanced function, and $args cannot be used in advanced functions.
But if you want a hashtable of all the specified parameters and their values, you could simply use $PSBoundParameters, like so :
function foo {
[cmdletbinding()]
param(
[Parameter(Position=0)]
$Name,
[Parameter(Position=1,ValueFromRemainingArguments=$true)]
$LastName
)
"PSBoundParameters : "
$PSBoundParameters
}
foo -Name Mike Jordan Jones
This results in the following :
PSBoundParameters :
Key Value
--- -----
Name Mike
LastName {Jordan, Jones}
Is this what you are trying to achieve ?
There are a number of caveats with this method but I just wanted to show that ConvertFrom-StringData could also be considered here.
function Convert-StringToHashTable {
param(
[parameter(ValueFromRemainingArguments=$true)]
[string]$string
)
$string -replace "(^| )-","`r`n" | ForEach-Object{$_ -replace "(\w+) (.*)",'$1=$2'} | ConvertFrom-StringData
}
Convert-StringToHashTable -var1-5 value1 -var2 "value2 or 3"
Output from above would be
Name Value
---- -----
var2 value2 or 3
var1-5 value1
We take the all the remaining arguments as a single string. Then we split the string on - that occur at the beginning of the line or after a space. ( This accounts for dashes in the middle of works like Ansgar mentioned in another answer). Then we convert the first space after the first word into an equal sign. That will make a string of key value pairs that ConvertFrom-StringData expects.
Known Caveats
This will not work if you try to send arrays like in Frode's answer. He can handle those.

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'

How to output multiple hash tables in 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.