在Windows 11中,如何通过PowerShell来调整PowerShell窗口的大小?

编程语言 2026-07-12

当我在Windows 11直接启动 PowerShell.exe 时,看起来像是在使用Windows Terminal。但父进程实际上是 explorer.exe

调整PowerShell窗口大小的常规方法似乎不起作用:

$host.UI.RawUI.WindowSize = New-Object System.Management.Automation.Host.Size(80, 40)
Add-Type @"
using System;
using System.Runtime.InteropServices;

public class Win32 {
    [DllImport("user32.dll")]
    public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
}
"@

$ThisProc = Get-Process -Id $PID
[Win32]::MoveWindow($ThisProc.MainWindowHandle, 100, 100, 600, 200, $true)

如何定位PowerShell运行所在的Windows Terminal窗口?

解决方案

确实,问题在于如果你直接启动 powershell.exe,而不是从WT(Windows Terminal)内部启动,PowerShell进程的父进程是 explorer.exe,而不是 WindowsTerminal 进程。 [1]

由于是WT进程拥有终端窗口,挑战在于如何明确无歧义地识别实际承载PowerShell会话的那个“具体”的WT进程——正如 Santiago 所指出的。


一个看起来足够稳健的解决方案——它依赖于WT的实现细节,这些细节不太可能改变,然而——是要定位大概是唯一的、未提升权限且具备可见主窗口的WT进程

# Compile the helper type for invoking the MoveWindow WinAPI function.
Add-Type @'
using System;
using System.Runtime.InteropServices;

public class Win32 {
  [DllImport("user32.dll")]
  public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
}
'@

# Obtain the parent process, tentatively assumed to be the hosting WT instance,
# as is the case if PowerShell was launched *from WT*.
$processWt = Get-Process -Id (Get-CimInstance Win32_Process -Filter "ProcessId=$PID").ParentProcessId
# If the parent is *not* WT, PowerShell was launched *directly, from outside WT*, so 
# the hosting WT instance must be determined differently:
if ($processWt.Name -cne 'WindowsTerminal') {
  # PowerShell was launched *directly*, invariably *non-elevated*.
  # Find the WT process that has a main window handle and whose .Path property
  # has a value (this filters out *elevated* instances),
  $processWt = Get-Process WindowsTerminal | Where MainWindowTitle | Where Path 
} 

[Win32]::MoveWindow(
  $processWt.MainWindowHandle,
  100, 100, 600, 200, $true
)

