简体   繁体   English

如何从 Powershell Runspace 连接回 Powershell 主线程?

[英]How do I connect from a Powershell Runspace back to the main Powershell thread?

I've started thinkering around with runspaces in powershell and I was able to do the followings我已经开始在 powershell 中考虑运行空间,并且能够执行以下操作

  • Create the RunSpace创建运行空间
  • Pass variables to it将变量传递给它
  • Pass a pre-written function to it将预先编写的函数传递给它
  • GUI/WPF shows up and I can even manipulate the elements on it (Eg. changing text within the textblock) GUI/WPF 出现,我什至可以操纵它上面的元素(例如,更改文本块中的文本)

The issue is one of the buttons (BTN_Exit)... I would like to achieve that when clicked, it's not only closing the GUI but closes the RunSpace where it runs.问题是按钮之一(BTN_Exit)...我想在点击时实现这一点,它不仅关闭 GUI,而且关闭它运行的 RunSpace。 I've got to the point where I managed to close the GUI and even to call the close sequence for the Runspace but (I assume) as the called function runs in the RunSpace itself, it just hangs up (RunSpace remains in "closing" state) -Apparently Powershell is unable to kill itself :) Luckily I am still able to dispose it from the main thread with $(Get-Runspace)[-1].Dispose() (Manually)我已经到了设法关闭 GUI 甚至调用 Runspace 的关闭序列的地步,但是(我假设)当被调用的函数在 RunSpace 本身中运行时,它只是挂断了(RunSpace 保持在“关闭”状态) state) - 显然 Powershell 无法杀死自己 :) 幸运的是我仍然能够使用$(Get-Runspace)[-1].Dispose() (手动)从主线程中处理它

I believe I need to connect back to the main thread where the GUI Runspace has been created and close it from there but I am unable to get back there within the Function.我相信我需要连接回创建 GUI 运行空间的主线程并从那里关闭它,但我无法在函数中返回那里。 Manually if window is open I am able to execute all cmdlets within the close-runspace function and it achieves the desired goal.如果窗口打开,我可以手动执行 close-runspace 函数中的所有 cmdlet 并实现预期目标。

I've tried adding $rs.connect($(Get-Runspace).Name -eq "Runspace1") & $RS.disconnect() If I try the same thing on the "Handle" or Instance it yields the same result.我试过添加$rs.connect($(Get-Runspace).Name -eq "Runspace1") & $RS.disconnect()如果我在“句柄”或实例上尝试同样的事情,它会产生相同的结果。 Neither was I able to call the original function on button click without passing the function to the RunSpace.我也无法在按钮单击时调用原始函数而不将函数传递给 RunSpace。

How would I get programmatically to the point that when the Button is being clicked it closes the GUI and disposes the Runspace?我如何以编程方式获得单击 Button 时它关闭 GUI 并处理运行空间的程度?

Here is the code:这是代码:

#===[___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()

OH I forgot about this post!哦,我忘记了这篇文章! The solution/workaround I've found is in this article.该解决方案/解决方法,我发现在这个文章。

Basically the local RunSpace needs to be passed as well to the RunSpace where the GUI is running.基本上,本地运行空间也需要传递给运行 GUI 的运行空间。 (Which I did :) ) And then events can be raised which are calling function/scriptblocks etc. in the main thread. (我做了 :) )然后可以引发在主线程中调用函数/脚本块等的事件。 This event trigger then does the housekeeping stuff and cleans/removes the GUI Runspace NOTE: the window itself needs to be closed from within the GUI RunSpace / @ button click otherwise the Runspace won't get into IsComplete state and the called function will hang (wait till RS is completed)此事件触发器然后执行内务处理并清除/删除 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()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM