How PowerShell conquered the console window flash
pwsh.exe flashes a console window before
-WindowStyle Hidden can hide it
Tell Windows to skip auto-allocation, then allocate the right way in code
Run pwsh -WindowStyle Hidden -Command 'exit' — a window
appears for a split second then vanishes.
-WindowStyle Hidden and hides the
window
Because pwsh.exe is a
CUI binary
Console User Interface - an executable type that tells
Windows to automatically create a console window before any
code runs , Windows creates the console window BEFORE any
PowerShell code runs. By the time Main() executes and
can read the -WindowStyle argument, the window is
already visible.
How Windows creates console windows — and why the OS acts before your code runs
CUI (Console User Interface) apps get a console
window automatically. This enables stdin/stdout/stderr, console
APIs like Console.ReadLine(), and pipe operators.
GUI (Graphical User Interface) apps don't get a console. They start without any console window.
pwsh.exe is CUI because it needs
Console.ReadLine(), Write-Host, and pipe
operators to work.
The OS acts BEFORE your code runs. This is a
fundamental Windows design — the console exists before
Main() is even called.
Manifest + Early Initialization = No Flash
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<consoleAllocationPolicy
xmlns="http://schemas.microsoft.com/SMI/2024/WindowsSettings">
detached
</consoleAllocationPolicy>
</windowsSettings>
</application>
detached policy means Windows skips the
automatic console creation step, giving PowerShell full control.
detached →
skips console creation
Main() starts → calls
EarlyConsoleInit(args) before any other code
EarlyConsoleInit scans args for
-WindowStyle Hidden
AllocConsoleWithOptions(NoWindow) —
invisible console with full I/O
AllocConsoleWithOptions(Default) —
normal visible console
EntryPointNotFoundException → fallback
AllocConsole()
private static void EarlyConsoleInit(string[] args)
{
nint existingConsole = Interop.Windows.GetConsoleWindow();
if (existingConsole != nint.Zero)
{
if (EarlyCheckForHiddenWindowStyle(args))
{
Interop.Windows.ShowWindow(existingConsole,
Interop.Windows.SW_HIDE);
}
return;
}
if (EarlyCheckForHiddenWindowStyle(args))
{
if (!Interop.Windows.TryAllocConsoleNoWindow())
{
Interop.Windows.AllocConsole();
nint hwnd = Interop.Windows.GetConsoleWindow();
if (hwnd != nint.Zero)
{
Interop.Windows.ShowWindow(hwnd,
Interop.Windows.SW_HIDE);
}
}
}
else
{
if (!Interop.Windows.TryAllocConsoleDefault())
{
Interop.Windows.AllocConsole();
}
}
}
private static bool EarlyCheckForHiddenWindowStyle(string[] args)
{
for (int i = 0; i < args.Length - 1; i++)
{
string arg = args[i];
if (arg.Length >= 2 && (arg[0] == '-' || arg[0] == '/'))
{
int start = 1;
if (arg.Length >= 3 && arg[0] == '-' && arg[1] == '-')
{
start = 2;
}
ReadOnlySpan<char> key = arg.AsSpan(start);
if (key.Length >= 1
&& key.Length <= "windowstyle".Length
&& "windowstyle".AsSpan().StartsWith(key,
StringComparison.OrdinalIgnoreCase))
{
if (args[i + 1].Equals("hidden",
StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
}
return false;
}
-, /, or
--)
-w, -win,
-windowstyle all work)
private static bool TryAllocConsoleWithMode(AllocConsoleMode mode)
{
try
{
var options = new AllocConsoleOptions
{
Mode = mode,
UseShowWindow = 0,
ShowWindow = 0,
};
int hr = AllocConsoleWithOptions(ref options, out _);
return hr >= 0; // S_OK
}
catch (EntryPointNotFoundException)
{
return false;
}
}
EntryPointNotFoundException and return false so we
can fall back to the old method.
bool allocated = Interop.Windows.TryAllocConsoleNoWindow();
if (!allocated)
{
Interop.Windows.AllocConsole();
hwnd = Interop.Windows.GetConsoleWindow();
if (hwnd == nint.Zero)
{
return false;
}
Interop.Windows.ShowWindow(hwnd,
Interop.Windows.SW_HIDE);
}
AllocConsole(), then immediately hide
it with ShowWindow(SW_HIDE). internal static void SetConsoleMode(ProcessWindowStyle style)
{
IntPtr hwnd = GetConsoleWindow();
if (hwnd == IntPtr.Zero)
{
// No console window to modify. This can happen when
// running with consoleAllocationPolicy=detached before
// the console is allocated.
return;
}
// ... switch on style ...
}
| Launch Context | Win11 26100+ (New) | Older Windows (Legacy) |
|---|---|---|
| Direct terminal |
Console inherited, no flash
EarlyConsoleInit detects existing console, keeps it
visible
|
Console inherited, no flash
OS provides console automatically, works as before
|
| Explorer/Scheduler |
Visible console, no flash
AllocConsoleWithOptions(Default) creates normal console
|
Visible console, no flash
OS creates console automatically, works as before
|
| -WindowStyle Hidden |
No window ever appears ✨
AllocConsoleWithOptions(NoWindow) creates invisible
console with full I/O
|
Brief flash, then hidden
Falls back to AllocConsole() + ShowWindow(SW_HIDE), but
now happens in EarlyConsoleInit (faster than before)
|
| DETACHED_PROCESS parent |
Respects parent's intent
AllocConsoleWithOptions(Default) respects the DETACHED
flag from parent
|
Overrides to visible
Old AllocConsole() would override DETACHED flag and create
a visible console
|
| Inherited console |
Uses inherited console
EarlyConsoleInit detects existing console via
GetConsoleWindow() and uses it
|
Uses inherited console
OS provides inherited console automatically
|
pwsh -WindowStyle Hidden from Task
Scheduler on Windows 11 24H2. What happens?
Verifies pwsh.manifest contains the detached policy
Tests that -WindowStyle Hidden still captures output, errors, exit codes, and Write-Host
Tests -w, -win, -window, -windowstyle, and --windowstyle
Checks if AllocConsoleWithOptions is available
Ensures normal (non-hidden) launch still works
It "captures output from -WindowStyle Hidden -Command" {
$output = & $powershell -NoProfile `
-WindowStyle Hidden `
-Command "'hello'"
$output | Should -Be "hello"
}
-WindowStyle Hidden, tell it to
output the string 'hello', and verify we actually get 'hello'
back. -w Hidden (shortest
prefix)?
"windowstyle".AsSpan().StartsWith(key,
StringComparison.OrdinalIgnoreCase). As long as the prefix is at least 1 character and doesn't
exceed "windowstyle".Length, it matches. So -w,
-win, -windowstyle all work.
You now understand how PowerShell conquered the console window flash by combining a manifest directive with early initialization and graceful fallbacks. This fix works on both new and old Windows versions, respects parent process intentions, and maintains full console I/O functionality.