Powershell多运行空间事件传递 [英] powershell multi-runspace event passing

查看:84
本文介绍了Powershell多运行空间事件传递的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在寻找一种在不同运行空间之间传递事件的方法,但尚未找到任何方法.下面的代码片段创建了一个背景运行空间,该运行空间显示了一个只有一个按钮的小窗口. OnClick它将发布主运行空间应接收的事件:

I've been searching for a way to pass events between different runspaces and yet have not found any. The following snipped creates a background runspace, which shows a small window with only one button. OnClick it shall post an event that the main runspace should receive:

$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}

但这不起作用.我将最后几行更改为此:

But that doesn't work. I changed the last lines to this:

$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" -Event,但是我无法回忆或重现...

... which also didn't work. I guess as I cannot call functions from the parent-RS inside the Child-RS. Weirdly enough I had some situations where Get-Event returned some "TestClicked"-Events, but I cannot recall nor reproduce...

显然,以上方法可以某种方式工作-我只是再次遇到了我的问题,这是与一些功能结合在一起的.大多数人都知道Scripting Guy在Powershell-BLog上发布的Show-Control函数.当我宁愿显示整个GUI而不是单个控件时,我对此进行了修改:

obviously the above works some way - I just ran into my problem again, which is in conjunction with a few functions. Most people know the Show-Control function published by Scripting Guy on the Powershell-BLog. As I rather show a whole GUI instead of single Controls, I modified it like this:

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)
            }
        }
    }
}

很抱歉用德语发表评论.

I'm sorry for commenting in German.

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

Now using this function (which also uses the techniques to send "GUIloaded" and "WindowClosed" events) with "GuI-events" in the function call, it seems to not be possible to send events from within the gui-events. Like this:

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设置的动作.

It should be noted that $ui is a global SyncHasTable. Weirdly enough those "$ParentHost.UI.WriteLine()" calls work and produce output on the parent console. The "GenerateEvent"-calls seem to not work at all. Neither Get-Event shows any events, nor do actions get triggered that were set-up via Register-EngineEvent.

对此有何想法?

推荐答案

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

I was able to receive events on the parent runspace using the following code:

$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!")}

这篇关于Powershell多运行空间事件传递的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