I’m trying create some automations to help with general activities on an environment and was trying to think of a clean accessible way to store and access info via powershell hashmaps
$test=@{
"server1"=@{
"service1"=@{
"filePath"="C:\hi";
"serviceName"="servName1";
"processName"="procName1" ;
"group"=@("ALL", "SUBSET" );
"properties1"=@{"prop1"="prop2";};
"properties2"=@{"prop1"="prop2";};
};
"service2"=@{
"filePath"="C:\hi2";
"serviceName"="servName2";
"processName"="procName2" ;
"group"=@("ALL");
"properties1"=@{"prop1"="prop2";};
"properties2"=@{"prop1"="prop2";};
};
};
"server2"=@{
"service3"=@{
"filePath"="C:\hi3";
"serviceName"="servName3";
"processName"="procName3" ;
"group"=@("ALL", "SUBSET" )
"properties1"=@{"prop1"="prop2";};
"properties2"=@{"prop1"="prop2";};
};
};
};
Above lets me get list of servers, services on a specific server, etc easily but more importantly it gives an easy way to return a filter-down hashmap to get all info e.g. on a particular server etc
write-host "list of servers:";
$test.keys;
write-host "list of services on server1:";
$test['server1'].keys;
write-host "hashmap of services on server1:";
$test['server1'];
list of servers:
server2
server1
list of services on server1:
service2
service1
hashmap of services on server1:
Name Value
---- -----
service2 {serviceName, processName, properties1, group…}
service1 {serviceName, processName, properties1, group…}
hashmap of SUBSET services:
server2 {service3}
server1 {service1}
I’m a little unsure if there is an elegant way to do more tailored filters that are nested much deeper e.g get the hashmap filtered down to only services that have SUBSET in the group name etc
write-host "hashmap of SUBSET services:";
#expected result
@{
"server1"=@{
"service1"=@{
"filePath"="C:\hi";
"serviceName"="servName1";
"processName"="procName1" ;
"group"=@("ALL", "SUBSET" );
"properties1"=@{"prop1"="prop2";};
"properties2"=@{"prop1"="prop2";};
};
};
"server2"=@{
"service3"=@{
"filePath"="C:\hi3";
"serviceName"="servName3";
"processName"="procName3" ;
"group"=@("ALL", "SUBSET" )
"properties1"=@{"prop1"="prop2";};
"properties2"=@{"prop1"="prop2";};
};
};
};
Only way I can think of is just a huge clunky series of nested for loops which might make it difficult to reuse if I want to do other filters like get hashmap of everything that has a specific serviceName.
Does anyone have any idea? If I can easily access $test['server1'] without a first level for loop, is there a way I can do the same thing for nested fields e.g.
$test(*)(*)[“group”].contains(“SUBSET”)
I’m trying create some automations to help with general activities on an environment and was trying to think of a clean accessible way to store and access info via powershell hashmaps
$test=@{
"server1"=@{
"service1"=@{
"filePath"="C:\hi";
"serviceName"="servName1";
"processName"="procName1" ;
"group"=@("ALL", "SUBSET" );
"properties1"=@{"prop1"="prop2";};
"properties2"=@{"prop1"="prop2";};
};
"service2"=@{
"filePath"="C:\hi2";
"serviceName"="servName2";
"processName"="procName2" ;
"group"=@("ALL");
"properties1"=@{"prop1"="prop2";};
"properties2"=@{"prop1"="prop2";};
};
};
"server2"=@{
"service3"=@{
"filePath"="C:\hi3";
"serviceName"="servName3";
"processName"="procName3" ;
"group"=@("ALL", "SUBSET" )
"properties1"=@{"prop1"="prop2";};
"properties2"=@{"prop1"="prop2";};
};
};
};
Above lets me get list of servers, services on a specific server, etc easily but more importantly it gives an easy way to return a filter-down hashmap to get all info e.g. on a particular server etc
write-host "list of servers:";
$test.keys;
write-host "list of services on server1:";
$test['server1'].keys;
write-host "hashmap of services on server1:";
$test['server1'];
list of servers:
server2
server1
list of services on server1:
service2
service1
hashmap of services on server1:
Name Value
---- -----
service2 {serviceName, processName, properties1, group…}
service1 {serviceName, processName, properties1, group…}
hashmap of SUBSET services:
server2 {service3}
server1 {service1}
I’m a little unsure if there is an elegant way to do more tailored filters that are nested much deeper e.g get the hashmap filtered down to only services that have SUBSET in the group name etc
write-host "hashmap of SUBSET services:";
#expected result
@{
"server1"=@{
"service1"=@{
"filePath"="C:\hi";
"serviceName"="servName1";
"processName"="procName1" ;
"group"=@("ALL", "SUBSET" );
"properties1"=@{"prop1"="prop2";};
"properties2"=@{"prop1"="prop2";};
};
};
"server2"=@{
"service3"=@{
"filePath"="C:\hi3";
"serviceName"="servName3";
"processName"="procName3" ;
"group"=@("ALL", "SUBSET" )
"properties1"=@{"prop1"="prop2";};
"properties2"=@{"prop1"="prop2";};
};
};
};
Only way I can think of is just a huge clunky series of nested for loops which might make it difficult to reuse if I want to do other filters like get hashmap of everything that has a specific serviceName.
Does anyone have any idea? If I can easily access $test['server1'] without a first level for loop, is there a way I can do the same thing for nested fields e.g.
$test(*)(*)[“group”].contains(“SUBSET”)
Share
Improve this question
edited Feb 2 at 16:50
iRon
23.9k10 gold badges58 silver badges98 bronze badges
asked Feb 2 at 3:21
CaptainObvCaptainObv
4221 gold badge6 silver badges15 bronze badges
3
|
2 Answers
Reset to default 0Thanks for the extra info in your comment.
If you want a filtered result where only services are listed that have keyword 'SUBSET' as one of the values in their 'group' item array, you could do like below:
$filtered = @{} # a new Hashtable object to store the results
foreach ($server in $test.Keys) { # loop over the items in the original Hash
foreach ($service in $test.$server.Keys) {
if ($test.$server.$service.group -contains 'SUBSET') {
$filtered.Add($server,@{$service = $test.$server.$service})
}
}
}
Now, $filtered
contains only Hashtables of servers and their services containing 'SUBSET' in the group item:
Name Value
---- -----
server1 {service1}
server2 {service3}
It's not exactly what you're looking for, but you can extend the Hashtable
type, adding a ScriptMethod
that can help you find nodes in your nested hashtable based on a delegate:
Update-TypeData -TypeName Hashtable -MemberType ScriptMethod -MemberName Find -Value {
param([System.Func[object, object, bool]] $Delegate)
$stack = [System.Collections.Stack]::new()
$stack.Push([pscustomobject]@{ Node = $this })
while ($stack.Count) {
$dict = $stack.Pop()
foreach ($pair in $dict.Node.GetEnumerator()) {
$node = [pscustomobject]@{
Path = $dict.Path + "[$($pair.Key)]"
Node = $pair.Value
Parent = $dict
}
if ($pair.Value -is [System.Collections.IDictionary]) {
$stack.Push($node)
continue
}
try {
if ($Delegate.Invoke($pair.Key, $pair.Value)) {
$node
}
}
catch { }
}
}
}
The usage would be:
$test.Find({param($key, $value) $value.Contains('SUBSET') })
# Path Node Parent
# ---- ---- ------
# [server1][service1][group] {ALL, SUBSET} @{Path=[server1][service1]; Node=System.Co...
# [server2][service3][group] {ALL, SUBSET} @{Path=[server2][service3]; Node=System.Co...
$test.Find({param($key, $value) $value -eq 'C:\hi3' })
# Path Node Parent
# ---- ---- ------
# [server2][service3][filePath] C:\hi3 @{Path=[server2][service3]; Node=System.Collec...
$test.Find({param($key, $value) $key -eq 'serviceName' -and $value -eq 'servName2' })
# Path Node Parent
# ---- ---- ------
# [server1][service2][serviceName] servName2 @{Path=[server1][service2]; Node=System....
Then using the .Parent
property you can navigate up in the tree, for example:
$result = $test.Find({param($key, $value) $value.Contains('SUBSET') })
$result[0].Parent.Parent.Node
# Name Value
# ---- -----
# service1 {[properties1, System.Collections.Hashtable], [filePath, C:\hi], ...
# service2 {[properties1, System.Collections.Hashtable], [filePath, C:\hi2], ...
$test.Keys | Where-Object { $test.$_.Values.group -contains 'SUBSET' }
? P.S. This is PowerShell, you don't need all those semi-colons – Theo Commented Feb 2 at 11:35$Test | Get-Node '*.*.group' | Where Value -Contains SUBSET
. As an aside: avoid semicolons as line terminators and avoid enclosing strings with smart quotes – iRon Commented Feb 2 at 16:50