使用PowerShell隐藏Windows终端控制台窗口

3

背景

  • 我想在 PowerShell 脚本中隐藏控制台窗口。

    • 编辑:我正在使 此脚本 保持常驻系统托盘图标并从任务栏隐藏。 此脚本使用 OneDrive 存储截图。当您运行此脚本时,您必须对 OneDrive 进行身份验证,因此首先不能使用 -WindowStyle Hidden 选项运行此脚本(应显示身份验证窗口)。身份验证后,我希望将终端从任务栏隐藏并显示系统托盘图标。
  • 在 Windows 11 上,当您将 Windows Console Host 设置为 Windows 终端的启动设置中的“默认终端应用程序”时,您可以像这样隐藏控制台窗口:

$windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
$asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
$hwnd = (Get-Process -PID $pid).MainWindowHandle
if ($hwnd -ne [System.IntPtr]::Zero) {
  $hidden = $asyncwindow::ShowWindowAsync($hwnd, 0)
}

问题

在Windows 11上,当您将Windows终端设置为"默认终端应用程序"时,在Windows终端的启动设置中,您无法使用上述代码获取控制台窗口的句柄。

您可以使用以下代码获取窗口句柄,而非上述代码:

Add-Type -Name ConsoleAPI -Namespace Win32Util -MemberDefinition '[DllImport("Kernel32.dll")] public static extern IntPtr GetConsoleWindow();'
$hwnd = [Win32Util.ConsoleAPI]::GetConsoleWindow()
$hidden = $asyncwindow::ShowWindowAsync($hwnd, 0)

但是在这段代码中,ShowWindowAsync($hwnd, 0)不能正常工作。根据ShowWindowAsync的文档,当您将0作为第二个参数传递时,它会隐藏窗口。当我运行上面的代码时,Windows终端窗口被最小化而不是隐藏。

问题

当您在Windows 11的Windows终端启动设置中将Windows Terminal设置为“默认终端应用程序”时,如何使用PowerShell隐藏控制台窗口?


你为什么要这样做呢?如果你只是想调用一个脚本而不创建新窗口,你可以通过以下方式来调用它:Start-Process powershell.exe -WindowStyle Hidden -Arg script_path 或者编写一个以 .psm1 扩展名结尾的模块。你也可以创建一个计划任务并在其中运行你的 PowerShell。 - user13523921
这个问题还没有解决吗?或者你最后的编辑已经解决了你的问题吗?为了明确起见,你可以(并且应该)通过提供答案来回答自己的问题。 - Luuk
谢谢您的评论。我已经在“背景”部分添加了我想要做的事情(使脚本保持驻留在系统托盘中并从任务栏中隐藏),以及为什么“-WindowStyle Hidden”选项不适用于这种情况。 - SATO Yusuke
这是一个常见问题。你可能需要使用隐藏选项运行vbscript。 - js2010
3个回答

3

所以,根据我的评论,在ISE/console/WindowTerminal中...只需这样做:

$host.UI.RawUI.WindowTitle = 'YourCoolScriptName'

$stringbuilder             = New-Object System.Text.StringBuilder 256
$windowcode                = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
$asyncwindow               = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
$hwnd                      = (Get-Process -PID $pid).MainWindowHandle
$count                     = [UserWindows]::GetWindowText($hwnd, $stringbuilder, 256)

"The name of this window is: $($stringbuilder.ToString())"

更新:

根据我对 '@Luuk' 的评论

自从早期的DOS/Windows,你就可以用同样的方法来做这件事。

title YourCoolScriptName

甚至包括 .Net 命名空间

[System.Console]::Title = 'YourCoolScriptName'

3

根据@postanote和@Luuk的答案,我制作了一个隐藏控制台窗口的函数,如下:

function Hide-ConsoleWindow() {
  $ShowWindowAsyncCode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
  $ShowWindowAsync = Add-Type -MemberDefinition $ShowWindowAsyncCode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru

  $hwnd = (Get-Process -PID $pid).MainWindowHandle
  if ($hwnd -ne [System.IntPtr]::Zero) {
    # When you got HWND of the console window:
    # (It would appear that Windows Console Host is the default terminal application)
    $ShowWindowAsync::ShowWindowAsync($hwnd, 0)
  } else {
    # When you failed to get HWND of the console window:
    # (It would appear that Windows Terminal is the default terminal application)

    # Mark the current console window with a unique string.
    $UniqueWindowTitle = New-Guid
    $Host.UI.RawUI.WindowTitle = $UniqueWindowTitle
    $StringBuilder = New-Object System.Text.StringBuilder 1024

    # Search the process that has the window title generated above.
    $TerminalProcess = (Get-Process | Where-Object { $_.MainWindowTitle -eq $UniqueWindowTitle })
    # Get the window handle of the terminal process.
    # Note that GetConsoleWindow() in Win32 API returns the HWND of
    # powershell.exe itself rather than the terminal process.
    # When you call ShowWindowAsync(HWND, 0) with the HWND from GetConsoleWindow(),
    # the Windows Terminal window will be just minimized rather than hidden.
    $hwnd = $TerminalProcess.MainWindowHandle
    if ($hwnd -ne [System.IntPtr]::Zero) {
      $ShowWindowAsync::ShowWindowAsync($hwnd, 0)
    } else {
      Write-Host "Failed to hide the console window."
    }
  }
}

2

我认为您试图隐藏错误的窗口。我尝试添加了WindowTitle,但它具有不正确(不是预期的)的值。

我将代码更改为只响铃10次,然后停止,并添加了在此处找到的一些代码:https://stackoverflow.com/a/40354761/724039

Add-Type @"
  using System;
  using System.Runtime.InteropServices;
  public class UserWindows {
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowText(IntPtr hWnd, System.Text.StringBuilder text, int count);
}
"@

$stringbuilder = New-Object System.Text.StringBuilder 256

$windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
$asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
$hwnd = (Get-Process -PID $pid).MainWindowHandle
$count = [UserWindows]::GetWindowText($hwnd, $stringbuilder, 256)
"The name of this window is: $($stringbuilder.ToString())"
if ($hwnd -ne [System.IntPtr]::Zero) {
  $hidden = $asyncwindow::ShowWindowAsync($hwnd, 0)
}

$run = $true
$x=1
while ($x -le 10) {
  [console]::beep(500,100)
  Start-Sleep 1
  $x++
  }

当使用快捷方式启动以下内容时:pwsh.exe -File "d:\temp\beep1.ps1"

一个窗口会打开,上面显示文本The name of this window is:,我听到10声蜂鸣声,然后执行停止。

当在“Windows Powershell ISE”中执行时,该IDE的窗口会关闭。通过一些调试,我发现输出为“The name of this window is: Windows PowerShell ISE”。

我认为从获取WindowText的返回值应该类似于您的快捷方式标题。(但是,不幸的是,我无法解决此问题)


你可以在脚本中设置控制台窗口为任何你想要的样子,$host.UI.RawUI.WindowTitle = 'YourCoolScriptName'并在你的代码中使用它。只要你的配置文件中没有进一步的实时提示自定义。将其放在$stringbuilder = New-Object System.Text.StringBuilder 256行的前面即可。 - postanote
@postanote:这可能比我(试图)做的事情更容易。 - Luuk
没问题,很高兴你觉得它有用。在CMD/BAT/VBS/WMIC时代,Windows就已经有了这个功能。人们经常忘记它的存在,或者不知道它已经存在多年了。 - postanote

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