如何在PowerShell中使用SetConsoleMode更改控制台输出模式?

10
我正在尝试使用Windows API和SetConsoleMode调用更改Windows控制台模式以进行输出CONOUT$)。因此,我修改了一个基于ConinMode.ps1的PowerShell脚本(适用于输入)来完成此操作。使用ConoutMode.exe和我的脚本都可以正常进行阅读,并返回:
# .\ConoutMode.exe
mode: 0x3
ENABLE_PROCESSED_OUTPUT            0x0001 ON
ENABLE_WRAP_AT_EOL_OUTPUT          0x0002 ON
ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 off   <====
DISABLE_NEWLINE_AUTO_RETURN        0x0008 off
ENABLE_LVB_GRID_WORLDWIDE          0x0010 off

# .\ConoutMode.ps1
OK! GetConsoleWindow handle  :  0x1F06D6
Console Input  Mode (CONIN)  :  0x21f
Console Output Mode (CONOUT) :  0x3

然而,我的脚本和exe在写入模式时都失败了。(可能是因为它认为我的输出句柄没有指向一个控制台?)
在PowerShell中:
# .\ConoutMode.exe set 0x000f
error: SetConsoleMode failed (is stdout a console?)

# .\ConoutMode.ps1 -Mode 7
OK! GetConsoleWindow handle  :  0x1F06D6
old (out) mode: 0x3
SetConsoleMode (out) failed (is stdout a console?)
At C:\cygwin64\home\emix\win_esc_test\ConoutMode.ps1:112 char:9
+         throw "SetConsoleMode (out) failed (is stdout a console?)"
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : OperationStopped: (SetConsoleMode ...out a console?):String) [], RuntimeException
+ FullyQualifiedErrorId : SetConsoleMode (out) failed (is stdout a console?)

这是完整的ConoutMode.ps1脚本:
{{以下为脚本内容,请自行翻译。}}
param (
    [int] $Mode
)

$signature = @'
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr GetConsoleWindow();

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

[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);

public const int STD_INPUT_HANDLE  = -10;
public const int STD_OUTPUT_HANDLE = -11;

public const int  ENABLE_PROCESSED_OUTPUT            = 0x0001;
public const int  ENABLE_WRAP_AT_EOL_OUTPUT          = 0x0002;
public const int  ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
public const int  DISABLE_NEWLINE_AUTO_RETURN        = 0x0008;
public const int  ENABLE_LVB_GRID_WORLDWIDE          = 0x0010;
'@
#----------------------------------------------------------
$WinAPI = Add-Type -MemberDefinition $signature -Name WinAPI -Namespace ConoutMode -PassThru
#$WinAPI = Add-Type -MemberDefinition $signature -Name WinAPI -Namespace IdentifyConsoleWindow -PassThru

$hwnd = $WinAPI::GetConsoleWindow()
if ($hwnd -eq 0) {
    throw "Error: GetConsoleWindow returned NULL."
} 
echo "OK! GetConsoleWindow handle  :  0x$($hwnd.ToString("X"))"

function GetConIn {
    $ret = $WinAPI::GetStdHandle($WinAPI::STD_INPUT_HANDLE)
    if ($ret -eq -1) {
        throw "Error: cannot get stdin handle"
    }
    return $ret
}

function GetConOut {
    $ret = $WinAPI::GetStdHandle($WinAPI::STD_OUTPUT_HANDLE)
    if ($ret -eq -1) {
        throw "Error: cannot get stdout handle"
    }
    return $ret
}

function GetConInMode { #GetCOnsoleMode
    $conin = GetConIn
    $mode = 0
    $ret = $WinAPI::GetConsoleMode($conin, [ref]$mode)
    if ($ret -eq 0) {
        throw "GetConsoleMode (in) failed (is stdin a console?)"
    }
    return $mode
}

function GetConOutMode {
    $conout = GetConOut
    $mode   = 0
    $ret    = $WinAPI::GetConsoleMode($conout, [ref]$mode)
    if ($ret -eq 0) {
        throw "GetConsoleMode (out) failed (is stdout a console?)"
    }
    return $mode
}

function SetConInMode($mode) {
    $conin = GetConIn
    $ret = $WinAPI::SetConsoleMode($conin, $mode)
    if ($ret -eq 0) {
        throw "SetConsoleMode (in) failed (is stdin a console?)"
    }
}

function SetConOutMode($mode) {
    #$conout = GetConOut
    # Different tack:
    $conout = $hwnd
    $ret = $WinAPI::SetConsoleMode($conout, $mode)
    if ($ret -eq 0) {
        throw "SetConsoleMode (out) failed (is stdout a console?)"
    }
}

#----------------------------------------------------------
# MAIN
#----------------------------------------------------------
$oldInMode  = GetConInMode
$oldOutMode = GetConOutMode

$newMode = $oldOutMode
$doit = $false

if ($PSBoundParameters.ContainsKey("Mode")) {
    $newMode = $Mode
    $doit = $true
}

