locked
Creating Runspaces RRS feed

  • Question

  • Hello there,

    i have some problems with runspaces and shared variables. I try to generate a form with textbox, than call a function and write the logs of this function in to the textbox as the commands happen.

    If i only generate a new namespace with a shared variable, the namespace generation and the acces of the shared variable is working

            $code = {
                $sharedData.Text = "Test "
                Start-sleep 10
                $sharedData.Text += 'Test2'
            }
    
            #functions: [HashTable]::Synchronized(). Esentially this allows you to map an existing hash table to a syncronized variable for use between threads:
            $sharedData = [HashTable]::Synchronized(@{})
            $sharedData.Text
    
            #PowerShell uses the concept of RunSpaces – each of which can be set up to run as either MTA (multi-threaded appartment) or STA (single-threaded appartment).
            #Create a new RunSpace easily using PowerShell v2.0:
    
            $newRunspace = [RunSpaceFactory]::CreateRunspace()
            $newRunspace.ApartmentState = "MTA"
            $newRunspace.ThreadOptions = "ReuseThread"
            $newRunspace.Open()
     
            #Add shared variable to runspace
     
            $newRunspace.SessionStateProxy.setVariable("sharedData", $sharedData)
     
            #Run script code in child thread
    
            $newPowerShell = [PowerShell]::Create().AddScript($code)
            $newPowerShell.Runspace = $newRunspace
            $newPowerShell.BeginInvoke()
    
            #Wait for runspaces to finish
           
            #$newrunspace.Dispose()
    

    But if i try to run it within my form it will not work at all.

    [xml]$XAMLSummary = @"
    <Window x:Class="Shared_Mailboxes_Ressources.Window1"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:Shared_Mailboxes_Ressources"
            mc:Ignorable="d"
            Title="Summary" Height="400" Width="500" ResizeMode="NoResize" WindowStartupLocation="CenterOwner">
        <Grid Background="#FFF0F0F0">
            <TextBox x:Name="txt_summary" Height="Auto" TextWrapping="Wrap" Text="" Width="Auto" VerticalScrollBarVisibility="Visible" Margin="10,10,10,35" Background="#FFF0F0F0" IsReadOnly="True"/>
            <Button x:Name="btn_summarycontinue" Content="Continue" HorizontalAlignment="Left" Margin="409,341,0,0" VerticalAlignment="Top" Width="75"/>
            <Button x:Name="btn_summarycancel" Content="Cancel" HorizontalAlignment="Left" Margin="316,341,0,0" VerticalAlignment="Top" Width="75"/>
        </Grid>
    </Window>
    
    "@ -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window' #-replace wird benötigt, wenn XAML aus Visual Studio kopiert wird. 
        #XAML laden 
        [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') 
        try{ 
          $GetSummaryForm=[Windows.Markup.XamlReader]::Load( (New-Object System.Xml.XmlNodeReader $XAMLSummary) ) 
        } catch { 
          Write-Host "Windows.Markup.XamlReader konnte nicht geladen werden. Mˆgliche Ursache: ung¸ltige Syntax oder fehlendes .net" 
        } 
    
        $xamlSummary.SelectNodes("//*[@Name]") | %{Set-Variable -Name ($_.Name) -Value $GetSummaryForm.FindName($_.Name)} 
        #region subfunctions
            $code = {
                $sharedData.Text = "Test "
                Start-sleep 10
                $sharedData.Text += 'Test2'
            }
        
    
        #endregion subfunctions
        
        #region actions
        $btn_summarycancel.Add_Click{
            $GetSummaryForm.Close() 
        }
    
        $btn_summarycontinue.Add_Click{
    
            $btn_summarycontinue.IsEnabled = $false  
              
            #functions: [HashTable]::Synchronized(). Esentially this allows you to map an existing hash table to a syncronized variable for use between threads:
            $sharedData = [HashTable]::Synchronized(@{})
            
            #PowerShell uses the concept of RunSpaces – each of which can be set up to run as either MTA (multi-threaded appartment) or STA (single-threaded appartment).
            #Create a new RunSpace easily using PowerShell v2.0:
    
            $newRunspace = [RunSpaceFactory]::CreateRunspace()
            $newRunspace.ApartmentState = "MTA"
            $newRunspace.ThreadOptions = "ReuseThread"
            $newRunspace.Open()
     
            #Add shared variable to runspace
     
            $newRunspace.SessionStateProxy.setVariable("sharedData", $sharedData)
     
            #Run script code in child thread
    
            $newPowerShell = [PowerShell]::Create().AddScript($code)
            $newPowerShell.Runspace = $newRunspace
            $asyncobject=$newPowerShell.BeginInvoke()       
    
            #Wait for runspaces to finish
            do{
                $txt_summary.Text = $shareddata.text
            }while($SharedData -ne $null)
            while ($asyncobject.IsCompleted -notcontains $true) {}
            $newrunspace.Dispose()
            
            $btn_summarycontinue.isEnabled = $true
    	}
        #endregion actions
    
        $GetSummaryForm.ShowDialog() 
    
    

    I don't really get it what i miss here, it always freeze my window and nothing happens :(

    At the end i want to call a script with about 20 commands. After each command i want to report back the return value of the command to the textbox.

    Any help would be appreciated.

    Regards,

    Tom

    Monday, April 23, 2018 4:12 AM

Answers

  • Here is a completely functional version using the Window.Dispatcher to access the foreign thread.  It updates the textbox and does not dreeze the form.

    [xml]$XAMLSummary = @'
    <Window x:Class="Shared_Mailboxes_Ressources.Window1"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:Shared_Mailboxes_Ressources"
            mc:Ignorable="d"
            Title="Summary" Height="400" Width="500" ResizeMode="NoResize" WindowStartupLocation="CenterOwner">
        <Grid Background="#FFF0F0F0">
            <TextBox x:Name="txt_summary" Height="Auto" TextWrapping="Wrap" Text="" Width="Auto" VerticalScrollBarVisibility="Visible" Margin="10,10,10,35" Background="#FFF0F0F0" IsReadOnly="True"/>
            <Button x:Name="btn_summarycontinue" Content="Continue" HorizontalAlignment="Left" Margin="409,341,0,0" VerticalAlignment="Top" Width="75"/>
            <Button x:Name="btn_summarycancel" Content="Cancel" HorizontalAlignment="Left" Margin="316,341,0,0" VerticalAlignment="Top" Width="75"/>
        </Grid>
    </Window>
    '@ -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window' 
    
    #XAML laden 
    [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') 
    try{ 
      $GetSummaryForm=[Windows.Markup.XamlReader]::Load( (New-Object System.Xml.XmlNodeReader $XAMLSummary) ) 
    } catch { 
      Write-Host "Windows.Markup.XamlReader konnte nicht geladen werden. Mˆgliche Ursache: ung¸ltige Syntax oder fehlendes .net" 
    } 
    
    $xamlSummary.SelectNodes("//*[@Name]") | %{
        Set-Variable -Name $_.Name -Value $GetSummaryForm.FindName($_.Name)
    }
    
    $code = {
        $sharedData.Window.Dispatcher.invoke([action]{$sharedData.TB.Text = "Test 0`n"},'Normal')
        1..20 | %{
            sleep 1
            $sharedData.Window.Dispatcher.invoke([action]{$sharedData.TB.Text += "Test $_`n"},'Normal')
        }
    }
    $sharedData = [HashTable]::Synchronized(@{})
    $sharedData.Window = $GetSummaryForm
    $sharedData.TB = $txt_summary
    $newRunspace = [RunSpaceFactory]::CreateRunspace()
    $newRunspace.ApartmentState = 'STA'
    #$newRunspace.ThreadOptions = 'ReuseThread'
    $newRunspace.Open()
    
    #Add shared variable to runspace
    $newRunspace.SessionStateProxy.setVariable("sharedData", $sharedData)
    
    $newPowerShell = [PowerShell]::Create().AddScript($code)
    $newPowerShell.Runspace = $newRunspace
    
    $btn_summarycancel.Add_Click{$GetSummaryForm.Close()}
    
    $btn_summarycontinue.Add_Click{
        Write-Host 'Hello from click' -ForegroundColor Green
        $btn_summarycontinue.IsEnabled = $false
        $asyncobject=$newPowerShell.BeginInvoke()      
        #$btn_summarycontinue.isEnabled = $true
    }
    $GetSummaryForm.ShowDialog() 


    \_(ツ)_/




    • Edited by jrv Monday, April 23, 2018 9:46 AM
    • Marked as answer by peterlustig_123 Monday, April 23, 2018 10:38 AM
    Monday, April 23, 2018 9:43 AM

All replies

  • We cannot help you with code copied from the Internet. We also will not decode 100+ lines of code to figure out your request.

     Please only post code that you have written and as a specific question.  Post any full error messages.


    \_(ツ)_/


    • Edited by jrv Monday, April 23, 2018 4:35 AM
    Monday, April 23, 2018 4:34 AM
  • Hello,

    yes the first code is taken from the internet, but it is not copied one to one.

    I worked through following articles:

    https://learn-powershell.net/2012/10/14/powershell-and-wpf-writing-data-to-a-ui-from-a-different-runspace/
    https://smsagent.wordpress.com/2015/09/07/powershell-tip-utilizing-runspaces-for-responsive-wpf-gui-applications/
    https://blogs.technet.microsoft.com/heyscriptingguy/2015/11/28/beginning-use-of-powershell-runspaces-part-3/
    https://cjoprey.wordpress.com/archived/asynchronicity-in-powershell/

    The second code is written by myself (yes i changed the $code behind and the part with runspace is also taken from the internet).
    I regret this, but i do not get allong with it. With the first code i have a responsive powershell and the code block is executed in a separate runspace. With the second code, my GUI freezes and i get a "Not responding".

    There is no error output.

    The specific question is, what did i miss in the second code to have a respnding GUI and the code part is executed in a separate runspace.

    Thanks in advance.

    Regards,

    Tom


    Monday, April 23, 2018 5:59 AM
  • You will have to use standard debugging techniques to track down your errors.  We can answer specific questions but the forum is not for free debugging.


    \_(ツ)_/

    Monday, April 23, 2018 6:01 AM
  • Also your question is way to vague.  What does "if i try to run it within my form"?   There is no way for us to know what you mean by this.

    The first block of code does nothing and the second block shows a simple GUI and runs as expected.


    \_(ツ)_/

    Monday, April 23, 2018 6:06 AM
  • Also you cannot do the following in a Form's event code.

            #Wait for runspaces to finish
            do{
                $txt_summary.Text = $shareddata.text
            }while($SharedData -ne $null)
            while ($asyncobject.IsCompleted -notcontains $true) {}
            $newrunspace.Dispose()
            
            $btn_summarycontinue.isEnabled = $true
    	}
    

    This will freeze any form whether there is a runspace or not.

    What you are trying to do just doesn't make any technical sense.  Perhaps you should learn both PowerShell and how to program forms.  It is not a trivial task and WPF is a bad place for non-programmers to start.


    \_(ツ)_/

    Monday, April 23, 2018 6:12 AM
  • Hi,

    Yes i did not learn programming in the past and i am no powershell guru, I just try to get along with this wpf thing with runspaces.
    So keep your knowledge, i try get around myself...


    I can't find a way to delete the post so please do this for me
    Monday, April 23, 2018 6:31 AM
  • No matter what you say the code cannot be run as you think because you cannot run blocking code in a Form event.  The issue you are asking about is caused by exactly that. 

    What yo are trying to do is impossible to discover from what you have posted.  Without some knowledge of Windows Forms and threading it will be very hard for you to understand this.  You cannot guess your way threw any complex technology.

    Start by learning basic PowerShell and then search for articles explaining how forms and events work in Windows.  Once you have some knowledge of how these tings work you will be able to understand why you can't block an event.  Until then just understand that you cannot sit in a loop in an event without freezing the form.


    \_(ツ)_/

    Monday, April 23, 2018 8:02 AM
  • Hi,

    i am aware that my while loop is freezing my window.
    I tried a lot to get this textbox updated dynamically by a script running in a separate runspace.
    I just hoped that anyone tells me how to proceed.

    I don't want that anyone writes the code for me but perhaps try to explain in words how to get along.

    So i will keep reading articles.

    So please close the thread.

    Regards,

    Tom

    Monday, April 23, 2018 8:27 AM
  • I explained the issue you asked about as clearly as I can. 

    To share data you also need to pass a reference to the shared object/variable in the synchash.  You have not done this.  The synchash has no shared objects.  Assign the textbox object as a shared object in the synchash.

    $synchash.TextBox = $yourtextbox

    You also need to understand how to get references to objects in the WPF/XAML from to use from PowerShell code.

    The reason you cannot do this is very clear if you look at th streams to see the errors generated by your code.

    Error       : {Exception setting "Text": "The calling thread cannot access this object because a different thread owns it.", Exception setting "Text":
                  "The calling thread cannot access this object because a different thread owns it.", Exception setting "Text": "The calling thread cannot
                  access this object because a different thread owns it.", Exception setting "Text": "The calling thread cannot access this object because a
                  different thread owns it."...}

    Note that you cannot call across threads.  In a form the event code is running on a thread that is inaccessible from the new runspace.

    If you use standard Windows forms for this then the synchash will work because standard forms is threaded differently.  I  WPF the form objects can only be accessed through a delegate.


    \_(ツ)_/


    • Edited by jrv Monday, April 23, 2018 9:09 AM
    Monday, April 23, 2018 9:08 AM
  • Here is a completely functional version using the Window.Dispatcher to access the foreign thread.  It updates the textbox and does not dreeze the form.

    [xml]$XAMLSummary = @'
    <Window x:Class="Shared_Mailboxes_Ressources.Window1"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:Shared_Mailboxes_Ressources"
            mc:Ignorable="d"
            Title="Summary" Height="400" Width="500" ResizeMode="NoResize" WindowStartupLocation="CenterOwner">
        <Grid Background="#FFF0F0F0">
            <TextBox x:Name="txt_summary" Height="Auto" TextWrapping="Wrap" Text="" Width="Auto" VerticalScrollBarVisibility="Visible" Margin="10,10,10,35" Background="#FFF0F0F0" IsReadOnly="True"/>
            <Button x:Name="btn_summarycontinue" Content="Continue" HorizontalAlignment="Left" Margin="409,341,0,0" VerticalAlignment="Top" Width="75"/>
            <Button x:Name="btn_summarycancel" Content="Cancel" HorizontalAlignment="Left" Margin="316,341,0,0" VerticalAlignment="Top" Width="75"/>
        </Grid>
    </Window>
    '@ -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window' 
    
    #XAML laden 
    [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework') 
    try{ 
      $GetSummaryForm=[Windows.Markup.XamlReader]::Load( (New-Object System.Xml.XmlNodeReader $XAMLSummary) ) 
    } catch { 
      Write-Host "Windows.Markup.XamlReader konnte nicht geladen werden. Mˆgliche Ursache: ung¸ltige Syntax oder fehlendes .net" 
    } 
    
    $xamlSummary.SelectNodes("//*[@Name]") | %{
        Set-Variable -Name $_.Name -Value $GetSummaryForm.FindName($_.Name)
    }
    
    $code = {
        $sharedData.Window.Dispatcher.invoke([action]{$sharedData.TB.Text = "Test 0`n"},'Normal')
        1..20 | %{
            sleep 1
            $sharedData.Window.Dispatcher.invoke([action]{$sharedData.TB.Text += "Test $_`n"},'Normal')
        }
    }
    $sharedData = [HashTable]::Synchronized(@{})
    $sharedData.Window = $GetSummaryForm
    $sharedData.TB = $txt_summary
    $newRunspace = [RunSpaceFactory]::CreateRunspace()
    $newRunspace.ApartmentState = 'STA'
    #$newRunspace.ThreadOptions = 'ReuseThread'
    $newRunspace.Open()
    
    #Add shared variable to runspace
    $newRunspace.SessionStateProxy.setVariable("sharedData", $sharedData)
    
    $newPowerShell = [PowerShell]::Create().AddScript($code)
    $newPowerShell.Runspace = $newRunspace
    
    $btn_summarycancel.Add_Click{$GetSummaryForm.Close()}
    
    $btn_summarycontinue.Add_Click{
        Write-Host 'Hello from click' -ForegroundColor Green
        $btn_summarycontinue.IsEnabled = $false
        $asyncobject=$newPowerShell.BeginInvoke()      
        #$btn_summarycontinue.isEnabled = $true
    }
    $GetSummaryForm.ShowDialog() 


    \_(ツ)_/




    • Edited by jrv Monday, April 23, 2018 9:46 AM
    • Marked as answer by peterlustig_123 Monday, April 23, 2018 10:38 AM
    Monday, April 23, 2018 9:43 AM
  • Hello,

    i was not aware, that runspaces in wpf has a different behaviour.
    Thank you very much for your answer i understand why this is not working, but at the moment i can not follow the windows dispatcher logic. I will have a look at this.

    Regards,

    Tom

    Monday, April 23, 2018 10:38 AM