【问题标题】:How do I connect from a Powershell Runspace back to the main Powershell thread?如何从 Powershell 运行空间连接回主 Powershell 线程?
【发布时间】:2019-03-12 15:00:59
【问题描述】:

我已经开始在 powershell 中考虑运行空间和 我能够做到以下几点

  • 创建运行空间
  • 将变量传递给它
  • 将预先编写的函数传递给它
  • GUI/WPF 出现了,我什至可以操作上面的元素(例如,更改文本块中的文本)

问题是按钮之一(BTN_Exit)... 我想在单击时实现这一点,它不仅会关闭 GUI,还会关闭它运行的 RunSpace。我已经到了设法关闭 GUI 甚至调用 Runspace 的关闭序列的地步,但是(我假设)由于被调用的函数在 RunSpace 本身中运行,它只是挂断了(RunSpace 仍然处于“关闭”状态状态) - 显然 Powershell 无法杀死自己 :) 幸运的是,我仍然能够使用 $(Get-Runspace)[-1].Dispose()(手动)从主线程处理它

我相信我需要连接回创建 GUI 运行空间的主线程并从那里关闭它,但我无法在函数中返回那里。如果窗口打开,我可以手动执行 close-runspace 函数中的所有 cmdlet,并达到预期的目标。

我尝试添加$rs.connect($(Get-Runspace).Name -eq "Runspace1")$RS.disconnect() 如果我在“句柄”或实例上尝试相同的操作,它会产生相同的结果。如果不将函数传递给 RunSpace,我也无法在单击按钮时调用原始函数。

我如何以编程方式达到在单击按钮时关闭 GUI 并释放运行空间的程度?

代码如下:

#===[___VARIABLES___]===
$GUI = [hashtable]::Synchronized(@{})
$GUI.Host = $host

#===[___FUNCTIONS___]===
function Close-RunSpace {
  if(!($RSI01H.Iscompleted)){
    $GUI.BS.Dispatcher.invoke([action]{
      $GUI.BS.Close()
    })
  }
  $RSI01.EndInvoke($RSI01H)
  $RS.Close()
  $RS.Dispose()
}

#===[__RunSpaceCfg__]===
$RS_ISS = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
$RS_ISS.Commands.Add((New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList 'Close-RunSpace', (Get-Content Function:\Close-RunSpace -ErrorAction Stop)))
$RS = [runspacefactory]::CreateRunspace($RS_ISS)
$RS.ApartmentState = "STA"
$RS.ThreadOptions = "ReuseThread"
$RS.Open()
$RS.SessionStateProxy.SetVariable("GUI",$GUI)
$RS.SessionStateProxy.SetVariable("RS",$RS)
$RS.SessionStateProxy.SetVariable("RSI01",$RSI01)
$RS.SessionStateProxy.SetVariable("RSI01H",$RSI01H)

#===[___EXECUTION___]===
$RSI01 = [powershell]::Create().AddScript(
  {
    [void][System.Reflection.Assembly]::LoadWithPartialName("PresentationCore")
    [void][System.Reflection.Assembly]::LoadWithPartialName("PresentationFramework")
    [void][System.Reflection.Assembly]::LoadWithPartialName("WindowsBase")
    [void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

    $Def_XAML = DATA {'
      <Window x:Name="BS" x:Class="PST.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:PST"
        mc:Ignorable="d"
        Title="BootStrapR" 
        Height="450" 
        Width="800" 
        ResizeMode="NoResize" 
        Topmost="True" 
        FontFamily="Arial" 
        WindowStartupLocation="CenterScreen" 
        WindowStyle="None">
      <Grid>
        <Button x:Name="BTN_Update" 
          Content="Update" 
          HorizontalAlignment="Left" 
          VerticalAlignment="Top" 
          Margin="109,352,0,0" 
          Width="75"/>
        <Button x:Name="BTN_Exit" 
          Content="exit" 
          HorizontalAlignment="Left" 
          Margin="431,352,0,0" 
          VerticalAlignment="Top" 
          Width="75"/>
        <ProgressBar x:Name="pb" 
          HorizontalAlignment="Left" 
          Height="29" 
          Margin="10,150,0,0" 
          VerticalAlignment="Top" 
          Width="780" 
          Background="White" 
          Foreground="Black" 
          BorderBrush="White" 
          Value="0" />
        <TextBlock x:Name="pstext" 
          HorizontalAlignment="Left" 
          Height="266" 
          Margin="26,22,0,0" 
          TextWrapping="Wrap" 
          Text="TextBlock" 
          VerticalAlignment="Top" 
          Width="223"/>
        </Grid>
      </Window>
    '}

    [xml]$XAML = $Def_XAML -replace 'x:Class=*.*','' `
                           -replace 'mc:Ignorable="d"','' `
                           -replace "x:Name",'Name'
    $WPF = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $XAML))
    $XAML.SelectNodes("//*[@Name]") | ForEach-Object{
      $GUI."$($_.Name)" = $WPF.FindName($_.Name)
    }

    $GUI.Error = $Error

    $GUI.BTN_Exit.Add_Click({
      #$GUI.BS.Close()
      Close-RunSpace
    })

    $GUI.BTN_Update.Add_Click({
      $GUI.pb.Value+=1
    })

    $GUI.BS.ShowDialog() | Out-Null
  }
)
$RSI01.Runspace = $RS
$RSI01.Runspace.Name = "GUI"
$RSI01H = $RSI01.BeginInvoke()

