2015-08-12 6 views
8

Ich versuche, ein PowerShell-Add-in in .NET zu erstellen, mit dem ich auf einige Windows 10-Funktionen zugreifen kann. Ich kenne C#, aber PowerShell ist relativ neu für mich.C# -Ereignisse nie in PowerShell 5 behandelt

Ich habe alle meine Funktionsaufrufe und Cmdlets arbeiten. Ich kann die C# -Klassen dazu bringen, Dinge aus PowerShell zu machen. Was mich stolpert, ist Event Raising und Handling, um den C# -Code zurückrufen zu lassen. Ich bin jetzt an drei Abenden dabei.

Beispiele online und hier in SO zeigen immer Timer und Dateisystembeobachter, oder manchmal ein Windows Form. Ich habe noch nie ein Beispiel mit einer Klasse gesehen, die jemand selbst geschrieben hat. Nicht sicher, ob etwas Zucker benötigt wird, um die Ereignisse anzuheben, oder etwas anderes.

Der Hauptunterschied, den ich zwischen meinem Code und anderen gesehen habe, ist, dass ich eine Factory-Klasse habe, die eine Instanz des Objekts zurückgibt, das die Ereignisse auslöst. Ich benutze das, anstatt das neue Objekt aufzurufen. Allerdings erkennt PowerShell den Typ und freut sich, Mitglieder darauf aufzulisten, also habe ich angenommen, dass das nicht das Problem ist.

Ich habe ein einfaches Repro-Cmdlet und Klasse und Skripts erstellt. Hier sind sie:

GetTestObject Cmdlets

using System.Management.Automation; 

namespace PeteBrown.TestFoo 
{ 
    [Cmdlet(VerbsCommon.Get, "TestObject")] 

    public class GetTestObject : PSCmdlet 
    { 
     protected override void ProcessRecord() 
     { 
      var test = new TestObject(); 
      WriteObject(test); 
     } 
    } 
} 

Testobject Klasse

using System; 

namespace PeteBrown.TestFoo 
{ 
    public class TestObject 
    { 
     public string Name { get; set; } 

     public event EventHandler FooEvent; 

     public void CauseFoo() 
     { 
      Console.WriteLine("c#: About to raise foo event."); 
      try 
      { 
       if (FooEvent != null) 
        FooEvent(this, EventArgs.Empty); 
       else 
        Console.WriteLine("c#: no handlers wired up."); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex.ToString()); 
      } 
      Console.WriteLine("c#: Raised foo event. Should be handler executed above this line."); 
     } 


     public void CauseAsyncFoo() 
     { 
      Console.WriteLine("c#: About to raise async foo event."); 
      try 
      { 
       if (FooEvent != null) 
       { 
        // yeah, I know this is silly 
        var result = FooEvent.BeginInvoke(this, EventArgs.Empty, null, null); 
        FooEvent.EndInvoke(result); 
       } 
       else 
        Console.WriteLine("c#: no handlers wired up."); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex.ToString()); 
      } 
      Console.WriteLine("c#: Raised async foo event."); 
     } 

    } 
} 

Und hier sind die Skripten und deren Ausgang

Test-events1.ps1 einfache .NET-Ereignishandler Syntax

Import-Module "D:\U.....\Foo.dll" 

Write-Output "Getting test object -------------------------------------- " 
[PeteBrown.TestFoo.TestObject]$obj = Get-TestObject 

# register for the event 
Write-Output "Registering for .net object event ------------------------ " 
$obj.add_FooEvent({Write-Output "Powershell: Event received"}) 

Write-Output "Calling the CauseFoo method to raise event --------------- " 
$obj.CauseFoo() 

