MshLog.GetLogContext throws InvalidCastException (crashes host) when InvocationInfo.MyCommand is a RemoteCommandInfo with CommandType Application/ExternalScript
还没有人认领这个 Issue。
评估
- 难度
- 2/5
- 预计耗时
- 1-3 小时
- 新手友好度
- 78/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 冷清
- 技术栈
- csharp
- 领域
- cli, observability
调研方向
从 src/System.Management.Automation/logging/MshLog.cs 的第 826-840 行附近开始,跟踪通过 MshCommandRuntime.ManageException 的运行状况日志记录路径。复现所提供的 RemoteCommandInfo 形状,然后验证 Application 和 ExternalScript 命令类型不再导致日志记录引发异常并终止主机。
由索引模型根据 Issue 内容生成。
描述
Summary
MshLog.GetLogContext unconditionally down-casts InvocationInfo.MyCommand to ApplicationInfo / ExternalScriptInfo based solely on its CommandType. A RemoteCommandInfo (the type the remoting deserializer produces via InvocationInfo.FromPSObjectForRemoting) can report CommandType.Application or CommandType.ExternalScript but is not an ApplicationInfo/ExternalScriptInfo, so the cast throws InvalidCastException.
Because this runs while a terminating error is being logged (MshCommandRuntime.ManageException → MshLog.LogCommandHealthEvent → GetLogContext) on the pipeline worker thread (LocalPipeline.InvokeThreadProc), and that thread has no catch-all, the exception is unhandled and terminates the entire process.
The offending code
if (invocationInfo.MyCommand != null)
{
logContext.CommandName = invocationInfo.MyCommand.Name;
logContext.CommandType = invocationInfo.MyCommand.CommandType.ToString();
switch (invocationInfo.MyCommand.CommandType)
{
case CommandTypes.Application:
logContext.CommandPath = ((ApplicationInfo)invocationInfo.MyCommand).Path; // <-- throws for RemoteCommandInfo
break;
case CommandTypes.ExternalScript:
logContext.CommandPath = ((ExternalScriptInfo)invocationInfo.MyCommand).Path; // <-- same
break;
}
}
RemoteCommandInfo derives directly from CommandInfo and is constructed by InvocationInfo.FromPSObjectForRemoting, copying CommandType verbatim from the serialized CommandInfo_CommandType:
internal static RemoteCommandInfo FromPSObjectForRemoting(PSObject psObject)
{
...
CommandTypes type = RemotingDecoder.GetPropertyValue<CommandTypes>(psObject, "CommandInfo_CommandType");
string name = RemotingDecoder.GetPropertyValue<string>(psObject, "CommandInfo_Name");
commandInfo = new RemoteCommandInfo(name, type);
...
}
So whenever an error record is rehydrated from a remote/serialized source (PowerShell remoting, Invoke-Command, or any host that uses the remoting serialization to surface errors) and the original failing command was a native application or an external script, MyCommand is a RemoteCommandInfo reporting that CommandType — and the cast is invalid.
Steps to reproduce
Minimal, deterministic reproduction of the invalid cast (this synthesizes exactly the object InvocationInfo.FromPSObjectForRemoting builds when deserializing a remote error for a native command):
using System;
using System.Management.Automation;
using System.Reflection;
var sma = typeof(PSObject).Assembly;
var remoteCommandInfoType = sma.GetType("System.Management.Automation.RemoteCommandInfo")!;
// Same shape the remoting deserializer produces: CommandType is copied from the wire.
var remoteCommandInfo = (CommandInfo)Activator.CreateInstance(
remoteCommandInfoType,
BindingFlags.Instance | BindingFlags.NonPublic, binder: null,
args: new object[] { "some-native-tool.exe", CommandTypes.Application }, culture: null)!;
Console.WriteLine(remoteCommandInfo.CommandType); // Application
Console.WriteLine(remoteCommandInfo is ApplicationInfo); // False
// This is precisely what MshLog.GetLogContext does for CommandTypes.Application:
var path = ((ApplicationInfo)(object)remoteCommandInfo).Path; // System.InvalidCastException
In the wild this is reached from a real terminating error being logged; the crash observed in a PowerShell-SDK host was:
Unhandled exception. System.InvalidCastException: Unable to cast object of type
'System.Management.Automation.RemoteCommandInfo' to type 'System.Management.Automation.ApplicationInfo'.
at System.Management.Automation.MshLog.GetLogContext(ExecutionContext executionContext, InvocationInfo invocationInfo, Severity severity)
at System.Management.Automation.MshLog.LogCommandHealthEvent(ExecutionContext executionContext, Exception exception, Severity severity)
at System.Management.Automation.MshCommandRuntime.ManageException(Exception e)
at System.Management.Automation.CommandProcessorBase.ManageInvocationException(Exception e)
at System.Management.Automation.CommandProcessorBase.Complete()
at System.Management.Automation.Internal.PipelineProcessor.DoCompleteCore(CommandProcessorBase commandRequestingUpstreamCommandsToStop)
at System.Management.Automation.Internal.PipelineProcessor.SynchronousExecuteEnumerate(Object input)
at System.Management.Automation.Runspaces.LocalPipeline.InvokeHelper()
at System.Management.Automation.Runspaces.LocalPipeline.InvokeThreadProc()
at System.Management.Automation.Runspaces.PipelineThread.WorkerProc()
at System.Threading.Thread.StartHelper.Callback(Object state)
LocalPipeline.InvokeThreadProc only catches PipelineStoppedException, RuntimeException, ScriptCallDepthException, SecurityException, and HaltCommandException; an InvalidCastException escapes and the process dies.
Expected behavior
Building a log context for a health event must never throw / crash the host. A command whose CommandType is Application/ExternalScript but which is not the corresponding concrete type (e.g. a RemoteCommandInfo) should be handled gracefully (skip CommandPath, or use it if available), and logging should never be able to terminate the process.
Actual behavior
InvalidCastException is thrown from GetLogContext, propagates through ManageException on the pipeline worker thread, is unhandled, and terminates the process.
Proposed fix
Use pattern matching instead of unchecked casts, e.g.:
switch (invocationInfo.MyCommand)
{
case ApplicationInfo applicationInfo:
logContext.CommandPath = applicationInfo.Path;
break;
case ExternalScriptInfo externalScriptInfo:
logContext.CommandPath = externalScriptInfo.Path;
break;
}
(Optionally, the health-logging entry points could also be defensive so that a logging failure can never crash the host.)
Environment data
Reproduced against Microsoft.PowerShell.SDK / System.Management.Automation 7.5.4 (hosted in-process on Linux). The offending cast is unchanged on master (6633089), so it affects current builds as well.
- 主要语言
- C#
- 星标
- 55.5k
- 派生
- 8.5k
- 平均合并
- 1 天 17 小时
- 30 天内合并 PR
- 97
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
PowerShell/PowerShell 的其他 Issue
-
Issue-Enhancement Needs-Triage
难度 1/5 1 小时以内 新手友好度 86/100
PowerShell/PowerShell#28061 · 2 个 reaction ·
-
难度 1/5 1 小时以内 新手友好度 90/100
PowerShell/PowerShell#28037 ·
-
Needs-Triage
难度 2/5 1-3 小时 新手友好度 74/100
PowerShell/PowerShell#27973 · 1 条评论 ·
-
Issue-Bug Up-for-Grabs WG-Cmdlets WG-Reviewed
难度 2/5 1-3 小时 新手友好度 72/100
PowerShell/PowerShell#27670 · 3 条评论 ·
-
Issue-Bug Up-for-Grabs WG-Reviewed
难度 2/5 1-3 小时 新手友好度 84/100
PowerShell/PowerShell#27656 · 2 条评论 ·
查看 PowerShell/PowerShell 的全部 Issue
相似的 Issue
-
Documentation
难度 2/5 1-3 小时 新手友好度 75/100
cake-build/cake#5024 ·
-
Frontend status/draft TechnicalDebt
难度 2/5 1-2 天 新手友好度 75/100
Altinn/altinn-auth#4143 ·
-
.NET Flaky Test Testing Tests
难度 2/5 1-3 小时 新手友好度 84/100
getsentry/sentry-dotnet#5617 · 1 条评论 ·
-
:watch: Not Triaged dotnet-fsharp/svc
难度 1/5 1 小时以内 新手友好度 90/100
-
Client customer-reported needs-team-attention question Service Attention WebPubSub
难度 2/5 1-3 小时 新手友好度 76/100
Azure/azure-sdk-for-net#63292 · 3 条评论 · 1 个 reaction ·