【问题讨论】:

    标签: wpf powershell runspace


    【解决方案1】:

    哦,我忘记了这篇文章! 我找到的解决方案/解决方法在 this 文章中。

    基本上,本地 RunSpace 也需要传递到运行 GUI 的 RunSpace。 (我做了:))然后可以引发在主线程中调用函数/脚本块等的事件。 这个事件触发器然后做家务并清理/删除 GUI Runspace 注意:窗口本身需要从 GUI RunSpace 内关闭/@按钮单击,否则 Runspace 不会进入 IsComplete 状态并且被调用的函数将挂起(等到RS完成)

    #===[___VARIABLES___]===
    $Global:GUI = [hashtable]::Synchronized(@{})
    $Global:VAR = [hashtable]::Synchronized(@{})
    $VAR.Host = $Host
    
    #===[______XAML_____]===
    $XAML_BS = @"
    <Window x:Name="BootStrapper" x:Class="SIT_SDM.Window"
      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:SIT_SDM"
      mc:Ignorable="d"
      Height="450" Width="800" Background="#FF2F1333" WindowStyle="None">
     <Grid>
       <Button Name="BS_BTN_X" Content="Close" Margin="600,0,25,0" VerticalAlignment="Top" Width="75"/>
     </Grid>
    </Window>
    "@
    
    #===[___FUNCTIONS___]===
    function BS_Close {
      begin {
        $Global:GUI.BootStrapper.Add_Closing({$_.Cancel = $true})
      }
    
      process {
        if ($BS_Handle.IsCompleted) {
          $BS.EndInvoke($BS_Handle)
          $BS.RunSpace.Close()
          $BS.RunSpace.Dispose()
        } else {
          Write-Error -Text "Runspace job not complete!"
        }
        Unregister-Event -SourceIdentifier "BS_Close"
      }
    
      end {
        if ((Get-Runspace).count -ge 2) {
          $(Get-Runspace)[-1].Dispose()
        }
      }
    }
    
    function Initialize-XAML {
    
      [CmdletBinding()]
      Param(
        [Parameter()]
        [string]$File,
        [string]$Variable
      )
      [void][System.Reflection.Assembly]::LoadWithPartialName("PresentationCore")
      [void][System.Reflection.Assembly]::LoadWithPartialName("PresentationFramework")
    
      if (!([string]::IsNullOrEmpty($File))) {
        $inputXAML = Get-Content -Path $File -ErrorAction Stop
      } elseif (!([string]::IsNullOrEmpty($Variable))) {
        $inputXAML = $Variable 
      } else {
        #Write-Error "Neither File nor Variable has been declared"
        break
      }
    
       #Clean XAML for PowerShell compatibilty
       [xml]$XAML = $inputXAML -replace 'x:Class=*.*','' -replace 'mc:Ignorable="d"','' -replace "x:Name",'Name'
    
      #Create the XAML reader using XML node reader 
      $ReadR = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $XAML))
    
      #Grab named objects from tree and put in a flat structure using Xpath
      $NamedNodes = $XAML.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]")
      $NamedNodes | ForEach-Object {
        $Script:GUI.Add($_.Name, $ReadR.FindName($_.Name))
      }
    }
    
    #===[___EXECUTION___]===
    $InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
    $Functions = Get-Command -Module SITSD #Script is being used with import-module
    
    foreach($Function in $Functions){
      $functionDefinition = Get-Content function:\$Function
      $functionEntry = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $functionDefinition
      $InitialSessionState.Commands.Add($functionEntry)
    }
    
    $RS = [runspacefactory]::CreateRunspace($InitialSessionState)
    $RS.ApartmentState = "STA"
    $RS.ThreadOptions = "ReuseThread"
    $RS.Open()
    
    #Passing variables to Runspace
    $RS.SessionStateProxy.SetVariable("GUI",$GUI)
    $RS.SessionStateProxy.SetVariable("VAR",$VAR)
    $RS.SessionStateProxy.SetVariable("XAML_BS",$XAML_BS)
    
    Register-EngineEvent -SourceIdentifier "BS_Close" -Action {BS_Close}
    
    $BS = [PowerShell]::Create().AddScript({
      Initialize-XAML -Variable $XAML_BS
    
      $GUI.BS_BTN_X.Add_Click({
         $Global:VAR.host.UI.Write("Button pressed")
         $GUI.BootStrapper.Close()
         $Global:VAR.Host.Runspace.Events.GenerateEvent( "BS_Close", $GUI.BS_BTN_X, $null, "BS_Close")
      })
    
      $GUI.BootStrapper.Showdialog()|Out-Null
    })
    
    $BS.RunSpace = $RS
    $BS_Handle = $BS.BeginInvoke()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-07-08
      • 2010-11-14
      • 2015-11-17
      • 1970-01-01
      • 2021-03-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多