Ausgang mit: (Ereignis wird in C# gefeuert, aber nie in PS behandelt)

Getting test object -------------------------------------- 
Registering for .net object event ------------------------ 
Calling the CauseFoo method to raise event --------------- 
c#: About to raise foo event. 
c#: Raised foo event. Should be handler executed above this line. 

Test-events2.ps1 mit Asynchron/BeginInvoke Syntax im Fall, dass das Problem war

Import-Module "D:\U.....\Foo.dll" 

Write-Output "Getting test object -------------------------------------- " 
[PeteBrown.TestFoo.TestObject]$obj = Get-TestObject 

# register for the event 
Write-Output "Registering for .net object event ------------------------ " 
$obj.add_FooEvent({Write-Output "Powershell: Event received"}) 

Write-Output "Calling the CauseAsyncFoo method to raise event ---------- " 
$obj.CauseAsyncFoo() 

Ausgang: (Ereignis wird in C# gefeuert, aber nie in PS behandelt. Außerdem erhalte ich eine Ausnahme.)

Getting test object -------------------------------------- 
Registering for .net object event ------------------------ 
Calling the CauseAsyncFoo method to raise event ---------- 
c#: About to raise async foo event. 
System.ArgumentException: The object must be a runtime Reflection object. 
    at System.Runtime.Remoting.InternalRemotingServices.GetReflectionCachedData(MethodBase mi) 
    at System.Runtime.Remoting.Messaging.Message.UpdateNames() 
    at System.Runtime.Remoting.Messaging.Message.get_MethodName() 
    at System.Runtime.Remoting.Messaging.MethodCall..ctor(IMessage msg) 
    at System.Runtime.Remoting.Proxies.RemotingProxy.Invoke(Object NotUsed, MessageData& msgData) 
    at System.EventHandler.BeginInvoke(Object sender, EventArgs e, AsyncCallback callback, Object object) 
    at PeteBrown.TestFoo.TestObject.CauseAsyncFoo() in D:\Users\Pete\Documents\GitHub\Windows-10-PowerShell-MIDI\PeteBrow 
n.PowerShellMidi\Test\TestObject.cs:line 37 
c#: Raised async foo event. 

Test-events3.ps1 Verwendung von Register-Object Ansatz. Zusätzlich wurden hier noch einige Diagnoseinformationen zur Ausgabe hinzugefügt.

Import-Module "D:\U......\Foo.dll" 

Write-Output "Getting test object -------------------------------- " 
[PeteBrown.TestFoo.TestObject]$obj = Get-TestObject 

# register for the event 
Write-Output "Registering for .net object event ------------------ " 

$job = Register-ObjectEvent -InputObject $obj -EventName FooEvent -Action { Write-Output "Powershell: Event received" } 

Write-Output "Calling the CauseFoo method to raise event --------- " 
$obj.CauseFoo() 

Write-Output "Job that was created for the event subscription ----" 
Write-Output $job 

# show the event subscribers 
Write-Output "Current event subscribers for this session ---------" 
Get-EventSubscriber 

# this lists the events available 
Write-Output "Events available for TestObject --------------------" 
$obj | Get-Member -Type Event 

Ausgang: (Ereignis wird in C# gefeuert, aber nie in PS behandelt)

Getting test object -------------------------------- 
Registering for .net object event ------------------ 
Calling the CauseFoo method to raise event --------- 
c#: About to raise foo event. 
c#: Raised foo event. Should be handler executed above this line. 
Job that was created for the event subscription ---- 

Id  Name   PSJobTypeName State   HasMoreData  Location    Command 
--  ----   ------------- -----   -----------  --------    ------- 
1  2626de85-523...     Running  True         Write-Output "Powersh... 
Current event subscribers for this session --------- 

SubscriptionId : 1 
SourceObject  : PeteBrown.TestFoo.TestObject 
EventName  : FooEvent 
SourceIdentifier : 2626de85-5231-44a0-8f2c-c2a900a4433b 
Action   : System.Management.Automation.PSEventJob 
HandlerDelegate : 
SupportEvent  : False 
ForwardEvent  : False 

Events available for TestObject -------------------- 

TypeName : PeteBrown.TestFoo.TestObject 
Name  : FooEvent 
MemberType : Event 
Definition : System.EventHandler FooEvent(System.Object, System.EventArgs) 

In keinem Fall bekomme ich tatsächlich die Event-Handler-Aktion feuerte. Ich habe auch versucht, eine Variable zu verwenden, um die Aktion zu halten, aber das scheint auf die gleiche Weise behandelt zu werden. Der Console.Writeline-Code in C# dient nur zur Fehlersuche. Und ich habe den vollständigen Pfad für die DLL in den eingefügten Skripten entfernt, um sie einfacher zu lesen. Die DLL lädt gut.

Verwenden von PowerShell 5 unter Windows 10 pro 64 Bit mit .NET Framework 4.6 (CLR 4). VS 2015 RTM.

Name       Value 
----       ----- 
PSVersion      5.0.10240.16384 
WSManStackVersion    3.0 
SerializationVersion   1.1.0.1 
CLRVersion      4.0.30319.42000 
BuildVersion     10.0.10240.16384 
PSCompatibleVersions   {1.0, 2.0, 3.0, 4.0...} 
PSRemotingProtocolVersion  2.3 

Irgendwelche Vorschläge für dieses PowerShell noob?

+0

Ich weiß nicht, ob das das Problem ist, aber die Art, wie Sie das Ereignis aufrufen, ist nicht Thread-sicher. 'if (FooEvent! = null) FooEvent (this, EventArgs.Empty);' sollte eine lokale Variable verwenden, um den 'FooEvent'-Wert zuerst zu speichern, dann überprüfe das auf null und rufe es auf. Wie in, 'var foo = FooEvent; if (foo! = null) foo (dies, EventArgs.Empty); '. Oder in C# 6, 'Foo? .Invoke (dies, EventArgs.Empty);' http://codeblog.jonskeet.uk/2015/01/30/clean-event-handlers-invocation-with-c-6/ –

+0

Angesichts des Async-Fehlers, den Sie sehen, vermute ich, dass Sie mit Remoting- und Remoting-Proxies zu kämpfen haben. Vielleicht möchten Sie auf [Making Objekte Remotable] (https://msdn.microsoft.com/en-us/library/vstudio/wcf3swha (v = vs.100) .aspx), insbesondere das Bit auf [Remoting] und Ereignisse] (https://msdn.microsoft.com/en-us/library/vstudio/ms172343 (v = vs.100) .aspx) –

+0

Welche Ergebnisse von 'Receive-Job $ job'? – PetSerAl

Antwort

3

Write-Output zeigt kein Objekt auf der Konsole an, es sendet Objekt an den nächsten Befehl in der Pipeline. Wenn Sie sofort Text auf der Konsole anzeigen möchten, müssen Sie das Cmdlet Write-Host oder Out-Host verwenden.

"Benutze niemals Write-Host, weil böse!"

Ich würde es so sagen: "Never Write-Host auf Zwischendaten verwenden weil das Böse!". Es ist OK, eine Fortschrittsbenachrichtigung mit Write-Host anzuzeigen (obwohl Sie statt dessen Write-Progress verwenden können) oder es zum Anzeigen des Endergebnisses verwenden, aber Write-Host nichts an den nächsten Befehl in der Pipeline senden, sodass Sie Daten nicht weiter verarbeiten können.

Warum schreiben alle anderen Write-Output-Befehle auf die Konsole?

Wenn ein Objekt das ultimative Ende der Pipeline erreicht, muss PowerShell etwas dagegen tun. Standardmäßig es zeigt sie auf Host (Konsole in diesem Fall), obwohl man dies außer Kraft setzen kann:

New-Alias Out-Default Set-Clipboard # to copy anything into clipboard 
New-Alias Out-Default Out-GridView # to use grid view by default 

Powershell keine Unterstützung von Skriptblock Aufruf in einem beliebigen Thread, also für asynchrone Ereignisse Sie haben Register-ObjectEvent zu verwenden, PowerShell kann damit umgehen.

+0

Perfekt, danke. Die Posts, die ich gesehen habe, sagten wirklich, dass man außer dem Anzeigen von farbigem Text NIEMALS "write-host" benutzen sollte, weil Gründe und Böses. Ich hätte es realisieren müssen. Das habe ich letztendlich aufgebaut: https://github.com/Psychlist1972/Windows-10-PowerShell-MIDI Jetzt mit farbigem Text. :) – Pete

Verwandte Themen