4

$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()

}

6
  • 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. Commented Jun 23, 2022 at 3: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. :) Commented Jun 23, 2022 at 3:42
  • 1
    $syncHash is not used anywhere in your script block Commented Jun 23, 2022 at 3: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. Commented Jun 23, 2022 at 3: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. Commented Jun 23, 2022 at 3:55

1 Answer 1

6

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'].BeginStop($null, $null)
            $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(-not $status['AsyncResult'].IsCompleted) {
            $async = $status['Instance'].BeginStop($null, $null)
            while (-not $async.AsyncWaitHandle.WaitOne(200)) { }
        }

        $runBtn.Text  = 'Start!'
        $this.Enabled = $false
        $listBox.Items.Clear()
    })
    $form.Controls.Add($resetBtn)

    $status = @{}
    $rs = [runspacefactory]::CreateRunspace([initialsessionstate]::CreateDefault2())
    $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 {
    if ($form) { $form.Dispose() }
    if ($instance) { $instance.Dispose() }
    if ($rs) { $rs.Dispose() }
}

Demo

demo

Sign up to request clarification or add additional context in comments.

16 Comments

THANK YOU!! THAT LOOKS AWESOME!! I'll try it, as soon as I get to work. 😊
@user1546559 hopefully it gives you a hint where to start :)
Not bad. I'd be really impressed if you incorporated a cancellation mechanism. :)
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?
@SantiagoSquarzon I have no clue! You are way beyond me here.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.