2

Background

  • I want to hide the console window in a PowerShell script.

    • EDIT: I am making this script stay resident with the system tray icon and hide from the taskbar. This script uses OneDrive to store screenshots. When you run this script, you have to authenticate to OneDrive, so first you can't run this script with -WindowStyle Hidden option (the window for authentication should be shown). After authentication, I want to hide the terminal from the taskbar and show the system tray icon.
  • On Windows 11, when you set Windows Console Host as the "Default terminal application" in the Startup setting of Windows Terminal, you can hide the console windows like this:

$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)
}

Problem

On Windows 11, when you set Windows Terminal as the "Default terminal application" in the Startup setting of Windows Terminal, you can't get the window handle of console windows with the code above.

Instead of the code above, you can get the window handle like this:

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

But in this code, ShowWindowAsync($hwnd, 0) doesn't work properly. According to the document of ShowWindowAsync, it hides the windows when you pass 0 as the 2nd parameter. When I ran the code above, the Windows Terminal window is minimized rather than hidden.

Question

How can I hide the console window with PowerShell when you set Windows Terminal as the "Default terminal application" in the Startup setting of Windows Terminal on Windows 11?

SATO Yusuke
  • 1,600
  • 15
  • 39
  • Why exactly do you want to do that? If you just want to call a script without creating a new window you can either invoke it by doing the following `Start-Process powershell.exe -WindowStyle Hidden -Arg script_path` or by writing a [module](https://learn.microsoft.com/en-us/powershell/scripting/developer/module/how-to-write-a-powershell-script-module?view=powershell-7.3) which ends in a `.psm1` extension. You could also create a scheduled task and run your powershell there. –  Dec 31 '22 at 10:07
  • Is this question still open? or did you last edit solve your question? To make it clear you can (and should) simply answer your own question by providing an answer, – Luuk Dec 31 '22 at 13:06
  • Thank you for your comment. I added what I want to do (make a script stay resident with the system tray and hide from the taskbar) and why the `-WindowStyle Hidden` option is not suitable for the situation to the "Background" section. – SATO Yusuke Dec 31 '22 at 13:07
  • This is a common question. You might have to use vbscript run with the hidden option. – js2010 Dec 31 '22 at 16:26

3 Answers3

3

So, as per my comment. In ISE/console/WindowTerminal... just do this:

$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())"

Update:

As per my comment to '@Luuk'

Since very early DOS/Windows you could just do the same thing this way.

title YourCoolScriptName

or even .Net namespaces

[System.Console]::Title = 'YourCoolScriptName'
postanote
  • 15,138
  • 2
  • 14
  • 25
2

I do think you are trying to hide the wrong window. I (tried to) add the WindowTitle, but it has an incorrect (not expected value)

I changed the code to just beep 10 times, and then stop, and added some code found here: 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++
  }

When starting this using a shortcut to: pwsh.exe -File "d:\temp\beep1.ps1"

A window is opened with the text The name of this window is:, I hear 10 beeps, and the execution stops.

When executed from within "Windows Powershell ISE", the window from that IDE close. With some debugging I found out that the output is "The name of this window is: Windows PowerShell ISE".

I do think that the return value from getting the WindowText should be like the title of your shortcut. (But, unfortunately, knowledge of how to solve this is not available on my side )

Luuk
  • 12,245
  • 5
  • 22
  • 33
  • You can set a console window in your script to whatever you want, ```$host.UI.RawUI.WindowTitle = 'YourCoolScriptName'```and use that in your code. ***Well, as long as you've not any further real-time prompt customizations*** in your profiles. Just put that in front of that ```$stringbuilder = New-Object System.Text.StringBuilder 256``` line. – postanote Dec 31 '22 at 19:41
  • @postanote: This may make things easier than what I was (trying) to do. – Luuk Jan 01 '23 at 10:00
  • No worries, glad you found it useful. It's been a thing in Windows well before PS was ever a thing in CMD/BAT/VBS/WMIC days. Folks just often forget about it, or was not aware that it's been a thing for many years. – postanote Jan 01 '23 at 20:13
0

Based on @postanote and @Luuk's answers, I made a function to hide the console window like this:

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."
    }
  }
}
SATO Yusuke
  • 1,600
  • 15
  • 39