从未在PowerShell 5中处理过C#事件

时间:2015-08-12 04:23:08

标签: c# .net powershell events

我试图在.NET中创建一个PowerShell加载项,这将允许我访问某些Windows 10功能。我知道C#,但PowerShell对我来说相对较新。

我的所有函数调用和cmdlet都在工作。我可以让C#类从PowerShell做一些事情。让我沮丧的是事件提升和处理让C#代码回调。我现在已经有三个晚上了。

在线和在此处的示例总是显示计时器和文件系统观察者,或者有时是Windows表单。我从未见过使用某人自己编写的课程的例子。不确定在举起活动时是否需要一些糖,或者别的什么。

我的代码和其他代码之间的主要区别在于我有一个工厂类,它返回引发事件的对象实例。我使用它而不是调用new-object。但是,PowerShell会识别该类型并很乐意在其上列出成员,因此我认为这不是问题。

我创建了一个简单的repro cmdlet,类和脚本。他们在这里:

GetTestObject cmdlet

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类

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.");
        }

    }
}

以下是脚本及其输出

测试events1.ps1 使用简单的.NET事件处理程序语法

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

输出:(事件在C#中触发,但从未在PS中处理过)

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.

测试events2.ps1 如果是问题

,请使用async / BeginInvoke语法
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()

输出:(事件在C#中触发,但从未在PS中处理。另外,我得到一个例外。)

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.

测试events3.ps1 使用Register-ObjectEvent方法。此处还为输出添加了一些诊断类型信息。

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

输出:(事件在C#中触发,但从未在PS中处理过)

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)

在任何情况下,我都不会实际触发事件处理程序操作。我也尝试使用变量来保持动作,但这似乎也是以同样的方式对待。 C#中的Console.Writeline代码只是为了帮助调试。我在粘贴的脚本中删除了DLL的完整路径,只是为了使它们更容易阅读。 DLL加载正常。

在Windows 10 Pro 64位上使用PowerShell 5和.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

对此PowerShell noob的任何建议?

1 个答案:

答案 0 :(得分:3)

Write-Output不在控制台上显示对象,它将对象发送到管道中的下一个命令。如果要在控制台上立即显示某些文本,则必须使用Write-HostOut-Host cmdlet。

  

“永远不要使用Write-Host,因为邪恶!”

我会这样说:“永远不要在中间数据上使用Write-Host ,因为邪恶!”。可以使用Write-Host显示一些进度通知(尽管您可以考虑使用Write-Progress代替)或使用它来显示最终结果,但Write-Host不向管道中的下一个命令发送任何内容,所以你不能再进一步处理数据了。

  

为什么所有其他Write-Output命令都写入控制台?

当某个对象到达管道的最终端时,PowerShell必须对此做些什么。默认情况下,它会在主机(本例中为控制台)上显示它,但您可以覆盖它:

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

PowerShell不支持某些任意线程中的脚本块调用,因此对于必须使用Register-ObjectEvent的异步事件,PowerShell可以处理它们。