VB.NET调用PowerShell时如何从错误流获取PSComputerName?
问题
我用VB.NET编写代码,通过Invoke-Command远程检查特定进程(如notepad)是否运行:
- 找到进程时,在
Output_DataAdded_ProcessRunning事件中获取PSComputerName并加入processrunning列表 - 未找到进程时,希望在
ps.Streams.Error.DataAdded事件中获取故障计算机名并加入processnotrunning列表
调试时能看到OriginInfo中的PSComputerName值,但无法直接访问该属性。此前执行Invoke-Command简单脚本、Test-Connection等命令时,可通过currentStreamRecord.TargetObject.ToString()获取计算机名,现在需要知道如何在Error_DataAdded_ProcessRunning方法中获取该计算机名。
原代码如下:
Dim test = "MYPC" Dim processtocheck = "notepad" Using ps As PowerShell = PowerShell.Create() Dim command As New PSCommand() command.AddCommand("Invoke-Command") command.AddParameter("ComputerName", test) command.AddParameter("ScriptBlock", ScriptBlock.Create("Get-Process " & processtocheck)) ps.Commands = command Dim output As New PSDataCollection(Of PSObject)() AddHandler output.DataAdded, AddressOf Output_DataAdded_ProcessRunning AddHandler ps.Streams.Error.DataAdded, AddressOf Error_DataAdded_ProcessRunning Dim results As PSDataCollection(Of PSObject) = Await Task.Run(Function() ps.InvokeAsync(Of PSObject, PSObject)(Nothing, output)) End Using Private Sub Output_DataAdded_ProcessRunning(ByVal sender As Object, ByVal e As DataAddedEventArgs) Dim myp As PSDataCollection(Of PSObject) = DirectCast(sender, PSDataCollection(Of PSObject)) Dim results As Collection(Of PSObject) = myp.ReadAll() For Each result As PSObject In results ' add to list AddIfNotExists(processrunning, result.Properties("PSComputerName").Value) Next result End Sub Private Sub Error_DataAdded_ProcessRunning(ByVal sender As Object, ByVal e As DataAddedEventArgs) ' do something when an error is written to the error stream Dim streamObjectsReceived = TryCast(sender, PSDataCollection(Of ErrorRecord)) Dim currentStreamRecord = streamObjectsReceived(e.Index) ' add to list ' ??? End Sub
解决方案
方法1:通过反射获取OriginInfo中的PSComputerName
ErrorRecord的InvocationInfo.OriginInfo属性包含远程计算机信息,但该属性是内部可见的,无法直接访问。可以用反射读取其中的PSComputerName值:
修改Error_DataAdded_ProcessRunning方法如下:
Private Sub Error_DataAdded_ProcessRunning(ByVal sender As Object, ByVal e As DataAddedEventArgs) Dim streamObjectsReceived = TryCast(sender, PSDataCollection(Of ErrorRecord)) Dim currentStreamRecord = streamObjectsReceived(e.Index) ' 用反射获取OriginInfo的PSComputerName Dim originInfoProperty = currentStreamRecord.InvocationInfo.GetType().GetProperty("OriginInfo", System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance) If originInfoProperty IsNot Nothing Then Dim originInfo = originInfoProperty.GetValue(currentStreamRecord.InvocationInfo) If originInfo IsNot Nothing Then Dim psComputerNameProperty = originInfo.GetType().GetProperty("PSComputerName") If psComputerNameProperty IsNot Nothing Then Dim computerName = psComputerNameProperty.GetValue(originInfo) ' 添加到processnotrunning列表 AddIfNotExists(processnotrunning, computerName) End If End If End If End Sub
方法2:修改脚本块,主动返回包含计算机名的错误信息(更可靠)
避免依赖内部属性,直接在远程脚本块中捕获错误,手动构造包含计算机名的对象返回:
修改Invoke-Command的脚本块部分:
command.AddParameter("ScriptBlock", ScriptBlock.Create($" try {{ Get-Process {processtocheck} }} catch {{ # 返回包含计算机名和错误信息的对象 [PSCustomObject]@{ 'PSComputerName' = $env:COMPUTERNAME 'ErrorMessage' = $_.Exception.Message } }} "))
然后修改错误事件处理(此时错误流中会包含我们自定义的对象,直接读取PSComputerName即可):
Private Sub Error_DataAdded_ProcessRunning(ByVal sender As Object, ByVal e As DataAddedEventArgs) Dim streamObjectsReceived = TryCast(sender, PSDataCollection(Of ErrorRecord)) Dim currentStreamRecord = streamObjectsReceived(e.Index) ' 从TargetObject中获取自定义的PSComputerName Dim errorObj = TryCast(currentStreamRecord.TargetObject, PSObject) If errorObj IsNot Nothing Then Dim computerName = errorObj.Properties("PSComputerName")?.Value If computerName IsNot Nothing Then AddIfNotExists(processnotrunning, computerName) End If End If End Sub
方法2更稳定,因为不依赖PowerShell内部未公开的属性,避免后续版本更新导致代码失效。
内容的提问来源于stack exchange,提问作者Darren Rose
相关产品推荐
相关产品推荐

