0

I created this function but I don't know how to make it work. here is the code:

function ProgramRegistry {
    param (
        
    [Parameter(Mandatory=$false)][HashTable]$HashTable,
    [Parameter(Mandatory=$false)][String]$AlertPath,
    [Parameter(Mandatory=$false)][String]$AlertName,
    [Parameter(Mandatory=$false)][String]$AlertValue
     )

    

     foreach ($AlertPath in $HashTable.Values){
        foreach($AlertName in $HashTable.Values){
            foreach($AlertValue in $HashTable.Values){
                          
  
    New-Item -Path $AlertPath -Force | Out-Null
    New-ItemProperty -Path $AlertPath -Name $AlertName -Value $AlertValue -PropertyType DWORD -Force
    }


                  
            }
        }
     }
 


$keys = [ordered]@{


    key1 = @{
        AlertPath = 'Path'
        AlertName = 'Name'
        AlertValue = 'Value'

    }

    key2 = @{

        AlertPath = 'Path'
        AlertName = 'Name'
        AlertValue = 'Value'

    }

    # and so on...

}

ModifyRegistry @keys

ModifyRegistry -AlertPath "path" -AlertName "name" -AlertValue "value"

I want to be able to call the function in 2 different ways (as shown in the script)

  1. either by defining its 3 parameters explicitly in one line.
  2. or by passing a nested hash table consisting of multiple objects each having the function's 3 parameters.

how can I achieve that?

