You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PowerShell v5.1中配置ConPTY?代码报错求助

用ConPTY在PowerShell 5.1中向其他控制台窗口发送输出(无WinForms/WPF/IPC)

报错根源

你遇到的System.RuntimeType无法编组的错误,是因为调用[Marshal]::SizeOf时传入了错误参数——大概率是误传了类型的运行时内部实例(RuntimeType),而非标记了[StructLayout]的结构体类型本身。正确调用需要传入结构体的类型对象或其实例。

修复后的完整实现

以下是适配PowerShell 5.1的轻量实现,无需额外依赖,普通用户权限即可运行:

1. 定义Interop结构体与系统函数

Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;

public class ConPtyHelper
{
    [StructLayout(LayoutKind.Sequential)]
    public struct COORD
    {
        public short X;
        public short Y;
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr GetStdHandle(int nStdHandle);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool WriteConsole(
        IntPtr hConsoleOutput,
        string lpBuffer,
        int nNumberOfCharsToWrite,
        out int lpNumberOfCharsWritten,
        IntPtr lpReserved);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool DuplicateHandle(
        IntPtr hSourceProcessHandle,
        IntPtr hSourceHandle,
        IntPtr hTargetProcessHandle,
        out IntPtr lpTargetHandle,
        uint dwDesiredAccess,
        bool bInheritHandle,
        uint dwOptions);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool GetConsoleProcessList(out uint[] lpdwProcessList, uint dwProcessCount);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool CloseHandle(IntPtr hObject);
}
"@

2. 获取目标控制台窗口的句柄与进程ID

# 获取当前用户所有控制台进程ID
$processListBuffer = New-Object uint[] 1024
$consoleProcessCount = [ConPtyHelper]::GetConsoleProcessList([ref]$processListBuffer, 1024)
$consoleProcesses = $processListBuffer[0..($consoleProcessCount-1)] | ForEach-Object {
    Get-Process -Id $_ -ErrorAction SilentlyContinue
}

# 排除当前PowerShell窗口,选第一个目标控制台
$currentConsolePid = [ConPtyHelper]::GetConsoleProcessList([ref]$null, 0)
$targetProcess = $consoleProcesses | Where-Object { $_.Id -ne $currentConsolePid } | Select-Object -First 1

if (-not $targetProcess) {
    Write-Error "未找到其他控制台窗口,请先打开一个cmd/PowerShell窗口"
    exit 1
}

# 获取目标窗口句柄
$targetWindowHandle = [ConPtyHelper]::FindWindow("ConsoleWindowClass", $targetProcess.MainWindowTitle)
if ($targetWindowHandle -eq [IntPtr]::Zero) {
    Write-Error "无法找到目标窗口句柄"
    exit 1
}

3. 复制目标窗口输出句柄并发送内容

# 打开目标进程(普通用户权限仅需查询+句柄复制权限)
$PROCESS_QUERY_INFO = 0x0400
$PROCESS_DUP_HANDLE = 0x0008
$targetProcessHandle = [ConPtyHelper]::OpenProcess(
    $PROCESS_QUERY_INFO -bor $PROCESS_DUP_HANDLE,
    $false,
    $targetProcess.Id
)

if ($targetProcessHandle -eq [IntPtr]::Zero) {
    Write-Error "无法打开目标进程,错误代码: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
    exit 1
}

# 获取目标进程的标准输出句柄并复制到当前进程
$STDOUT_HANDLE = -11
$sourceStdOut = [ConPtyHelper]::GetStdHandle($STDOUT_HANDLE)
$targetStdOut = [IntPtr]::Zero
$duplicateSuccess = [ConPtyHelper]::DuplicateHandle(
    $targetProcessHandle,
    $sourceStdOut,
    [System.Diagnostics.Process]::GetCurrentProcess().Handle,
    [ref]$targetStdOut,
    0,
    $false,
    2 # DUPLICATE_SAME_ACCESS
)

if (-not $duplicateSuccess) {
    Write-Error "复制输出句柄失败,错误代码: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
    [ConPtyHelper]::CloseHandle($targetProcessHandle)
    exit 1
}

# 修复SizeOf调用示例(正确传入结构体类型)
$coordSize = [System.Runtime.InteropServices.Marshal]::SizeOf([ConPtyHelper+COORD])
Write-Host "COORD结构体大小验证: $coordSize"

# 向目标窗口发送内容
$outputContent = "来自另一个PowerShell窗口的消息:`r`nHello, Target Console!`r`n"
$charsWritten = 0
$writeSuccess = [ConPtyHelper]::WriteConsole(
    $targetStdOut,
    $outputContent,
    $outputContent.Length,
    [ref]$charsWritten,
    [IntPtr]::Zero
)

if ($writeSuccess) {
    Write-Host "成功写入 $charsWritten 个字符到目标窗口"
} else {
    Write-Error "写入失败,错误代码: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
}

# 清理资源
[ConPtyHelper]::CloseHandle($targetStdOut)
[ConPtyHelper]::CloseHandle($targetProcessHandle)

关键修复与说明

  • 所有Interop结构体均标记[StructLayout(LayoutKind.Sequential)],确保CLR能正确编组到非托管内存
  • 调用[Marshal]::SizeOf时传入结构体类型(如[ConPtyHelper+COORD]),而非类型的RuntimeType实例
  • 使用普通用户权限允许的进程访问权限,无需管理员身份即可操作同用户下的控制台窗口
  • 通过句柄复制复用目标窗口的标准输出,避免复杂的ConPTY创建流程,保持轻量高效

内容的提问来源于stack exchange,提问作者Dennis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 23:32:07