簡體   English   中英

如何從 Powershell Runspace 連接回 Powershell 主線程?

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

我已經開始在 powershell 中考慮運行空間,並且能夠執行以下操作

  • 創建運行空間
  • 將變量傳遞給它
  • 將預先編寫的函數傳遞給它
  • GUI/WPF 出現,我什至可以操縱它上面的元素(例如,更改文本塊中的文本)

問題是按鈕之一(BTN_Exit)...我想在點擊時實現這一點,它不僅關閉 GUI,而且關閉它運行的 RunSpace。 我已經到了設法關閉 GUI 甚至調用 Runspace 的關閉序列的地步,但是(我假設)當被調用的函數在 RunSpace 本身中運行時,它只是掛斷了(RunSpace 保持在“關閉”狀態) state) - 顯然 Powershell 無法殺死自己 :) 幸運的是我仍然能夠使用$(Get-Runspace)[-1].Dispose() (手動)從主線程中處理它

我相信我需要連接回創建 GUI 運行空間的主線程並從那里關閉它,但我無法在函數中返回那里。 如果窗口打開,我可以手動執行 close-runspace 函數中的所有 cmdlet 並實現預期目標。

我試過添加$rs.connect($(Get-Runspace).Name -eq "Runspace1") & $RS.disconnect()如果我在“句柄”或實例上嘗試同樣的事情,它會產生相同的結果。 我也無法在按鈕單擊時調用原始函數而不將函數傳遞給 RunSpace。

我如何以編程方式獲得單擊 Button 時它關閉 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()

哦,我忘記了這篇文章! 該解決方案/解決方法,我發現在這個文章。

基本上,本地運行空間也需要傳遞給運行 GUI 的運行空間。 (我做了 :) )然后可以引發在主線程中調用函數/腳本塊等的事件。 此事件觸發器然后執行內務處理並清除/刪除 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