3

$syncHash button event with a separate runspace

Not sure if this is a duplicate, checked online, and worked with what I found, Working with Boe Prox's solutions, which from another StackOverflow article references (https://stackoverflow.com/a/15502286/1546559), but in his, he is updating from a command line/powershell window, via a function run inside a thread. I'm running an event from a button, inside of a thread and trying to run a separate thread, for the click event(s). Outside of the thread, the event works fine, but inside, it doesn't work, at all.. What am I doing wrong? PS. I found another blog referencing Boe Prox's work (https://www.foxdeploy.com/blog/part-v-powershell-guis-responsive-apps-with-progress-bars.html), building another multi-threaded application, but pretty much the same concept, updating a window, through powershell commandlet/function, placed inside of a separate thread.

$push.Add_Click{
    $newRunspace =[runspacefactory]::CreateRunspace()
    $newRunspace.ApartmentState = "STA"
    $newRunspace.ThreadOptions = "ReuseThread"         
    $newRunspace.Open()
    $newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
    $powershell = [powershell]::Create().AddScript({           
        $choice = $comboBox.SelectedItem
        # $drive = Get-Location
        if(!(Test-Path -PathType Container -Path "L:\$choice"))
        {
    #        New-Item -ItemType "container" -Path . -Name $choice
            New-Item -ItemType "Directory" -Path . -Name $choice
        }

    #        $folder = $_
            # Where is it being stored at?
            [System.IO.File]::ReadLines("Y:\$choice\IPs.txt") | foreach {
                ping -a -n 2 -w 2000 $_ | Out-Null
                Test-Connection -Count 2 -TimeToLive 2 $_ | Out-Null

                if($?)
                {
                   RoboCopy /Log:"L:\$folder\$_.log" $source \\$_\c$\tools
                   RoboCopy /Log+:"L:\$folder\$folder-MovementLogs.log" $source \\$_\c$\tools
                   Start-Process "P:\psexec.exe" -ArgumentList "\\$_ -d -e -h -s cmd /c reg import C:\tools\dump.reg"
                   # Copy-Item -LiteralPath Y:\* -Destination \\$_\c$\tools
                   $listBox.Items.Add($_)
                }
            }
    })
    $powershell.Runspace = $newRunspace
    $powershell.BeginInvoke()

}

user1546559
  • 49
  • 10
  • 1
    there isn't enough information on your question but at first glance, 1. your runspace has no idea what `$comboBox.SelectedItem` is, and 2. you're initializing your runspace with a synchronized hashtable but making no use of it. – Santiago Squarzon Jun 23 '22 at 03:40
  • What do you mean, "not making use of it"? (Like I said, I'm following blogs').. 1) it knows comboBox.selectedItem, it's just a variable up out(side) of my button event. I chose not to show, as it is not an issue, that I am having, at the moment. :) – user1546559 Jun 23 '22 at 03:42
  • 1
    `$syncHash` is not used anywhere in your script block – Santiago Squarzon Jun 23 '22 at 03:43
  • It's used in the runspace i.e. $newRunspace.SessionStateProx.setVariable("syncHash", $syncHash), so I'm presuming, what you are seeing, from my provided code ( from a blog), is that it is being re-used, from atop. – user1546559 Jun 23 '22 at 03:48
  • So the blog does have a button event, but like I said inside of it's separate runspace, in the button event, he updates the window from (the) outside, in a cosole, that utilizes a commandlet/function to operate, I'm needing to display information, to the "screen"/window, from within the window, as I "push" the button, I need to run an action, (without freezing the window), so that the window can be updated as the events are ran, from the button, event. – user1546559 Jun 23 '22 at 03:55
  • $newRunspace =[runspacefactory]::CreateRunspace() $newRunspace.ApartmentState = "STA" $newRunspace.ThreadOptions = "ReuseThread" $newRunspace.Open() $newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash) So the same code is re-used, up at the top, (Above my code, in my program) – user1546559 Jun 23 '22 at 03:57

1 Answers1

4

You can use this as a blueprint of what you want to do, the important thing is that the runspace can't see the controls of your form. If you want your runspace to interact with the controls, they must be passed to it, either by SessionStateProxy.SetVariable(...) or as argument with .AddParameters(..) for example.

using namespace System.Windows.Forms
using namespace System.Drawing
using namespace System.Management.Automation.Runspaces

Add-Type -AssemblyName System.Windows.Forms

[Application]::EnableVisualStyles()

