【问题标题】:powershell multi-runspace event passingpowershell 多运行空间事件传递
【发布时间】:2013-03-19 14:16:06
【问题描述】:

我一直在寻找一种在不同运行空间之间传递事件的方法,但还没有找到。下面的片段创建了一个后台运行空间,它显示了一个只有一个按钮的小窗口。 OnClick 它将发布主运行空间应接收的事件:

$Global:x = [Hashtable]::Synchronized(@{})
$x.Host = $Host
$Global:rs = [RunspaceFactory]::CreateRunspace()
$rs.ApartmentState,$rs.ThreadOptions = "STA","ReUseThread"
$rs.Open()
$rs.SessionStateProxy.SetVariable("x",$x)
$Global:cmd = [PowerShell]::Create().AddScript(@'
Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase
$x.w = [Windows.Markup.XamlReader]::Parse(@"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MaxWidth="800" WindowStartupLocation="CenterScreen" WindowStyle="None" SizeToContent="WidthAndHeight">
<Button Name="test" Content="Starte Installation"/>
</Window>
"@)
$x.test = $x.w.Content.FindName('test')
$x.test.Add_Click( {New-Event -SourceIdentifier "TestClicked" -MessageData "test event"} )
$x.w.ShowDialog()
'@)
$cmd.Runspace = $rs
$null = $cmd.BeginInvoke()
while(!($x.ContainsKey("test"))) {Sleep -Milliseconds 500}
Register-EngineEvent -SourceIdentifier "TestClicked" -Action {$event}

但这不起作用。我把最后几行改成这样:

$x.test.Add_Click( {$x.Host.Runspace.Events.GenerateEvent( "TestClicked", $x.test, $null, "test event") } )
$x.w.ShowDialog()
'@)
$cmd.Runspace = $rs
$null = $cmd.BeginInvoke()
Wait-Event -SourceIdentifier "TestClicked"

...这也不起作用。我想因为我不能从 Child-RS 中的 parent-RS 调用函数。奇怪的是,在某些情况下,Get-Event 返回了一些“TestClicked”事件,但我无法回忆或重现......

编辑:显然上述方法在某种程度上起作用 - 我又遇到了我的问题,它与一些功能结合使用。大多数人都知道 Scripting Guy 在 Powershell-BLog 上发布的 Show-Control 功能。由于我宁愿显示整个 GUI 而不是单个控件,因此我将其修改如下:

Add-Type –assemblyName PresentationFramework,PresentationCore,WindowsBase,"System.Windows.Forms"

<#  Die folgende Funktion zeigt eine GUI an.  Die Informationen über die GUI
    müssen in XAML formuliert sein.  Sie können als String oder als Dateiname
    übergeben werden.
    Die Funktion erlaubt die Übergabe von WindowProperties als Hashtable
    (-> siehe [System.Windows.Window]), von gemeinsamen Objekten in einer syn-
    chronized HashTable und von Ereignissen, die mit den entsprechenden im xaml
    definierten Objekten verbunden werden.
    Der Switch "backgroundrunspace" macht, was sein Name sagt: er öffnet die GUI
    im Hintergrund, sodass das Hauptprogramm weiterlaufen kann.
#>
function Show-Control {
    param(
        [Parameter(Mandatory=$true,ParameterSetName="XamlString",ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [string] $xaml,

        [Parameter(Mandatory=$true,ParameterSetName="XamlFile",ValueFromPipeline=$false,ValueFromPipelineByPropertyName=$true)]
        [string] $xamlFile,

        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [Hashtable] $event,

        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [Hashtable] $windowProperties,

        # If this switch is set, Show-Control will run the control in the background runspace
        [switch] $backgroundRunspace,

        # To share Variables with the background runspace
        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [Hashtable] $sharedVariables
    )
    Begin
    {   # If it's in a background runspace, create a runspace and populate the runspace with Show-Control.
        if ($backgroundRunspace) {
            $newRunspace =[RunspaceFactory]::CreateRunspace()
            $newRunspace.ApartmentState,$newRunspace.ThreadOptions = "STA","ReuseThread"
            $newRunspace.Open()
            $newRunspace.SessionStateProxy.SetVariable("ParentHost",$Host)
            if ($sharedVariables) {
                $newRunspace.SessionStateProxy.SetVariable("sharedVariables",$sharedVariables)
            }
            $selfDefinition = "function Show-Control { $((Get-Command Show-Control).Definition) }"
            $psCmd = [PowerShell]::Create().AddScript($selfDefinition, $false)
            $psCmd.Runspace = $newRunspace
            $null = $psCmd.Invoke()
        } else {
            $window = New-Object Windows.Window
            $window.SizeToContent = "WidthAndHeight"
            # das Fenster in die sharedVariables aufnehmen
            if ($sharedVariables) {
                $sharedVariables.window=$window
            }
            if ($windowProperties) {
                foreach ($kv in $windowProperties.GetEnumerator()) {
                    $window."$($kv.Key)" = $kv.Value
                }
            }
            $visibleElements = @()
            $windowEvents = @()
        }
    }
    Process
    {   
        if ($backgroundRunspace) { # Invoke the command, using each parameter from commandlineparameters 
            $psCmd  = [Powershell]::Create().AddCommand("Show-Control",$false)
            $null = $psBoundParameters.Remove("BackgroundRunspace")
            $null = $psCmd.AddParameters($psBoundParameters)
<#            foreach ($namedArg in $psBoundParameters.GetEnumerator()) {
                $null = $psCmd.AddParameter($namedArg.Key, $namedArg.Value)                                                    
            }#>
            $psCmd.Runspace = $newRunspace
            $null = $psCmd.BeginInvoke()
        } else {
            # falls eine xaml-datei, dann diese in den xaml-string laden
            if($PSCmdlet.ParameterSetName -eq "xamlFile") {
                $xaml = [string](Get-Content -Encoding UTF8 -ReadCount 0 -Path $xamlFile)
            }
            # XAML parsen und so zu Objekten machen
            $window.Content=([system.windows.markup.xamlreader]::parse($xaml))
            # wir merken uns, ob wir ein Loaded-Event verknüpft haben
            $guiloaded_notadded = $true
            # event-hashtable parsen
            if($event) {
                foreach ($singleEvent in $event.GetEnumerator()) {
                    if ($singleEvent.Key.Contains(".")) {
                        # auseinander nehmen von Objektname und Eventname
                        $targetName = $singleEvent.Key.Split(".")[0].Trim()
                        $eventName = $singleEvent.Key.Split(".")[1].Trim()
                        if ($singleEvent.Key -like "Window.*") {
                            $target = $window
                        } else {
                            $target = $window.Content.FindName($targetName)                   
                        }                       
                    } else {    # kein Objektname -> das Fenster selbst ist das Objekt...
                        $target = $window
                        $eventName = $singleEvent.Key
                    }
                    # Prüfe, ob dieses Objekt auch dieses Event unterstützt, wenn ja: Skriptblock mit dem Event verheiraten
                    if( Get-Member -InputObject $target -MemberType Event -Name $eventName ) {
                        $eventMethod = $target."add_$eventName"
                        if( ($targetName -eq "Window") -and ($eventName -eq "Loaded") -and ($ParentHost)) {
                            $eventScript = [ScriptBlock]::Create( $singleEvent.Value.ToString() + "`n`$null = `$ParentHost.Runspace.Events.GenerateEvent('GUIloaded',$null,$null,$null)" )
                            $eventMethod.Invoke( $ExecutionContext.InvokeCommand.NewScriptBlock($eventScript) )
                            $guiloaded_notadded = $false
                        } else {
                            $eventMethod.Invoke( $ExecutionContext.InvokeCommand.NewScriptBlock($singleEvent.Value) )
                        }
                    }
                }
            }
            # wenn background (können wir hier nur durch Abfragen von "ParentHost" prüfen) und kein "Loaded" event,
            # dann das GUIloaded-event mit dem window.loaded event senden.
            if(($guiloaded_notadded) -and ($ParentHost)) {
                $window.add_Loaded( { 
                    $null = $ParentHost.Runspace.Events.GenerateEvent('GUIloaded',$null,$null,$null)
                } )
            }
            # benannte xaml-Objekte in die sharedVariables bringen...
            if($sharedVariables) {
                $match = [regex]::Matches($xaml,' [x]?[:]?Name="(\w+)"')
                foreach ($m in $match)
                {
                    $name = [string]($m.Groups[1].Value)
                    $sharedVariables.Add($name,$window.Content.FindName($name))
                }
            }
        }
    }
    End
    {
        if ($backgroundRunspace) {
            $newRunspace
        } else {
            $null = $window.ShowDialog()
            $window.Tag
            if($ParentHost) {
                $null = $ParentHost.Runspace.Events.GenerateEvent('WindowClosed',$null,$null,$window.Tag)
            }
        }
    }
}

很抱歉用德语发表评论。

现在在函数调用中使用带有“GuI-events”的此函数(也使用发送“GUIloaded”和“WindowClosed”事件的技术),似乎无法从 gui-events 中发送事件.像这样:

Show-Control -xamlfile ($PSScriptRoot+"\WimMounter.xaml") -backgroundRunspace -sharedVariables $ui -event @{
    "Loaded" = {
        $Global:fdlg = New-Object System.Windows.Forms.OpenFileDialog
        $fdlg.CheckFileExists = $true
        $fdlg.Filter = "WIM-Image Files|*.wim"
        $fdlg.Title = "Bitte WIM-Datei auswählen"

        $Global:ddlg = New-Object System.Windows.Forms.FolderBrowserDialog
        $ddlg.Description = "Bitte Verzeichnis zum Mounten des Images auswählen"
        $ui.fn = ""
        $ui.in = ""
        $ui.md = ""
    }
    "selectFile.Click" = {
        if($Global:fdlg.ShowDialog() -eq "OK") {
            $sharedVariables.ImageFile.Text = $fdlg.FileName.Trim()
            $sharedVariables.pl.Content = ("Ausgewählt: `""+$fdlg.FileName.Trim()+"`" - wird untersucht...")
            $sharedVariables.pb.IsIndeterminate = $true
            $sharedVariables.ImageName.Items.Clear()
            $ParentHost.UI.WriteLine("gleich gibbs 'ImageSelected'")
            $ParentHost.Runspace.Events.GenerateEvent("ImageSelected",$null,$null,($fdlg.FileName.Trim()))
        }
    }
}

需要注意的是,$ui 是一个全局的 SyncHasTable。奇怪的是,那些“$ParentHost.UI.WriteLine()”调用工作并在父控制台上产生输出。 “GenerateEvent”调用似乎根本不起作用。 Get-Event 既不显示任何事件,也不会触发通过 Register-EngineEvent 设置的操作。

对此有什么想法吗?

【问题讨论】:

    标签: events powershell inter-process-communicat runspace


    【解决方案1】:

    我能够使用以下代码在父运行空间上接收事件:

    $Global:x = [Hashtable]::Synchronized(@{})
    $x.Host = $Host
    $rs = [RunspaceFactory]::CreateRunspace()
    $rs.ApartmentState,$rs.ThreadOptions = "STA","ReUseThread"
    $rs.Open()
    $rs.SessionStateProxy.SetVariable("x",$x)
    $cmd = [PowerShell]::Create().AddScript({
    Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase
    $x.w = [Windows.Markup.XamlReader]::Parse(@"
    <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    MaxWidth="800" WindowStartupLocation="CenterScreen" WindowStyle="None" SizeToContent="WidthAndHeight">
    <Button Name="test" Content="Starte Installation"/>
    </Window>
    "@)
    $x.test = $x.w.FindName('test')
    
    $x.test.Add_Click({
        $x.Host.Runspace.Events.GenerateEvent( "TestClicked", $x.test, $null, "test event") 
    } )  
    
    $x.w.ShowDialog()
    })
    $cmd.Runspace = $rs
    $handle = $cmd.BeginInvoke()
    Register-EngineEvent -SourceIdentifier "TestClicked" -Action {$Global:x.host.UI.Write("Event Happened!")}
    

    【讨论】:

    • 奇怪的是,我已经尝试过这个,但结果几乎无法重现。以某种方式将您的剪辑复制并粘贴到 ISE 并运行它运行良好。我会用我当前的项目再试一次(它在 WindowsPE 中;)
    • 顺便说一句。它真的很好用,即使在 Windows PE 中也是如此 - 非常感谢 Boe!
    • 哇。这很漂亮。谢谢。
    【解决方案2】:

    如果有帮助,这里有一篇关于使用同步哈希表在运行空间之间传递数据的文章:

    http://learn-powershell.net/2012/10/14/powershell-and-wpf-writing-data-to-a-ui-from-a-different-runspace/

    【讨论】:

    • 谢谢,它没有帮助。如果您阅读了我的帖子,您会看到我已经使用了 SyncHashTable(第一行代码)。这不是交换数据,而是交换事件而无需主动轮询。不过还是谢谢你。
    猜你喜欢
    • 2018-07-08
    • 2010-11-14
    • 2015-11-17
    • 1970-01-01
    • 2019-06-02
    • 2012-07-29
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    相关资源
    最近更新 更多