我正在尝试在 .NET 中创建一个 PowerShell 加载项,它将允许我访问某些 Windows 10 功能。我知道 C#,但 PowerShell 对我来说比较陌生。
我的所有函数调用和 cmdlet 都在工作。我可以让 C# 类通过 PowerShell 执行操作。让我绊倒的是事件引发和处理让 C# 代码回调。我已经在这上面待了三个晚上了。
在线示例和 SO 中的示例始终显示计时器和文件系统观察器,或者有时显示 Windows 窗体。我从未见过使用某人自己编写的类的示例。不确定引发事件是否需要一些糖分或其他东西。
我发现我的代码与其他代码之间的主要区别是我有一个工厂类,它返回引发事件的对象的实例。我使用它而不是调用新对象。但是,PowerShell 可以识别该类型并乐于在其上列出成员,因此我认为这不是问题所在。
我已经创建了一个简单的重现 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.");
}
}
}
这是脚本及其输出
测试事件1.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.
测试事件2.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.
测试事件3.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)
在任何情况下,我实际上都没有触发事件处理程序操作。我也试过使用变量来保存 Action ,但似乎以同样的方式对待。 C# 中的 Console.Writeline 代码只是为了帮助调试。我在粘贴的脚本中删除了 DLL 的完整路径,只是为了让它们更易于阅读。 DLL 加载正常。
在装有 .NET Framework 4.6 (CLR 4) 的 Windows 10 专业版 64 位上使用 PowerShell 5。对比 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 菜鸟有什么建议吗?
最佳答案
Write-Output
不在控制台上显示对象,它将对象发送到管道中的下一个命令。如果您想立即在控制台上显示一些文本,则必须使用 Write-Host
或 Out-Host
cmdlet。
"Never use Write-Host because evil!"
我会这样说:“永远不要使用 Write-Host 中间数据,因为这是邪恶的!”。用 Write-Host
显示一些进度通知是可以的(尽管你可以考虑使用 Write-Progress
代替)或者用它来显示最终结果,但是 Write-Host
不向管道中的下一个命令发送任何内容,因此您无法进一步处理数据。
Why do all the other Write-Output commands write to the console?
当某些对象到达管道的最终末端时,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 不支持在某些任意线程中调用脚本 block ,因此对于异步事件,您必须使用 Register-ObjectEvent
,以便 PowerShell 可以处理它们。
关于PowerShell 5 中从未处理过的 C# 事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31955986/