I want to only modify the function and not the way I call it. I need to call it a bunch of times and want to keep the code for doing it as minimal as possible, like this ModifyRegistry @keys . it's okay if the function itself is complicated and long but I want calls to function to take very little code like that. instead of nested hash table, I could just call the function repeatedly but it'd be too much repeated code and that's what I want to avoid.

  • 1
    Use [splatting](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting?view=powershell-7.3). Also, the keys of your hashtables should not have `$` variables unless your intention is for the key to be the value of those variables instead. – Daniel Jan 22 '23 at 23:09
  • Thanks, fixed the `$` and I've read that but having hard time figuring out how to do it in a nested hashtable and whether or not I need to use a `foreach` like I used there or there is better ways. currently, when I call my function with hash table, I get the error saying `A parameter cannot be found that matches parameter name 'key1'.` –  Jan 22 '23 at 23:17
  • Should be able to do something like `$keys.Values | ForEach-Object {ProgramRegistry @_}` When you use a ForEach-Object normally the variable to reference the current loop object is `$_`. Replace the $ with @ to splat. `$keys.Values` will enumerate the nested hashtables which will be the objects used inside the ForEach-Object loop. – Daniel Jan 22 '23 at 23:18
  • Can I only modify the function and not the way I call it? I need to call it a bunch of times and want to keep the code for doing it as minimal as possible, like this `ModifyRegistry @keys` . it's okay if the function itself is complicated and long but I want calls to function to take very little code like that. instead of nested hash table, I could just call the function repeatedly but it'd be too much repeated code. –  Jan 22 '23 at 23:23
  • Where is your data? Key1 and key2 both contain the same dummy values. – Walter Mitty Jan 23 '23 at 03:44
  • @WalterMitty they can be any registry key `name,path,value` combination. –  Jan 23 '23 at 08:15
  • I am glad you got an acceptable answer to your question. I am still trying to understand why you want to overload a single function with two calling sequences. Why not just write two functions, one of which repeatedly calls the other? One of the functions would be called with a hashtable of hashtables, while the other would be called by a splatted hashtable. – Walter Mitty Jan 23 '23 at 14:34
  • Thank you, the functions that [Daniel](https://stackoverflow.com/users/11954025/daniel) wrote are perfect for my needs, exactly what I was trying to make which will also help me learn more. the reason I wanted 1 function instead of 2 is because of simplicity and really wanted one function being able to handle different types of inputs. 2 functions doing the same task and only difference being accepting different input types just wasn't what I was looking for. –  Jan 23 '23 at 14:41

1 Answers1

0

You may modify your function to accept a hashtable of hashtables. You just need to provide some logic to check if the hashtable received is a hashtable containing other hashtables that have the values you need or if it is instead a single hashtable containing the values you need. Also needed is to handle the other parameters still when not providing a hashtable. The example below shows how I would do this in an advanced function utilizing a begin, process, and end block. In the begin block we only need to create the collection object that we will use to sift out the inputs. The process block is repeated for each input object received when using the pipeline. If supplying the arguments to the function directly this process block will only run once. We will use this process block to determine and add our input objects to the $inputs arraylist we created. In the end block we will perform the actual processing on each of the objects that we've collected.

function ProgramRegistry {
    [cmdletbinding()]
    param (
        # ValueFromPipeline attribute will allow piping hashtables to the function
        [Parameter(Mandatory = $false, ValueFromPipeline)][HashTable]$HashTable,
        [Parameter(Mandatory = $false)][String]$AlertPath,
        [Parameter(Mandatory = $false)][String]$AlertName,
        [Parameter(Mandatory = $false)][String]$AlertValue
    )

    begin {
        # Create an arraylist to collect hashtables for later processing in end block
        $inputs = [System.Collections.ArrayList]::new()
    }

    process {
        if ($HashTable) {
            if ($HashTable.ContainsKey('AlertPath')) {
                # if single hashtable is received with 'AlertPath' key add to inputs for processing in end block
                $inputs.Add($HashTable) | Out-Null
            }
            else {
                foreach ($value in $HashTable.Values) {
                    # check if value of key is a hashtable
                    if ($value -is [hashtable]) {
                        # check if hashtable contains key "AlertPath" and if so add to $inputs for processing in end block
                        if ($value.ContainsKey('AlertPath')) {
                            $inputs.Add($value) | Out-Null
                        }
                        else {
                            Write-Warning "Invalid hashtable format - missing 'AlertPath' key"
                        }
                    } else {
                        Write-Warning "Object is not a hashtable"
                    }
                }
            }
        }
        else {
            # process when not a hashtable by creating a hashtable and adding to $inputs
            $inputs.Add(@{
                    AlertPath  = $AlertPath
                    AlertName  = $AlertName
                    AlertValue = $AlertValue
                }) | Out-Null
        }
    }

    end {
        # Process hashtables collected in $inputs 
        foreach ($hash in $inputs) {
            # your code here
            [pscustomobject]@{
                Path  = $hash.AlertPath
                Name  = $hash.AlertName
                Value = $hash.AlertValue 
            }
        }
    }
}



$keys = [ordered]@{
    key1 = @{
        AlertPath  = 'Path1'
        AlertName  = 'Name1'
        AlertValue = 'Value1'
    }
    key2 = @{
        AlertPath  = 'Path2'
        AlertName  = 'Name2'
        AlertValue = 'Value2'
    }
    # and so on...
}


ProgramRegistry -HashTable $keys
# or 
$keys | ProgramRegistry
# or even
ProgramRegistry -HashTable $keys.key1 #or $keys.key1 | ProgramRegistry

If pipeline and advanced function is not wanted you can still do something similar without begin, process, and end blocks. I use nested function 'processit' so that I don't have to repeat the processing logic multiple times

function ProgramRegistry {

    param (
        [Parameter(Mandatory = $false)][HashTable]$HashTable,
        [Parameter(Mandatory = $false)][String]$AlertPath,
        [Parameter(Mandatory = $false)][String]$AlertName,
        [Parameter(Mandatory = $false)][String]$AlertValue
    )
    

    # create nested function to process each hash table.  
    function processit {
        param([hashtable]$hash)

        # add processing logic here
        [pscustomobject]@{
            Path  = $hash.AlertPath
            Name  = $hash.AlertName
            Value = $hash.AlertValue
        }
    }

    if ($HashTable) {
        if ($HashTable.ContainsKey('AlertPath')) {
            # if single hashtable is received with 'AlertPath' key process it
            processit -hash $HashTable
        }
        else {
            foreach ($value in $HashTable.Values) {
                # check if value of key is a hashtable
                if ($value -is [hashtable]) {
                    # check if hashtable contains key "AlertPath" and if so process it
                    if ($value.ContainsKey('AlertPath')) {
                        processit -hash $value
                    }
                    else {
                        Write-Warning "Invalid hashtable format - missing 'AlertPath' key"
                    }
                }
                else {
                    Write-Warning 'Object is not a hashtable'
                }
            }
        }
    }
    else {
        processit @{AlertPath = $AlertPath; AlertName = $AlertName; AlertValue = $AlertValue }
    }
}
    

Update in response to your question regarding using the key name as AlertName

function ProgramRegistry {
    param (
        [Parameter(Mandatory = $false)][HashTable]$HashTable,
        [Parameter(Mandatory = $false)][String]$AlertPath,
        [Parameter(Mandatory = $false)][String]$AlertName,
        [Parameter(Mandatory = $false)][String]$AlertValue
    )
    # create nested function to process each hash table.  
    function processit {
        param([hashtable]$hash)
        # add processing logic here
        [pscustomobject]@{
            Path  = $hash.AlertPath
            Name  = $hash.AlertName
            Value = $hash.AlertValue
        }
    }
    if ($HashTable) {
        if ($HashTable.ContainsKey('AlertPath')) {
            # if single hashtable is received with 'AlertPath' key process it
            processit -hash $HashTable
        }
        else {
            foreach ($item in $HashTable.GetEnumerator()) {
                if ($item.Value -is [hashtable]) {
                    # check if the hashtable has AlertPath and AlertValue keys
                    if ($item.Value.ContainsKey('AlertPath') -and $item.Value.ContainsKey('AlertValue')) {
                        $hash = $item.Value
                        # check if hashtable contains an AlertName key.  
                        if (-not $hash.ContainsKey('AlertName')){
                            # If not use parent key name
                            $hash.AlertName = $item.Key
                        }
                        processit -hash $hash
                    }
                    else {
                        Write-Warning "Invalid hashtable format - missing AlertPath and/or AlertValue key"
                    }
                }
                else {
                    Write-Warning "Item does not contain a hashtable"
                }
            }
        }
    }
    else {
        processit @{AlertPath = $AlertPath; AlertName = $AlertName; AlertValue = $AlertValue }
    }
}

Calling the function

$items = @{
    Alert1 = @{
        AlertPath  = 'Path1'
        AlertValue = 'Value1'
    }
    Alert2 = @{
        AlertPath  = 'Path2'
        AlertValue = 'Value2'
    }
    Alert3 = @{
        AlertName = 'Overridden AlertName'
        AlertPath  = 'Path3'
        AlertValue = 'Value3'
    }
    Alert4 = @{
        AlertValue = 'Value2'
    }
    Alert5 = "just a string"
}

ProgramRegistry -HashTable $items

Output

WARNING: Item does not contain a hashtable

WARNING: Invalid hashtable format - missing AlertPath and/or AlertValue key
Path  Name                 Value
----  ----                 -----
Path3 Overridden AlertName Value3
Path2 Alert2               Value2
Path1 Alert1               Value1
Daniel
  • 4,792
  • 2
  • 7
  • 20
  • Thank you very much, I'm curious though, would it be possible to use the nested hashtable names like `key1` or `key2` as the `AlertName` so that I will only use 2 parameters instead of 3 inside each nested hash table? like how the 1st function would change? I assume i'd have to make the `key1` and `key2` variables by adding `$` to them ? if it's more suitable for a different question, please do let me know. –  Jan 23 '23 at 08:42
  • 1
    Yes, this is possible. Please see the update. – Daniel Jan 23 '23 at 13:17