try {
    $form = [Form]@{
        StartPosition   = 'CenterScreen'
        Text            = 'Test'
        WindowState     = 'Normal'
        MaximizeBox     = $false
        ClientSize      = [Size]::new(200, 380)
        FormBorderStyle = 'Fixed3d'
    }

    $listBox = [ListBox]@{
        Name       = 'myListBox'
        Location   = [Point]::new(10, 10)
        ClientSize = [Size]::new(180, 300)
    }
    $form.Controls.Add($listBox)

    $runBtn = [Button]@{
        Location   = [Point]::new(10, $listBox.ClientSize.Height + 30)
        ClientSize = [Size]::new(90, 35)
        Text       = 'Click Me'
    }
    $runBtn.Add_Click({
        $resetBtn.Enabled = $true

        if($status['AsyncResult'].IsCompleted -eq $false) {
            # we assume it's running
            $status['Instance'].Stop()
            $this.Text = 'Continue!'
            return # end the event here
        }

        $this.Text = 'Stop!'
        $status['Instance']    = $instance
        $status['AsyncResult'] = $instance.BeginInvoke()
    })
    $form.Controls.Add($runBtn)

    $resetBtn =  [Button]@{
        Location   = [Point]::new($runBtn.ClientSize.Width + 15, $listBox.ClientSize.Height + 30)
        ClientSize = [Size]::new(90, 35)
        Text       = 'Reset'
        Enabled    = $false
    }
    $resetBtn.Add_Click({
        if($status['AsyncResult'].IsCompleted -eq $false) {
            $status['Instance'].Stop()
        }
        $runBtn.Text  = 'Start!'
        $this.Enabled = $false
        $listBox.Items.Clear()
    })
    $form.Controls.Add($resetBtn)

    $status = @{}
    $rs = [runspacefactory]::CreateRunspace([initialsessionstate]::CreateDefault2())
    $rs.ApartmentState = [Threading.ApartmentState]::STA
    $rs.ThreadOptions  = [PSThreadOptions]::ReuseThread
    $rs.Open()
    $rs.SessionStateProxy.SetVariable('controls', $form.Controls)
    $instance = [powershell]::Create().AddScript({
        $listBox = $controls.Find('myListBox', $false)[0]
        $ran  = [random]::new()

        while($true) {
            Start-Sleep 1
            $listBox.Items.Add($ran.Next())
        }
    })
    $instance.Runspace = $rs
    $form.Add_Shown({ $this.Activate() })
    $form.ShowDialog()
}
finally {
    ($form, $instance, $rs).ForEach('Dispose')
}

Demo

demo

Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37
  • 1
    THANK YOU!! THAT LOOKS AWESOME!! I'll try it, as soon as I get to work. – user1546559 Jun 23 '22 at 19:35
  • 1
    @user1546559 hopefully it gives you a hint where to start :) – Santiago Squarzon Jun 23 '22 at 19:39
  • 1
    You mentioned my comboBox, (Which I just realized, you meant the runspace.. Lol.), but anyway, I was curious, what is your $instace variable? You begin with the invoke in the push (click) button event, but it isn't initialized, until after the push (click) button event. – user1546559 Jun 24 '22 at 01:27
  • 1
    @user1546559 remember that powershell loads all the code in the script before executing it. `$instance` already exists when the form pops (even if the code for initializing it, is below the button event handler (also remember that everything inside the event handler happens on a different scope // is only triggered on click in this case) – Santiago Squarzon Jun 24 '22 at 01:38
  • 2
    Not bad. I'd be really impressed if you incorporated a cancellation mechanism. :) – Doug Maurer Jun 24 '22 at 17:37
  • 2
    Thanks @Doug I think I could, will @ you again if I do post an update later. Would you add the event in a separate button or same button? – Santiago Squarzon Jun 24 '22 at 17:43
  • 2
    @SantiagoSquarzon I have no clue! You are way beyond me here. – Doug Maurer Jun 24 '22 at 19:29
  • 1
    @Doug being humble huh :) – Santiago Squarzon Jun 24 '22 at 19:31
  • Would you just SessionState.SetVariable() another button, to add to the current runspace, to exit the aapplication? – user1546559 Jun 24 '22 at 22:40
  • Lol. Looks like I got beat to it. Lol. – user1546559 Jun 24 '22 at 22:41
  • Ah, the questions of a programmer, that I ask myself, all the time. Lol. Single button: you could just use an if then else, Else.. Lol. I'm using a separate button. :) (Can I offer bonus points, on here.. Lol). – user1546559 Jun 24 '22 at 22:44
  • Sorry, I feel like I'm terrible at this.. (Trying to improve.. :)) does the form need to be in a separate thread, on it's own? – user1546559 Jun 24 '22 at 22:50
  • Okay. Update: So, it seems to be something I guess, with my code. Lol. I copied your template, paste in my event code.. nothing..same as before.. I did the button disable/enable, like you did, and it definitely does that, and.. that's about it.. Lol. – user1546559 Jun 25 '22 at 02:56
  • 1
    @DougMaurer there you go :) – Santiago Squarzon Jun 25 '22 at 13:44
  • 1
    @user1546559 see the update, that's as far as I can go. If you consider it was helpful please consider accepting the answer – Santiago Squarzon Jun 25 '22 at 13:50
  • 1
    Amazing code. :) I think I found out what my problem is/was. Thank you! For all your help. – user1546559 Jun 27 '22 at 12:48