locked
WPF Runspaces and shared variables RRS feed

  • Question

  • Hi,

    i already asked the question here

    i have a problem with shared variables in runspaces with wpf forms.

    I habe a wpf form with a button and a listbox. On the listbox i have a menuitem to export the results from the listbox.
    When i click on the button i start a runspace, that gets some information (in the example i just do a get-service) and writes them to the listbox.
    To have the possiblity to do an export of the items in the list box with all of the attributes i want to write the results of the command (get-service) to a shared variable that i can use in the wpf form later.
    How it should work:

    - Click Button
    --> Start Runspace and call get-service (Search Button and Export-csv will be set to isenabled=false)
    --> Write the results to a shared variable
    --> Add the results to the listbox within the runspace
    --> When runspace finished set search Button and Export-csv to isenabled=true
    - Click ExportCSV
    --> export the results from the shared variable with all attributes

    Within the runspace i can see that the variable is filled correctly and i can add the results to the listbox or a text field in the wpf gui. The problem is, that when i try to use the shared variable outside the runspace it is empty.

    If i do a similar thing without wpf gui the sharedvariable is accesible from my powershell session but wpf it wont work.

    I am really stuck here and don't get a way to manage it. Can you please provide some help/ideas how to do it.

    The global variable handle just was for testing issues.

    Thanks in advance.

    Regards,

    Tom

    [void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
    [void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
    #region Load XAML
    [xml]$script:XAMLTest = @"
    <Window x:Class="Test.MainWindow"
            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:Test"
            mc:Ignorable="d"
            Title="Test" Height="479.303" Width="320.697" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" Background="White">
        <Grid Margin="0,1,2,-1" Background="#FFF0F0F0">
            <Button x:Name="btn_search" Content="Search" HorizontalAlignment="Left" Margin="111,204,0,0" VerticalAlignment="Top" Width="75" FontSize="12" RenderTransformOrigin="3.108,9.031"/>
            <ListBox x:Name="lst_search" HorizontalAlignment="Left" Height="189" Margin="27,10,0,0" VerticalAlignment="Top" Width="269">
                <ListBox.ContextMenu>
                    <ContextMenu AllowDrop="True">
                        <MenuItem x:Name="lmi_exportcsv" Header="Export to CSV" IsEnabled="True"/>
                    </ContextMenu>
                </ListBox.ContextMenu>
            </ListBox>
            <TextBox x:Name="txt_message" HorizontalAlignment="Left" Height="155" Margin="10,270,0,0" TextWrapping="NoWrap" VerticalAlignment="Top" Width="286" IsReadOnly="True" Background="#FFF0F0F0" VerticalScrollBarVisibility="Auto"/>
        </Grid>
    </Window>
    "@ -replace 'mc:Ignorable="d"', '' -replace "x:N", 'N' -replace '^<Win.*', '<Window'
    #region Load Form
    try {
        $TestForm = [Windows.Markup.XamlReader]::Load( (New-Object System.Xml.XmlNodeReader $XAMLTest) )
    }
    catch {
        Write-Host "Cannot load Windows.Markup.XamlReader"
    }
    $XAMLTest.SelectNodes("//*[@Name]") | ForEach-Object {Set-Variable -Name ($_.Name) -Value $TestForm.FindName($_.Name)}
    #endregion Load Form
    #region variables
    $services   = @()
    $script:sharedData = [HashTable]::Synchronized(@{})
    #endregion variables
    #region functions
    function Invoke-ServiceSearch{
        [CmdletBinding()]
        param (
        [parameter(Position=0,
          Mandatory=$false)]
          $Testform,
        [parameter(Position=1,
          Mandatory=$false)]
          $lst_search,
        [parameter(Position=2,
          Mandatory=$false)]
          $btn_search,
        [parameter(Position=3,
          Mandatory=$false)]
          $txt_message,
          [parameter(Position=4,
          Mandatory=$false)]
          $lmi_exportcsv,
          [parameter(Position=5,
          Mandatory=$false)]
          $services
        )
    
        $code = {
            $shareddata.services = Get-Service
            $sharedData.testform.Dispatcher.invoke([action]{$null = $sharedData.lst_search.ItemsSource = $sharedData.services},'Normal')
            $sharedData.testform.Dispatcher.invoke([action]{$null = $sharedData.txt_message.AppendText("Runspace: $($Shareddata.services.Name)")})
            #$sharedData.testform.Dispatcher.invoke([action]{$null = $sharedData.lmi_exportcsv.IsEnabled="True"})
        }
        
        #region adding variables for use in new runspace
        $shareddata = @{
            testform                    = $testform
            lst_search                  = $lst_search
            btn_search                  = $btn_search
            txt_message                 = $txt_message
            services                    = $services
            error                       = $error
        }
    #endregion adding variables
    
    #generate a new runspace
        $runspace = [RunSpaceFactory]::CreateRunspace()
        $runspace.ApartmentState = 'STA'
        $runspace.ThreadOptions = 'ReuseThread'
        $runspace.Open()
    
        #add shared variable to runspace
        $runspace.SessionStateProxy.setVariable("sharedData", $sharedData)
        #generate a new psthread and add script code
        $ps             = [PowerShell]::Create().AddScript($code)
        $ps.Runspace    = $runspace
        $global:handle  = $ps.BeginInvoke()
    }
    #endregion functions
    $btn_search.Add_Click{
        $SearchParams = @{
            testform                = $TestForm
            btn_search              = $btn_search
            lst_search              = $lst_search
            txt_message             = $txt_message
            lmi_exportcsv           = $lmi_exportcsv
            services                = $services
        }
        Invoke-ServiceSearch @SearchParams
    }
    $lmi_exportcsv.Add_Click{
        $txt_message.AppendText("`nMain: $($shareddata.services.Name)")
    }
    $null = $TestForm.ShowDialog()

    Thursday, October 17, 2019 6:02 AM

Answers

  • #region functions
    function Invoke-ServiceSearch{
        [CmdletBinding()]
        param (
    		$Testform,
    		$lst_search,
    		$btn_search,
    		$txt_message,
    		$lmi_exportcsv,
    		$services
        )
    
        $code = {
            $shareddata.services = Get-Service
            $sharedData.testform.Dispatcher.invoke([action]{$null = $sharedData.lst_search.ItemsSource = $sharedData.services},'Normal')
            $sharedData.testform.Dispatcher.invoke([action]{$null = $sharedData.txt_message.AppendText("Runspace: $($Shareddata.services.Name)")})
            #$sharedData.testform.Dispatcher.invoke([action]{$null = $sharedData.lmi_exportcsv.IsEnabled="True"})
        }
        
        $script:sharedData = [HashTable]::Synchronized(@{
    	   testform                    = $testform
    	   lst_search                  = $lst_search
    	   btn_search                  = $btn_search
    	   txt_message                 = $txt_message
    	   services                    = $services
    	   error                       = $error
    	}
        )
    #generate a new runspace $runspace = [RunSpaceFactory]::CreateRunspace() $runspace.ApartmentState = 'STA' $runspace.ThreadOptions = 'ReuseThread' $runspace.Open() #add shared variable to runspace $runspace.SessionStateProxy.setVariable("sharedData", $sharedData) #generate a new psthread and add script code $ps = [PowerShell]::Create().AddScript($code) $ps.Runspace = $runspace $global:handle = $ps.BeginInvoke() } #endregion functions #region Load XAML [xml]$script:XAMLTest = @" <Window x:Class="Test.MainWindow" 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:Test" mc:Ignorable="d" Title="Test" Height="479.303" Width="320.697" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" Background="White"> <Grid Margin="0,1,2,-1" Background="#FFF0F0F0"> <Button x:Name="btn_search" Content="Search" HorizontalAlignment="Left" Margin="111,204,0,0" VerticalAlignment="Top" Width="75" FontSize="12" RenderTransformOrigin="3.108,9.031"/> <ListBox x:Name="lst_search" HorizontalAlignment="Left" Height="189" Margin="27,10,0,0" VerticalAlignment="Top" Width="269"> <ListBox.ContextMenu> <ContextMenu AllowDrop="True"> <MenuItem x:Name="lmi_exportcsv" Header="Export to CSV" IsEnabled="True"/> </ContextMenu> </ListBox.ContextMenu> </ListBox> <TextBox x:Name="txt_message" HorizontalAlignment="Left" Height="155" Margin="10,270,0,0" TextWrapping="NoWrap" VerticalAlignment="Top" Width="286" IsReadOnly="True" Background="#FFF0F0F0" VerticalScrollBarVisibility="Auto"/> </Grid> </Window> "@ -replace 'mc:Ignorable="d"', '' -replace "x:N", 'N' -replace '^<Win.*', '<Window' #endregion Load XAML #region Load Form add-type -AssemblyName presentationframework try { $TestForm = [Windows.Markup.XamlReader]::Load( (New-Object System.Xml.XmlNodeReader $XAMLTest) ) } catch { Write-Host "Cannot load Windows.Markup.XamlReader" } $XAMLTest.SelectNodes("//*[@Name]") | ForEach-Object {Set-Variable -Name ($_.Name) -Value $TestForm.FindName($_.Name)} $btn_search.Add_Click{ $SearchParams = @{ testform = $TestForm btn_search = $btn_search lst_search = $lst_search txt_message = $txt_message lmi_exportcsv = $lmi_exportcsv services = $services } Invoke-ServiceSearch @SearchParams } $lmi_exportcsv.Add_Click{ $txt_message.AppendText("`nMain: $($shareddata.services.Name)") } #endregion Load Form [void]$TestForm.ShowDialog() $shareddata.services


    \_(ツ)_/



    • Edited by jrv Thursday, October 17, 2019 6:52 AM
    • Marked as answer by peterlustig_123 Friday, October 18, 2019 5:06 AM
    Thursday, October 17, 2019 6:46 AM

All replies

  • $shareddata = [hashtable]::Synchronized(@{
            testform                    = $testform
            lst_search                  = $lst_search
            btn_search                  = $btn_search
            txt_message                 = $txt_message
            services                    = $services
            error                       = $error
        }
    )
    


    \_(ツ)_/

    Thursday, October 17, 2019 6:10 AM
  • Hello jrv,

    thanks for your answer, but i dont get it what you mean.

    The variable $shareddata is definded in line 37.

    Regards,

    Tom

    Thursday, October 17, 2019 6:14 AM
  • It is defined wrong. It needs to be defined as a sync hash.


    \_(ツ)_/

    Thursday, October 17, 2019 6:19 AM
  • Hi jrv,

    it is :

    $script:sharedData = [HashTable]::Synchronized(@{})

    Regards,
    Tom

    Thursday, October 17, 2019 6:24 AM
  • Sorry it is hard to read your code.


    \_(ツ)_/

    Thursday, October 17, 2019 6:25 AM
  • Hi,

    i had it before inside the function but i thought the scope of the variable is perhaps the problem so i defined it in main with scope script.

    Regards,

    Tom

    Thursday, October 17, 2019 6:27 AM
  • You create the variable outside of the form then create a new version insid of teh form.You cannot do both or the function versio will be created only inside the function and it will not be the correct type.

    \_(ツ)_/



    • Edited by jrv Thursday, October 17, 2019 6:33 AM
    Thursday, October 17, 2019 6:32 AM
  • #region functions
    function Invoke-ServiceSearch{
        [CmdletBinding()]
        param (
    		$Testform,
    		$lst_search,
    		$btn_search,
    		$txt_message,
    		$lmi_exportcsv,
    		$services
        )
    
        $code = {
            $shareddata.services = Get-Service
            $sharedData.testform.Dispatcher.invoke([action]{$null = $sharedData.lst_search.ItemsSource = $sharedData.services},'Normal')
            $sharedData.testform.Dispatcher.invoke([action]{$null = $sharedData.txt_message.AppendText("Runspace: $($Shareddata.services.Name)")})
            #$sharedData.testform.Dispatcher.invoke([action]{$null = $sharedData.lmi_exportcsv.IsEnabled="True"})
        }
        
        $script:sharedData = [HashTable]::Synchronized(@{
    	   testform                    = $testform
    	   lst_search                  = $lst_search
    	   btn_search                  = $btn_search
    	   txt_message                 = $txt_message
    	   services                    = $services
    	   error                       = $error
    	}
        )
    #generate a new runspace $runspace = [RunSpaceFactory]::CreateRunspace() $runspace.ApartmentState = 'STA' $runspace.ThreadOptions = 'ReuseThread' $runspace.Open() #add shared variable to runspace $runspace.SessionStateProxy.setVariable("sharedData", $sharedData) #generate a new psthread and add script code $ps = [PowerShell]::Create().AddScript($code) $ps.Runspace = $runspace $global:handle = $ps.BeginInvoke() } #endregion functions #region Load XAML [xml]$script:XAMLTest = @" <Window x:Class="Test.MainWindow" 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:Test" mc:Ignorable="d" Title="Test" Height="479.303" Width="320.697" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" Background="White"> <Grid Margin="0,1,2,-1" Background="#FFF0F0F0"> <Button x:Name="btn_search" Content="Search" HorizontalAlignment="Left" Margin="111,204,0,0" VerticalAlignment="Top" Width="75" FontSize="12" RenderTransformOrigin="3.108,9.031"/> <ListBox x:Name="lst_search" HorizontalAlignment="Left" Height="189" Margin="27,10,0,0" VerticalAlignment="Top" Width="269"> <ListBox.ContextMenu> <ContextMenu AllowDrop="True"> <MenuItem x:Name="lmi_exportcsv" Header="Export to CSV" IsEnabled="True"/> </ContextMenu> </ListBox.ContextMenu> </ListBox> <TextBox x:Name="txt_message" HorizontalAlignment="Left" Height="155" Margin="10,270,0,0" TextWrapping="NoWrap" VerticalAlignment="Top" Width="286" IsReadOnly="True" Background="#FFF0F0F0" VerticalScrollBarVisibility="Auto"/> </Grid> </Window> "@ -replace 'mc:Ignorable="d"', '' -replace "x:N", 'N' -replace '^<Win.*', '<Window' #endregion Load XAML #region Load Form add-type -AssemblyName presentationframework try { $TestForm = [Windows.Markup.XamlReader]::Load( (New-Object System.Xml.XmlNodeReader $XAMLTest) ) } catch { Write-Host "Cannot load Windows.Markup.XamlReader" } $XAMLTest.SelectNodes("//*[@Name]") | ForEach-Object {Set-Variable -Name ($_.Name) -Value $TestForm.FindName($_.Name)} $btn_search.Add_Click{ $SearchParams = @{ testform = $TestForm btn_search = $btn_search lst_search = $lst_search txt_message = $txt_message lmi_exportcsv = $lmi_exportcsv services = $services } Invoke-ServiceSearch @SearchParams } $lmi_exportcsv.Add_Click{ $txt_message.AppendText("`nMain: $($shareddata.services.Name)") } #endregion Load Form [void]$TestForm.ShowDialog() $shareddata.services


    \_(ツ)_/



    • Edited by jrv Thursday, October 17, 2019 6:52 AM
    • Marked as answer by peterlustig_123 Friday, October 18, 2019 5:06 AM
    Thursday, October 17, 2019 6:46 AM
  • Hello,

    sorry i dont understand it. Can you explain me a little more in detail please.

    Regards,

    Tom

    Thursday, October 17, 2019 6:47 AM
  • You define the variable where you need to use it but declare it at an outer scope so that it persists outside of the function.

    Look at how I did this and how I rearranged your code to make it more understandable.


    \_(ツ)_/

    Thursday, October 17, 2019 6:49 AM
  • Hi,

    thanks a lot i will have a look.

    Regards,
    Tom

    Thursday, October 17, 2019 7:21 AM
  • Hi jrv,

    thank you very much it works for me.

    Regards,

    Tom

    Friday, October 18, 2019 5:06 AM