最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Powershell hashmap: nested filtering - Stack Overflow

programmeradmin0浏览0评论

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
  • 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 – Theo 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 – CaptainObv 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 – iRon Commented Feb 2 at 16:50
Add a comment  | 

2 Answers 2

Reset to default 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}

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], ...
发布评论

评论列表(0)

  1. 暂无评论