locked
add_click Issue with GUI in another runspace RRS feed

  • Question

  • So I'm attempting to create a gui that runs in a separate runspace, and manipulate that GUI via a synchronized hash table. I've found multiple examples of this online and have got my GUI to show. When I have an .add_click control in the runspace that the GUI is NOT in. If I dont attempt to maniupulate the gui within the button, it works fine. As soon as I try to invoke a change to the gui within the button click. The GUI hangs. Below is the code. Note that the textchange invocation outside of the .add_click executes perfectly fine without issue. As soon as I put it within the .add_click and click the button, the GUI becomes unresponsive. Need some help with this one as I dont understand why the command works outside of the button click, but doesn't work within it.

    Here is the code:

    [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null

    $global:syncHash = [hashtable]::Synchronized(@{})

    $newRunspace = [runspacefactory]::CreateRunspace()
    $newRunspace.ApartmentState = "STA"
    $newRunspace.ThreadOptions = "ReuseThread"         
    $newRunspace.Open()
    $newRunspace.SessionStateProxy.SetVariable("syncHash",$global:syncHash)

    $code = {
        $global:syncHash.Error = $error
        [xml]$xaml = Get-Content -path "c:\gui.XAML"
        $global:syncHash.form=[Windows.Markup.XamlReader]::Load((new-object System.Xml.XmlNodeReader $xaml))
        #$global:XAML.SelectNodes("//*[@Name]")| % {$global:syncHash."$($_.Name)" = $global:syncHash.Form.FindName("$($_.Name)")}
        $global:xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | %{ $Global:synchash.Add($_.Name,$synchash.form.FindName($_.Name) ) } 
        $global:syncHash.Form.ShowDialog() | Out-Null
    }

    $psCmd = [PowerShell]::Create().addScript($code)
    $psCmd.Runspace = $newRunspace    
    $data = $psCmd.BeginInvoke()

    start-sleep -Milliseconds 100

    $syncHash.ButtonRun.add_Click({
        $syncHash.form.dispatcher.invoke([action]{$syncHash.TextBoxStatus.Text = "Changed"})
    })

    $syncHash.form.dispatcher.invoke([action]{$syncHash.TextBoxStatus.Text = "Outside"})

    Tuesday, September 19, 2017 6:24 PM

All replies

  • Aditionally, if I do any command within the button click, as long as it doesn't manipulate the gui, it works fine
    Tuesday, September 19, 2017 6:26 PM
  • You cannot add delegates through a hash.

    Add the button code  before you execute "ShowDialog"  The code has to be in the form to be called by the form event.


    \_(ツ)_/

    Tuesday, September 19, 2017 6:40 PM
  • Alright, that did take care of the issue, but it raises another question. I have a function I dont want to run in the GUI runspace. It exists in the script that created the gui runspace, but I need to call it when I click the Run button. Is there a way to call a function that exists in another runspace?
    Wednesday, September 20, 2017 1:49 PM
  • Basically Im trying to get the GUI and its manipulation to happen and in 1 runspace, and the work of the script to happen in another to keep the GUI responsive.
    Wednesday, September 20, 2017 1:50 PM
  • [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null

    $global:syncHash = [hashtable]::Synchronized(@{})

    $newRunspace = [runspacefactory]::CreateRunspace()
    $newRunspace.ApartmentState = "STA"
    $newRunspace.ThreadOptions = "ReuseThread"         
    $newRunspace.Open()
    $newRunspace.SessionStateProxy.SetVariable("syncHash",$global:syncHash)

    $code = {
        $global:syncHash.Error = $error
        [xml]$xaml = Get-Content -path "c:\gui.XAML"
        $global:syncHash.form=[Windows.Markup.XamlReader]::Load((new-object System.Xml.XmlNodeReader $xaml))
        #$global:XAML.SelectNodes("//*[@Name]")| % {$global:syncHash."$($_.Name)" = $global:syncHash.Form.FindName("$($_.Name)")}
        $global:xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | %{ $Global:synchash.Add($_.Name,$synchash.form.FindName($_.Name) ) } 

    $syncHash.ButtonRun.add_Click({

        $syncHash.form.dispatcher.invoke([action]{$syncHash.TextBoxStatus.Text = "Changed"})
    })
        $global:syncHash.Form.ShowDialog() | Out-Null
    }

    $psCmd = [PowerShell]::Create().addScript($code)
    $psCmd.Runspace = $newRunspace    
    $data = $psCmd.BeginInvoke()

    start-sleep -Milliseconds 100

    Function DoStuff(){

    }

    Wednesday, September 20, 2017 1:52 PM
  • In the above code, I want to call do stuff when the Run button is clicked in the UI. How would I go about that?

    Wednesday, September 20, 2017 1:52 PM
  • In the above code, I want to call do stuff when the Run button is clicked in the UI. How would I go about that?

    You cannot execute code across runspaces.  You can run call code in another runspace but it will still run in the calling runspace.  In compiled code we would do this using other mechanisms where we would send inter-thread messages.  In PS this is not an easy thing to do because PS is a console application and as such does not have a full message loop and the console is not a form.

    The best way to do this is to place all code and functions in the runspace or in a module and load the module/file in the runspace.

    You can use the hash to send events to the runspace but not back to the main PS.

    What is it that you are trying to do?


    \_(ツ)_/

    Wednesday, September 20, 2017 2:06 PM
  • The end result is when a user clicks the run button, its going to ping a machine to see if its active then if active, delete a few registry keys. In addition to those actions, theres a status box in the gui that I wanted to update with what step it was on/statuses. The issue I hit with everything in 1 runspace was when the ping and reg key deletions happened, the UI would hang until they were done executing so the status updates wouldn't be shown and would all go through after the ping/deletion occured. So I was attempting to make the GUI more responsive by doing the work in another runspace than the GUI.

    Wednesday, September 20, 2017 2:21 PM
  • Perhaps my approach is just backwards and I should be offloading the Ping/Reg deletion into the other runspace
    Wednesday, September 20, 2017 2:24 PM
  • Use a job in the GUI.

    See the following: https://info.sapien.com/index.php/guis/gui-advanced-tips/powershell-studio-creating-responsive-forms

    The technique can also be made to work in a XAML form but I recommend just using one form with the Job Tracker and not using a runspace.

    You can download a trial copy of PSS which is a good tool to learn forms scripting with.

    Check out the other forms articles on the site.  They are all very useful.


    \_(ツ)_/

    Wednesday, September 20, 2017 2:27 PM
  • Alright, So I've taken your advice and Im attempting to do this via a job. The issue is, I need to do a test-connection to a machine, and then check the results of that test (Im thinking variable in a hashtable) but the check needs to happen once the job executing the ping has finished. Im trying the object event approach and I cant seem to get it to trip when the job completes. The "do stuff here" in the script below never triggers, so the action of the event never goes. Any ideas what Im doing wrong?

    Function TestConnection() {
           UpdateStatus("Starting Job")
           $Job1 = Start-Job -ScriptBlock { Test-Connection -Computername $computer -BufferSize 16 -Count 1 -Quiet }
           Register-ObjectEvent -InputObject $Job1 -EventName StateChanged -Action {
            do stuff here
            Unregister-event -SourceIdentifier $event.SourceIdentifier
            Remove-Job -Name $event.SourceIdentifier -force

    }

    }

    Friday, September 22, 2017 1:54 PM
  • You cannot get job events in a form.  They are only available in the main script.

    To pick without a delay use the JobTrakker link I posted.


    \_(ツ)_/

    Friday, September 22, 2017 8:17 PM