if ($doit) {
    echo "old (out) mode: 0x$($oldOutMode.ToString("x"))"
    SetConOutMode $newMode
    $newMode = GetConOutMode
    echo "new (out) mode: 0x$($newMode.ToString("x"))"
} else {
    echo "Console Input  Mode (CONIN)  :  0x$($oldInMode.ToString("x"))"
    echo "Console Output Mode (CONOUT) :  0x$($oldOutMode.ToString("x"))"
}

最终,我想为控制台启用ENABLE_VIRTUAL_TERMINAL_PROCESSING

我使用的系统是:

---------------------------------------------------------
  PowerShell Version    : 6.1.1
  OS Name               : Microsoft Windows 8.1 (64-bit)
  OS Version            : 6.3.9600  [2018-11-16 00:50:01]
  OS BuildLabEx         : 9600.19179
  OS HAL                : 6.3.9600.18969
  OS Kernel             : 6.3.9600.18217
  OS UBR                : 19182
  -------------------------------------------------------
  with Privilege        : Administrator
  -------------------------------------------------------
  ExecutionPolicy :
        MachinePolicy   : Undefined
        UserPolicy      : Undefined
        Process         : Undefined
        CurrentUser     : Undefined
        LocalMachine    : Bypass

  Console Settings:
      Type              : ConsoleHost
      OutputEncoding    : Unicode (UTF-8)
      Color Capability  : 23
      Registry VT Level : 1
      CodePage (input)  : 437
      CodePage (output) : 437

我还按照其他地方的指示启用了注册表项:HKCU:\Console\VirtualTerminalLevel

问:如何使用PowerShell启用此位/标志?

可能存在以下问题:

  • 我肯定对其工作原理有错误的理解...
  • 我可能没有权限写入控制台?(如何设置?)
  • 我可能没有写入正确的输出缓冲区?(如何找到?)

    请注意,设置一个屏幕缓冲区的输出模式不会影响其他屏幕缓冲区的输出模式。

  • 执行脚本时,可能会创建新的输出缓冲区?(怎么办?)

  • 我可能需要创建一个新缓冲区?
  • 我可能需要创建一个新控制台?

相关问题和链接:


1
在我看来,当调用SetConsoleMode时,你似乎使用了错误类型的句柄。GetConsoleWindow与GetStdHandle。 - Palansen
我尝试了这两种方法,以及许多其他的方法。 - not2qubit
GetLastError报告了什么?另外,请编辑您的问题并删除GetConsoleWindow,使用此函数是不正确的。 - Anders
@Anders 抱歉,我不知道你的意思。最后一个错误就是上面显示的那个。如果有其他方法,我不知道如何读取它。而我只是使用 GetConsoleWindow 来查看是否有其他窗口 像这里 - not2qubit
1
http://www.exploit-monday.com/2016/01/properly-retrieving-win32-api-error.html 或 https://blogs.technet.microsoft.com/heyscriptingguy/2013/06/25/use-powershell-to-interact-with-the-windows-api-part-1/ - Anders
如果我做得对的话,我会得到:System.ComponentModel.Win32Exception (203):系统找不到输入的环境选项。 - not2qubit
2个回答

2
该代码使用“GetStdHandle”获取标准输入或输出句柄。这些句柄可能是控制台句柄,但这取决于进程的创建方式。如果您从控制台启动并且未重定向stdin/stdout,则可能会获得控制台句柄。但这不是保证。
与其请求stdin/stdout,不如直接打开控制台句柄-您可以为“CONIN $”和“CONOUT $”创建文件。 CreateFile文档中有一个完整的控制台部分。
更新:我创建了一个Gist,演示如何使用CreateFile从PowerShell打开CONIN$CONOUT$

CONOUT$ 是正确的答案 https://github.com/PowerShell/PowerShell/blob/2ec7aea2ec18d8729dfd9d57c321b05dc97a6737/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs#L615 - Luiz Felipe

1
我自己已经尝试了几天,并意识到一些事情。显然,PowerShell在启动时启用虚拟终端处理。但是,当它启动可执行文件时,它会再次禁用它,然后在可执行文件退出时重新启用它。这意味着需要虚拟终端处理的任何可执行文件都必须自行启用它,或者通过启用它并将stdin / stdout / stderr传输到终端的包装程序来调用。至于如何编写这样的程序,我不知道。

1
对于“这样的程序”,您可以查看在github上的callf实用程序的实现:https://github.com/andry81/contools/blob/trunk/Utilities/src/callf/help.tpl,https://github.com/andry81/contools/blob/trunk/Utilities/bin/contools或者在sourceforge上:https://sf.net/p/contools/contools/HEAD/tree/trunk/Utilities/src/callf/help.tpl,https://sf.net/p/contools/contools/HEAD/tree/trunk/Utilities/bin/contools。 - Andry
1
正如CodeMonster2000所说,我们需要使用CONOUT$(https://github.com/PowerShell/PowerShell/blob/2ec7aea2ec18d8729dfd9d57c321b05dc97a6737/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs#L615)。但问题是Powershell会将其重置回去(https://github.com/PowerShell/PowerShell/blob/2ec7aea2ec18d8729dfd9d57c321b05dc97a6737/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs#L1094)。 - Luiz Felipe
1
请查看此处的操作方法 https://github.com/microsoft/terminal/tree/main/samples/ConPTY/MiniTerm - Luiz Felipe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接