Variable in scriptblock not accessible every second try

Product: PowerShell Universal
Version: 4.3.3

I have a script with an Textbox, a Button and a table.
When I press the button, some data gets retrieved and the table populated.
This works fine.

However when I press the button again, the same variable is empty and I get an error.
When I press the button again, all is fine.
Next click: error. And so on.

I can’t share the original script, so I have created something to show the problem.

Edit: updated the script and updated psu-server to 4.3.3



$session:out=@{
    one=""
    two=""
    onedata=@()
    twodata=@()
    onetest="onetest"
    twotest="twotest"
}


$sb1= {
    param ($in)
    Show-UDToast -Message ("sb1 "+($session:out.onetest | convertto-json -Depth 3)) -BackgroundColor "lawngreen"
    $session:out.one=Invoke-Command -ScriptBlock {
        param($p1)
        (ls  )
    } -ArgumentList $in -ErrorAction Ignore
}
$sb2= {
    param ($in)
    Show-UDToast -Message ("sb2 "+($session:out.onetest | convertto-json -Depth 3)) -BackgroundColor "tomato"
    $session:out.two=Invoke-Command -ScriptBlock {
        param($p1)
        (ls )
    } -ArgumentList $in -ErrorAction Ignore
}


New-UDGrid -Container -Content {
    New-UDGrid -Item -ExtraSmallSize 4 -Content {
        New-UDTextbox -id "in" -Label "whatever" -FullWidth -value "whatever" 
    }
    New-UDGrid -Item -ExtraSmallSize 8 -Content {

    }

    New-UDGrid -Item -ExtraSmallSize 6 -Content {
        New-UDButton -id "do" -Text "do" -OnClick {
            Show-UDToast -Message "click"
            $in=(Get-UDElement -id "in").value
            & $sb1 -in $in
            & $sb2 -in $in

            $list=$session:out.one|Select-Object *
            $data=@()
            foreach ($item in $list) {
                foreach ($name in $item.psobject.Properties.Name) {
                    $data+=@{
                        name=$name
                        value=$item.$name
                    }
                }
            }
            $session:out.onedata=$data
            Sync-UDElement -id "tableone" -wait

            $list=$session:out.two|Select-Object * 
            $data=@()
            foreach ($item in $list) {
                foreach ($name in $item.psobject.Properties.Name) {
                    $data+=@{
                        name=$name
                        value=$item.$name
                    }
                }
            }
            $session:out.twodata=$data
            Sync-UDElement -id "tabletwo" -Wait
        }
    }
    New-UDGrid -Item -ExtraSmallSize 6 -Content {}

    $columns=@(
        New-UDTableColumn -Property Name 
        New-UDTableColumn -Property Value
    )
    $session:out.onedata=0..0xf|%{@{ name=""; value=""}} 
    $session:out.twodata=0..0xf|%{@{ name=""; value=""}} 

    New-UDGrid -Item -ExtraSmallSize 6 -Content {
        New-UDElement -Tag div -Attributes @{style=@{border="8px solid lawngreen";}} -Content {
            New-UDTable -id "tableone" -Dense -columns $columns -loaddata {
                $tdc=$session:out.onedata.count
                $session:out.onedata | Out-UDTableData -Page $EventData.Page -TotalCount $tdc -Properties $EventData.Properties
            }
        }
    }
    New-UDGrid -Item -ExtraSmallSize 6 -Content {
        New-UDElement -Tag div -Attributes @{style=@{border="8px solid tomato";}} -Content {
            New-UDTable -id "tabletwo" -Dense -columns $columns -loaddata {
                $tdc=$session:out.twodata.count
                $session:out.twodata | Out-UDTableData -Page $EventData.Page -TotalCount $tdc -Properties $EventData.Properties
            }
        }
    }
}

You say “some variable”… Is that to imply that it’s randomly selecting which variable is going to be empty? Or, is it the same variable each time that is empty on every other click?

sorry, the second “some” should have been “the same”.
When you look at the code, I define a $session:out variable.

One 1st click the variable is accessible, contains data and can be updated.
One 2nd click the variable is empty.
One 3rd click the variable is usable again.
Next, empty and so on.

The script shows exactly that behavior.

1 Like

Interesting. I used your script and can recreate the issue. It behaves like some sort of race condition, if I’m just taking a guess.

1 Like

I have that issue in more then one script.
That is because I found a way (just started with PSU) to create somewhat dynamic output from either psremote session calls or API calls using scriptblocks (instead of functions).
I was quite happy with the outcome, pretty simple web GUI for stuff I already have.
And then … this.
I tried the -wait option on the sync-udelement.
I added sleep commands…
I tried page and session variables.
I kept data amount in those variables low. (anything bigger I store in a file)
I already thought of keeping my variables in a json file…
But I think this should work anyway.

Ok, I found:

  • don’t use udtoast in a scriptblock
  • don’t use scope variables in a scriptblock, rather return a value
  • use -showloading with buttons to prevent another click before all previous action finished

This example works


$session:onedata=$null
$session:twodata=$null

$sb1= {
    param ($in)
    $one=Invoke-Command -ScriptBlock {
        param($p1)
        (ls -File )
    } -ArgumentList $in -ErrorAction Ignore
    return $one
}
$sb2= {
    param ($in)
    $two=Invoke-Command -ScriptBlock {
        param($p1)
        (ls )
    } -ArgumentList $in -ErrorAction Ignore
    return $two
}


New-UDGrid -Container -Content {
    New-UDGrid -Item -ExtraSmallSize 4 -Content {
        New-UDTextbox -id "in" -Label "whatever" -FullWidth -value "whatever" 
    }
    New-UDGrid -Item -ExtraSmallSize 8 -Content {

    }

    New-UDGrid -Item -ExtraSmallSize 6 -Content {
        New-UDButton -id "do" -Text "do" -ShowLoading -OnClick {
            $in=(Get-UDElement -id "in").value
            $one=& $sb1 -in $in
            $two=& $sb2 -in $in

            $list=$one|Select-Object *
            $data=@()
            foreach ($item in $list) {
                foreach ($name in $item.psobject.Properties.Name) {
                    $data+=@{
                        name=$name
                        value=$item.$name
                    }
                }
            }
            $session:onedata=$data
            Sync-UDElement -id "tableone" -wait

            $list=$two|Select-Object * 
            $data=@()
            foreach ($item in $list) {
                foreach ($name in $item.psobject.Properties.Name) {
                    $data+=@{
                        name=$name
                        value=$item.$name
                    }
                }
            }
            $session:twodata=$data
            Sync-UDElement -id "tabletwo" -Wait
        }
    }
    New-UDGrid -Item -ExtraSmallSize 6 -Content {}

    $columns=@(
        New-UDTableColumn -Property Name 
        New-UDTableColumn -Property Value
    )
    $session:onedata=0..0xf|%{@{ name=""; value=""}} 
    $session:twodata=0..0xf|%{@{ name=""; value=""}} 

    New-UDGrid -Item -ExtraSmallSize 6 -Content {
        New-UDElement -Tag div -Attributes @{style=@{border="8px solid lawngreen";}} -Content {
            New-UDTable -id "tableone" -Dense -columns $columns -loaddata {
                $tdc=$session:onedata.count
                $session:onedata | Out-UDTableData -Page $EventData.Page -TotalCount $tdc -Properties $EventData.Properties
            }
        }
    }
    New-UDGrid -Item -ExtraSmallSize 6 -Content {
        New-UDElement -Tag div -Attributes @{style=@{border="8px solid tomato";}} -Content {
            New-UDTable -id "tabletwo" -Dense -columns $columns -loaddata {
                $tdc=$session:twodata.count
                $session:twodata | Out-UDTableData -Page $EventData.Page -TotalCount $tdc -Properties $EventData.Properties
            }
        }
    }
}

1 Like

Nice. So, is this the solution you’re going with for the real script?

No. It just made this example work.
My other scripts still show the old behavior, even after several changes.
They are more complex and do more things…But the issue is similar, every second round some variables are not accessible.
Right now I am working on one where I can’t even set variable in a scriptblock.

Ok, progress.

On the first run, scriptblocks “know” script variables.
On the second run, initiated by button click, they don’t and there will be an error.
On the third try, behavior is like on first run, and so on.
So never expect any variable, any scope to “just” be in a scriptblock. (well, seems cache scope works…)

Show-udtoast and set-udelement clearly are no-no in script blocks - if you want to call them more then one time.

You need to capture a new closure if you are using ScriptBlocks like this.

$sb1c = $sb1.GetNewClosure()
$sb2c = $sb2.GetNewClosure()

$one=& $sb1c -in $in
$two=& $sb2c -in $in

The reason is that the script block captures the closure of the location you are defining it in. The problem is that we reconstruct script blocks in apps like this, so the closure is no longer valid and probably in some weird state. No idea why it works once but using GetNewClosure() captures the current state and then should work.

4 Likes

Wonderful, thank you.
This makes it all so much easier.
Would have never thought of that.

2 Likes