如何区分控制台程序是在 Powershell 中打开还是在 Windows Terminal 中打开?

3

我正在编写一个库,旨在使控制台程序中设置颜色、模式等变得更加容易。但是我遇到了Windows终端的问题。例如,我有一个函数:

void WindowsCLI::setUnderlinedFont()
{
    auto consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    config.underlined = true;
    SetConsoleTextAttribute(consoleHandle, getTextAttribute(config));
}

它使用windows.h中的COMMON_LVB_UNDERSCORE属性来使文本带下划线。在PowerShell中,这个函数的结果看起来像这样:enter image description here,而在Windows终端中则是这样的:enter image description here,因此显然在第二种情况下,我的功能没有正常工作。我认为问题在于Windows终端以虚拟终端模式运行。因此,我又为虚拟终端创建了另一个函数:

void WindowsVirtualCLI::setUnderlinedFont()
{
    printf("\x1b[4m");
}

现在这段代码在 Powershell 中无法正常工作:enter image description here,但在 Windows Terminal 中可以正常运行:enter image description here。不过,现在我还有另一个问题:如何区分程序是在 Powershell 还是 Windows Terminal 中运行。我尝试使用了以下函数:

CLI& cli()
{
    DWORD consoleMode;
    auto consoleHandle = GetStdHandle(STD_INPUT_HANDLE);
    GetConsoleMode(consoleHandle, &consoleMode);

    if ((consoleMode & ENABLE_PROCESSED_OUTPUT) && (consoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING))
    {
        return windows::WindowsVirtualCLI::getInstance();
    }
    else
    {
        return windows::WindowsCLI::getInstance();
    }
}

但实际上,PowerShell 和 Windows 终端都启用了 "ENABLE_PROCESSED_OUTPUT" 和 "ENABLE_VIRTUAL_TERMINAL_PROCESSING"。现在,我没有其他想法如何在运行时区分这些终端。你有任何想法吗?
附注: 我已将 "cli()" 方法更改为以下内容:
CLI& cli()
{
    DWORD consoleMode;
    auto consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
    GetConsoleMode(consoleHandle, &consoleMode);
    SetConsoleMode(consoleHandle, consoleMode);

    if ((consoleMode & ENABLE_PROCESSED_OUTPUT) && (consoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING))
    {
        return windows::WindowsVirtualCLI::getInstance();
    }
    else
    {
        return windows::WindowsCLI::getInstance();
    }
}

但是它仍然不能按照我的需求工作。


1
你应该使用 GetStdHandle(STD_OUTPUT_HANDLE) (我不太了解Windows控制台,可能没有任何区别) - Alan Birtles
@Mgetz 我没有调用SetConsoleMode,因为我不想设置任何模式。我只是想看看这些标志是否为真。 - Andrzej Sołtysik
1
然后调用 GetStdHandle(STD_OUTPUT_HANDLE)。你当前正在为 STD_INPUT_HANDLE 调用它,这是没有意义的。 - zett42
@zett42 除非他们使用的是经典终端序列而不是字符串中的VT100。这看起来像是预期的行为。 - Mgetz
PowerShell根本不是终端/控制台。也许你指的是旧的conhost终端? - Anders
显示剩余5条评论
1个回答

2

简单但不完美的解决方案:

Windows Terminal定义了两个应用程序特定的环境变量WT_SESSIONWT_PROFILE_ID,因此您可以测试其中一个变量是否已定义(具有非空值)。

根据这个答案,在C++中使用getenv("WT_SESSION")来检索该变量的值,如果已定义,则应该有效。

在PowerShell本身中,[bool] $env:WT_SESSION返回$true,如果该变量具有非空值。

这种方法并不是万无一失的,因为如果您从运行于Windows Terminal的shell中启动常规控制台窗口(conhost.exe)-例如使用Start-Process cmd.exe - 它将继承WT_SESSION变量,并因此在这些会话中产生错误的结果。


更详细但更健壮的解决方案:

一个强大的解决方案是沿着父进程链向上遍历,从当前进程开始,直到找到与主窗口句柄相关联的第一个进程:

  • 如果该进程的名称是WindowsTerminal,则可以安全地假定该进程在Windows Terminal中运行 - 可能是间接地,在从直接运行于Windows Terminal的shell启动的(定义为控制台子系统)进程中。

  • 否则,可以安全地假定不同的应用程序托管该进程,可能是进程本身(运行于常规控制台窗口中的进程本身拥有该窗口,并具有一个conhost.exe子进程)。

以下是PowerShell实现:

  • PowerShell(Core)7+ 实现:
$runningInWindowsTerminal = 
  if ($IsWindows) {
    $ps = Get-Process -Id $PID
    while ($ps -and 0 -eq [int] $ps.MainWindowHandle) { $ps = $ps.Parent }
    $ps.ProcessName -eq 'WindowsTerminal'
  } else {
    $false
  }
  • Windows PowerShell和跨版本实现:

不幸的是,Windows PowerShell没有为Get-Process输出的 System.Diagnostics.Process 对象装饰一个报告父进程的.Parent属性,这使得解决方案变得复杂,并需要调用Get-CimInstance来检索父信息。

$runningInWindowsTerminal = 
  if ($env:OS -eq 'Windows_NT') {
    $ps = Get-Process -Id $PID
    while ($ps -and 0 -eq [int] $ps.MainWindowHandle) { 
      $ps = Get-Process -ErrorAction Ignore -Id (Get-CimInstance Win32_Process -Filter "ProcessID = $($ps.Id)").ParentProcessId 
    }
    $ps.ProcessName -eq 'WindowsTerminal'
  } else {
    $false
  }

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