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
TryAlloc* returns false — but manifest is also
ignored, so OS already auto-allocated (step 1 skipped)
private static void EarlyConsoleInit(string[] args)
{
nint existingConsole = Interop.Windows.GetConsoleWindow();
if (existingConsole != nint.Zero)
{
// Console already exists (inherited from parent or
// auto-allocated on older Windows).
// Leave -WindowStyle handling to the existing
// SetConsoleMode code path.
return;
}
// No console exists. This means either:
// (a) Detached manifest policy is active (newer Windows)
// (b) DETACHED_PROCESS — no console at all
// (c) CREATE_NO_WINDOW — console session, no window
// No fallback to AllocConsole() — per DHowett's guidance,
// we don't override DETACHED_PROCESS intent.
if (EarlyCheckForHiddenWindowStyle(args))
{
Interop.Windows.TryAllocConsoleNoWindow();
}
else
{
Interop.Windows.TryAllocConsoleDefault();
}
}
-WindowStyle handling to the
existing SetConsoleMode code path and return.
DETACHED_PROCESS / CREATE_NO_WINDOW.
TryAllocConsoleNoWindow()
— creates an invisible console with full I/O.
TryAllocConsoleDefault()
— respects DETACHED_PROCESS from the parent (no console
forced). AllocConsole() —
per DHowett's guidance, we don't override the parent's intent.
On older Windows the manifest is ignored, so the OS auto-allocates
and we hit the early return above.
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 AllocConsoleResult result);
return hr >= 0 && result != AllocConsoleResult.NoConsole;
}
catch (EntryPointNotFoundException)
{
return false;
}
}
hr >= 0) AND that a console was
actually allocated (result != NoConsole).
EntryPointNotFoundException and return false.
On older Windows the manifest is ignored, so the OS already
auto-allocated a console (the early return in
EarlyConsoleInit handles that case).
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
OS auto-allocates console (manifest ignored). SetConsoleMode
hides it later — same timing as before this PR
|
| DETACHED_PROCESS parent |
Respects parent's intent
AllocConsoleWithOptions(Default) respects the DETACHED
flag from parent
|
No console (preserved)
API unavailable, no fallback to AllocConsole() — preserves
existing no-console behavior per DHowett's guidance
|
| 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.
✗ Not quite. The parser uses prefix matching —
any unique prefix of "windowstyle" works.
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.