注:

  • 此方法适用于两种调用场景:直接执行可执行文件并隐式启动WT,以及从现有WT实例中启动;但它确实要求PowerShell会话在一个可见的WT选项卡/窗口中运行——这是在处理窗口时一个公平的前提。
  • 之所以需要测试一个具有可见主窗口的 WindowsTerminal 进程,是基于以下原因:

  • 在同一操作系统用户会话中,长期看来WT似乎只维护一个管理所有打开标签和窗口的 WindowsTerminal 进程;但当使用 wt.exe 打开新标签页或新窗口时,短暂地可能会出现不止一个实例,即委托给现有实例的瞬态实例;你可以按如下方式验证:

    • 从一个已存在的WT标签页运行PowerShell,执行以下命令:

    bash Start-Process wt.exe; Start-Sleep -MilliSeconds 20; Get-Process WindowsTerminal - 这将打开一个新标签页(或窗口,取决于你的配置);关闭它以查看提交命令的结果。通常你会看到两个 WindowsTerminal 实例,而稍后再次执行 Get-Process WindowsTerminal 时只会显示一个。

  • 然而,只有长期实例维护可见的窗口;如果存在多个窗口——.MainWindowHandle / .MainWindowTitle 将反映当前活动的WT窗口的值。[2]

    • 因此,推断在 .MainWindowHandle / .MainWindowTitle 中只有一个具有有效值的 WindowsTerminal 进程,它代表着管理所有窗口的唯一可见实例(下文提到的用于提升WT选项卡的潜在第二个进程请参见)。
      (请注意,在其他已登录用户的桌面上运行的WT实例(若有)并非关注点,因为它们的 .MainWindowHandle / .MainWindowTitle 属性从未被填充。)
    • 如果你的 powershell.exe 进程是在常规桌面会话(窗口站)中从WT之外启动,它将成为第一个创建的 WindowsTerminal 进程的活动选项卡/窗口,或成为一个已有实例的活动窗口。无论哪种情况,定位到相关进程即可操作其活动窗口。
    • 还有一个额外的复杂性,由额外的 | Where Path 测试来解决:
  • 在某个OS用户会话中,可能存在两个非瞬态的 WindowsTerminal 进程,它们各自具有有效的窗口句柄:一个用于运行非提升权限的外壳的所有标签页/窗口,另一个用于所有提升(以管理员身份运行)外壳的标签页/窗口。

  • 然而,要判断某一进程是否提升权限,这需要多次WinAPI调用,比较繁琐(例如可参见 [DetectElevation] 项目)。

  • 一个简单的捷径,适用于未提升权限的PowerShell会话,是测试候选进程的 .Path 属性:未提升权限的进程不得查询被提升进程在此属性上的值;PowerShell会吞下由此产生的.NET异常,并将属性值显示为 $null
    因此,| Where Path——使用将 $null 视为 $false 的隐式布尔测试——可以过滤掉提升进程。

  • 虽然在相反的场景下该测试不会工作,因为提升进程可以查询未提升进程的所有属性,但这个场景很幸运地不会出现(至少到Windows 11 25H2为止,我猜这是设计使然):没有办法从WT外部直接启动一个提升的PowerShell实例,最终在WT中运行。
    看起来,直接启动的提升控制台应用程序总是运行在旧式控制台窗口中(conhost.exe);相反,唯一让提升的shell在 WT中运行的办法,是从WT自身启动它。


或者在控制 powershell.exe 调用方式时更稳妥地——如果你能控制调用方式的话——可以用 wt.exe powershell.exe 调用,在这种情况下WT将始终成为父进程,你可以使用以下简化代码:

# Only for PowerShell sessions started from Windows Terminal or via wt.exe
[Win32]::MoveWindow(
  (Get-Process -Id (Get-CimInstance Win32_Process -Filter "ProcessId=$PID").ParentProcessId).MainWindowHandle,
  100, 100, 600, 200, $true
)

PowerShell (Core) 7 (pwsh.exe) 允许更简洁高效的解决方案,因为这里的进程对象具有一个 .Parent 属性:

# Ditto, but PowerShell (Core) 7 only.
[Win32]::MoveWindow(
  (Get-Process -Id $PID).Parent.MainWindowHandle, 
  100, 100, 600, 200, $true
)

[1] 这在你的问题中有暗示,但还是把它说清楚:在旧式控制台窗口(由 conhost.exe 提供)中运行的PowerShell会话,PowerShell进程本身拥有控制台窗口(其句柄可通过 (Get-Process -Id $PID).MainWindowHandle 获取)。当PowerShell运行在Windows Terminal中时,如今默认情况下WT会接管控制台,(Get-Process -Id $PID).MainWindowHandle 返回空指针;其余的回答讨论了,需要处理的两种Windows Terminal情景,遗憾的是。

[2] 为方便起见,使用 Where MainWindowTitle,即当主窗口标题是非空字符串时返回 $true 的测试。由于WT忽略把标签页标题设为空字符串的尝试,这应当是鲁棒的。如果你想测试主窗口句柄,则需要使用 Where MainWindowHandle -ne ([IntPtr]::Zero);可惜的是,Where MainWindowHandle 并不足够,因为空指针在PowerShell中被意外地视为“假值”,并且为了解决这个问题的决定是被放弃的——参见 GitHub issue #17601

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章