1

So I am pulling in an array and then cleaning it to just the unique values I need and then running that though a foreach loop to create

$ZoneArray = Import-Csv -Path "Test.csv" | select Zone
$Zones = @()
foreach ($Zone in $ZoneArray){
        $Zones += $Zone.Zone
}
$Zones = $Zones | select -Unique

foreach ($ZonesTest in $Zones){
            Set-Variable -Name "Zone$_" -Value $ZonesTest -Force
    
            New-UDTab -Text $ZonesTest -Content {
                    New-Variable -Name 'CurrentZone'
                    New-UDmuButton -Id 'Zone1Test' -Text ( + " On") -Variant contained -FullWidth -OnClick {
                            Show-UDToast -Message "Starting" -Duration 1000 -Position center
                            $ZoneFullArray = $FullArray | Where-Object Zone -eq $ZonesTest
                            foreach ($ZoneFullArrayTest in $ZoneFullArray){
                                    Set-UDButtonStateChange -Id 'Zone1Test' -Text $ZoneFullArrayTest.ReceiverIPAddress
                                    Start-Sleep -Milliseconds 1000
                            }
                    }
            }
    }

Using Set-Variable -Name "Zone$_" -Value $ZonesTest -Force I am able to call the correct value while it is running. I have tried using

$Q=1
Set-Variable -Name "Zone$Q" -Value $ZonesTest -Force`
$Q++

But then I don't know how to dynamically call back the correct $Zone$Q for the later button press.

Is there anyway to program a dynamic variable that remembers the state it was made and then I can recall it later without knowing it's name? I know that sounds really confusing.

Ideas?


Edit:

I am banging my head against the wall. I know that the $ZoneFullArray = ... and foreach ($ZoneFullArrayTest in $ZoneFullArray){... is what is breaking it but can't think of another way to write it. The problem I am trying to solve with the second foreach to create a button dynamically based off the CSV table and then pull the value of the IP addresses that match the main row value making $Zones. The issue is that the code within the button is not run until it is pressed so it is hard to write them dynamically.

  • 3
    [Don't use the `-Variable` cmdlets for dynamic variable names!](https://stackoverflow.com/a/68830451/1701026). Besides, [try avoid using the increase assignment operator (`+=`) to create a collection](https://stackoverflow.com/a/60708579/1701026) as it is exponentially expensive. – iRon Jul 17 '22 at 19:00
  • @iRon I agree that correcting a hashtable is a good idea, I guess then my question is how would I loop that in a way to dynamically expanded based on the data set? My above code expands but once it runs through once I have the "New-UDmuButton -OnClick" command that will run anew and not know which value was it's Button made when it was originally made. I could expand my code to be the same set of data just adjusted again and again but I was hoping to make it into a automatically made section based off of the imported CSV. – William Wilkins Jul 17 '22 at 19:29
  • You can enumerate each key like: `foreach ($Key in $Hashtable.keys) { ...` and get each related value using:`$Hashtable[$Key]`. – iRon Jul 17 '22 at 19:40
  • @iRon Let me give more context. I am pulling in a CSV with 9 Columns. Then using the 2nd column to make a section but only once per unique row value. Then using that unique row value as a key to find all the rows that have that value in the 2nd column. The issue is that it runs through the `foreach ($Key in $Hashtable.keys) { ...` creating a section of code that will then run later on command however the $Key would no longer be valued because the foreach would have already run it's course and would only produce the last value for all sections of code. – William Wilkins Jul 17 '22 at 20:05
  • As an aside, to show how `+=` can be avoided for iteratively building an array: Your first block of statements can be simplified to: `$zones = (Import-Csv -Path Test.csv).Zone | Select-Object -Unique` – mklement0 Jul 17 '22 at 20:49
  • 1
    @mklement0 I agree, I had that in there just to test the outputs. iRon's suggestion of avoiding that has been taken to heart. – William Wilkins Jul 17 '22 at 20:52

2 Answers2

2

As iRon points out, variable indirection (referring to variables indirectly, via dynamically created variable names) is best avoided - see this post.

It sounds like what you're really looking for are script-block closures that lock in then-current variable values in each iteration of a loop, along the following lines:

# Sample input zones.
$zones = 'foo', 'bar', 'baz'

# Create a script block with a closure for each zone.
$scriptBlocks = 
 foreach ($zone in $zones) {
   # .GetNewClosure() locks in the iteration-specific value of $zone
   { "This zone: $zone" }.GetNewClosure()
 } 

# Execute the script blocks to demonstrate that the values where
# captured as intended.
$scriptBlocks | ForEach-Object { & $_ }

Output:

This zone: foo
This zone: bar
This zone: baz
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • I am having trouble placing the .GetNewClosure() in my code to get the desired results. Suggestions? In the `foreach ($ZonesTest in $Zones){` section. I tried placing it at end of `New-UDTab -Text $ZonesTest -Content { ... }` but that still causes `$ZoneFullArrayTest.ReceiverIPAddress` to output the last value instead of the held one. – William Wilkins Jul 17 '22 at 21:30
  • it is in the nested foreach section. `foreach ($ZoneFullArrayTest in $ZoneFullArray){` `Set-UDButtonStateChange -Id 'Zone1Test' -Text $ZoneFullArrayTest.ReceiverIPAddress` `Start-Sleep -Milliseconds 1000}` I might need to make nested scriptblocks? – William Wilkins Jul 17 '22 at 21:38
  • `$FullArray` is the Full/cleaned Array that I imported from the CSV so it's value is okay to stay the same. I think the code `$ZoneFullArray = $FullArray | Where-Object Zone -eq $ZonesTest` is what is breaking down on the runs. Because when I need to pull the value later with `$ZoneFullArrayTest` it is only giving the last run instead of the first. – William Wilkins Jul 17 '22 at 21:44
  • @WilliamWilkins, scratch that: It is `$FullArray` is nowhere to be found. There is a lot of incidental code that obscures what the real problem is. – mklement0 Jul 17 '22 at 21:44
  • I agree, the code needs to be cleaned up to make it more readable. – William Wilkins Jul 17 '22 at 21:46
  • Again thank you for all your help. https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.scriptblock?view=powershellsdk-7.0.0 are very new to me. (and I guess to powershell as well) – William Wilkins Jul 17 '22 at 21:56
  • @WilliamWilkins, happy to help. Actually, I think, script blocks have been there from the beginning, but creating [_closures_](https://learn.microsoft.com/en-US/dotnet/api/System.Management.Automation.ScriptBlock.GetNewClosure) is an advanced use case that doesn't arise frequently. – mklement0 Jul 17 '22 at 22:05
  • I am banging my head against the wall. I know that the `$ZoneFullArray = ...` and `foreach ($ZoneFullArrayTest in $ZoneFullArray){...` is what is breaking it but can't think of another way to write it. The problem I am trying to solve with the second foreach to create a button dynamically based off the CSV table and then pull the value of the IP addresses that match the main row value making $Zones. The issue is that the code within the button is not run until it is pressed so it is hard to write them dynamically. – William Wilkins Jul 17 '22 at 22:12
  • @WilliamWilkins, without knowing how `$FullArray` is populated, it's hard to know what the problem is, but the fact that you're using _nested_ script blocks shouldn't be a problem if you apply `.GetNewClosure()` to the _outer_ script block. Note that ` New-Variable -Name 'CurrentZone'` in your code serves no obvious purpose, and neither does `Set-Variable -Name "Zone$_" -Value $ZonesTest -Force`, given that `"Zone$_"` evaluates to just `Zone` in _every_ iteration, given that no _pipeline_ is being employed, where the automatic `$_` variable would have a meaningful value. – mklement0 Jul 17 '22 at 23:10
  • Thanks again, I used the scriptBlocks but found the Param option to pass variables into it on run. – William Wilkins Jul 19 '22 at 17:57
  • 1
    @WilliamWilkins, using parameters is a good solution (+1), though you couldn't use this technique to pass script blocks as _arguments_ to commands, as shown in the question. As an aside: I suggest placing a space between `&`, the call operator, and its operand (the command to call); this helps readability in general, but is also better for symmetry with the related dot-sourcing operator, `. `, which _requires_ a space. – mklement0 Jul 19 '22 at 19:20
1

So I ended up using the code blocks @mklement0 suggested but added the param option I found to pass data into the code block.

$CodeBlock = { param($par1, $par2)
foreach ($Row in $par1){
        $SwitchIPCurrent = $Row.SwitchIP
        $SwitchUsernameCurrent = $Row.SwitchUsername
        $SwitchPasswordCurrent = $Row.SwitchPassword
        $SwitchPortCurrent = $Row.SwitchPort
}
}

To run the code just pass the param into the code block

&$CodeBlock -par1 $ImportedArray -par2 $Variable