I'm working on a function to acess/modify nested hashtables via string input of keys hierarchy like so:
putH() $hashtable "key.key.key...etc." "new value"
Given:
$c = @{
k1 = @{
k1_1 = @{
k1_1_1 = @{ key = "QQQQQ"}
}
}
}
so far i've come up with this function for modifying values:
function putH ($h,$hKEYs,$nVAL){
if ($hKEYs.count -eq 1) {
$bID = $hKEYs #match the last remaining obj in $hkeys
}
else {
$bID = $hKEYs[0] #match the first obj in $hekys
}
foreach ($tk in $h.keys){
if ($tk -eq $bID){
if ($hKEYs.count -eq 1){ #reached the last obj in $hkeys so modify
$h.$tk = $nVAL
break
}
else {
$trash,$hKEYs = $hKEYs #take out the first obj in $hkeys
$h.$tk = putH $h.$tk $hKEYs $nVAL #call the function again for the nested hashtale
break
}
}
}
return $h
}
and this function for getting values :
function getH ($h,$hKEYs){
if ($hKEYs.count -eq 1) {
$bID = $hKEYs
}
else {
$bID = $hKEYs[0]
}
foreach ($tk in $h.keys){
if ($tk -eq $bID){
if ($hKEYs.count -eq 1){
$h = $h.$tk
break
}
else {
$trash,$hKEYs = $hKEYs
$h = getH $h.$tk $hKEYs
break
}
}
}
return $h
}
that i use like so:
$s = "k1.k_1.k1_1_1" #custom future input
$s = $s.split(".")
putH $c ($s) "NEW_QQQQQ"
$getval = getH $c ($s)
My question:
is there a more elegant way to achieve the function's results...say with invoke-expression?
i've tried invoke-expression - but can't access the hassstables trough it (no matter the combinations, nested quotes)
$s = "k1.k_1.k1_1_1" #custom future input
iex "$c.$s"
returns
System.Collections.Hashtable.k1.k_1.k1_1_1
Invoke-Expression
I'll answer your question at the bottom, but I feel obliged to point out that calling Invoke-Expression
here is both dangerous and, more importantly, unnecessary.
You can resolve the whole chain of nested member references by simply splitting the "path" into its individual parts ('A.B.C'
-> @('A', 'B', 'C')
) and then dereferencing them one-by-one (you don't even need recursion for this!):
function Resolve-MemberChain
{
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[psobject[]]$InputObject,
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$MemberPath,
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$Delimiter = '.'
)
begin {
$MemberPath = $MemberPath.Split([string[]]@($Delimiter))
}
process {
foreach($o in $InputObject){
foreach($m in $MemberPath){
$o = $o.$m
}
$o
}
}
}
Now you can solve your problem without iex
:
$ht = @{
A = @{
B = @{
C = "Here's the value!"
}
}
}
$ht |Resolve-MemberChain 'A.B.C' -Delimiter '.'
You can use the same approach to update nested member values - simply stop at the last step and then assign to $parent.$lastMember
:
function Set-NestedMemberValue
{
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[psobject[]]$InputObject,
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$MemberPath,
[Parameter(Mandatory = $true, position = 1)]
$Value,
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[string]$Delimiter = '.'
)
begin {
$MemberPath = $MemberPath.Split([string[]]@($Delimiter))
$leaf = $MemberPath |Select -Last 1
$MemberPath = $MemberPath |select -SkipLast 1
}
process {
foreach($o in $InputObject){
foreach($m in $MemberPath){
$o = $o.$m
}
$o.$leaf = $Value
}
}
}
And in action:
PS ~> $ht.A.B.C
Here's the value!
PS ~> $ht |Set-NestedMemberValue 'A.B.C' 'New Value!'
PS ~> $ht.A.B.C
New Value!
The problem you're facing with your current implementation is that the $c
in $c.$s
gets expanded as soon as the string literal "$c.$s"
is evaluated - to avoid that, simply escape the first $
:
iex "`$c.$s"