1

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”)
3
  • 1
    You mean something like this: $test.Keys | Where-Object { $test.$_.Values.group -contains 'SUBSET' } ? P.S. This is PowerShell, you don't need all those semi-colons Commented Feb 2 at 11:35
  • @Theo was looking more to return a filtered hash map (where service2 in server1 is removed. Fair enough with the semi colons :p Commented Feb 2 at 11:58
  • Its an external solution, but you might have a look at: ObjectGraphTools: $Test | Get-Node '*.*.group' | Where Value -Contains SUBSET. As an aside: avoid semicolons as line terminators and avoid enclosing strings with smart quotes Commented Feb 2 at 16:50

2 Answers 2

0

Thanks 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}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! I presume it isn’t possible to have anything like that one liner example I gave since it’s multi level. Your two-loop solution is still much simpler than the clunky idea I had and it’s also still intuitive to look at
0

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], ...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.