Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

MshLog.GetLogContext throws InvalidCastException (crashes host) when InvocationInfo.MyCommand is a RemoteCommandInfo with CommandType Application/ExternalScript

未关闭 适合新手
#27,720 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 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.ManageExceptionMshLog.LogCommandHealthEventGetLogContext) 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

https://github.com/PowerShell/PowerShell/blob/6633089f88c18574effae4a806eabf0e8345c003/src/System.Management.Automation/logging/MshLog.cs#L826-L840

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

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

PowerShell/PowerShell 的其他 Issue

查看 PowerShell/PowerShell 的全部 Issue

相似的 Issue

更多 C# Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。