Skip to content

Commit 6214139

Browse files
Address reviewer feedback from daxian-dbw and DHowett
EarlyConsoleInit: remove early ShowWindow(SW_HIDE) for existing consoles per daxian-dbw — leave -WindowStyle handling to the existing SetConsoleMode code path. EarlyConsoleInit now only handles the no-console case. TryAllocConsoleWithMode: check AllocConsoleResult instead of discarding it. Return false when result is NoConsole (DETACHED_PROCESS respected) so callers know a console was not actually allocated. NativeCommandProcessor: add comment clarifying foreground window restore runs for both NoWindow and fallback paths. Manifest: add XML comment explaining why asm.v3 xmlns is required on the application element (distinct from root asm.v1 namespace). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 87933d3 commit 6214139

4 files changed

Lines changed: 40 additions & 39 deletions

File tree

assets/pwsh.manifest

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
2424
</application>
2525
</compatibility>
26+
<!-- The asm.v3 xmlns is required for windowsSettings; it is distinct from the root asm.v1 namespace. -->
2627
<application xmlns="urn:schemas-microsoft-com:asm.v3">
2728
<windowsSettings>
2829
<consoleAllocationPolicy xmlns="http://schemas.microsoft.com/SMI/2024/WindowsSettings">detached</consoleAllocationPolicy>

src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ public static int Start([MarshalAs(UnmanagedType.LPArray, ArraySubType = Unmanag
5555
// On Windows with consoleAllocationPolicy=detached in the manifest,
5656
// no console is auto-allocated by the OS. We must allocate one ourselves
5757
// before anything touches CONOUT$/CONIN$ handles.
58-
// On older Windows the manifest element is ignored and this is a no-op
59-
// (AllocConsole returns false when a console already exists).
58+
// On older Windows the manifest element is ignored and the OS auto-allocates
59+
// a console, so GetConsoleWindow() != 0 and EarlyConsoleInit is a no-op.
6060
EarlyConsoleInit(args);
6161
#endif
6262

@@ -134,52 +134,48 @@ public static int Start([MarshalAs(UnmanagedType.LPArray, ArraySubType = Unmanag
134134
/// <summary>
135135
/// Allocates a console early in startup to support consoleAllocationPolicy=detached.
136136
/// On newer Windows (with the detached policy active), the OS does not auto-allocate
137-
/// a console for CUI apps. On older Windows, AllocConsole() returns false (no-op).
137+
/// a console for CUI apps. On older Windows the manifest is ignored, so the OS
138+
/// auto-allocates a console and GetConsoleWindow() returns non-zero (early return).
138139
/// </summary>
139140
private static void EarlyConsoleInit(string[] args)
140141
{
141142
nint existingConsole = Interop.Windows.GetConsoleWindow();
142143
if (existingConsole != nint.Zero)
143144
{
144145
// Console already exists (inherited from parent or auto-allocated on older Windows).
145-
// If -WindowStyle Hidden was requested, hide the window at the earliest possible moment
146-
// to minimize the flash on older Windows where the detached policy is not supported.
147-
if (EarlyCheckForHiddenWindowStyle(args))
148-
{
149-
Interop.Windows.ShowWindow(existingConsole, Interop.Windows.SW_HIDE);
150-
}
151-
146+
// Leave -WindowStyle handling to the existing SetConsoleMode code path.
152147
return;
153148
}
154149

155-
// No console exists. This means the detached policy is active (newer Windows)
156-
// and we were launched without console inheritance (e.g. from Explorer, Task Scheduler).
150+
// No console exists (GetConsoleWindow() == 0). This means either:
151+
// (a) The detached manifest policy is active (newer Windows), or
152+
// (b) DETACHED_PROCESS — no console at all, or
153+
// (c) CREATE_NO_WINDOW — console session exists but no window.
154+
//
155+
// For (c), AllocConsoleWithOptions returns ExistingConsole (no-op).
156+
// For (a) and (b), behavior depends on the mode:
157+
// Default mode: allocates if the parent would have given us a console
158+
// on prior Windows versions, returns NoConsole for DETACHED_PROCESS.
159+
// NoWindow mode: always creates a console session (overrides DETACHED).
160+
//
161+
// When -WindowStyle Hidden is specified, we intentionally use NoWindow
162+
// even though it overrides DETACHED_PROCESS — the user explicitly asked
163+
// for invisible PowerShell with working I/O, and the alternative (no
164+
// console, crashing on stdin/stdout access) is strictly worse.
165+
//
166+
// If the API is not available (older Windows), TryAlloc* returns false.
167+
// On older Windows the manifest is ignored and the OS auto-allocates
168+
// a console, so GetConsoleWindow() would have returned non-zero above.
169+
// The only older-Windows path here is DETACHED_PROCESS, where the
170+
// existing behavior is no console I/O; we preserve that by not
171+
// falling back to plain AllocConsole() (per DHowett's guidance).
157172
if (EarlyCheckForHiddenWindowStyle(args))
158173
{
159-
// Hidden: allocate an invisible console session so CONOUT$/CONIN$ work
160-
// (Write-Host, native commands, etc.) but no window is ever shown.
161-
if (!Interop.Windows.TryAllocConsoleNoWindow())
162-
{
163-
// Fallback (should not happen since we only reach here on newer Windows,
164-
// but be defensive): alloc + hide.
165-
Interop.Windows.AllocConsole();
166-
nint hwnd = Interop.Windows.GetConsoleWindow();
167-
if (hwnd != nint.Zero)
168-
{
169-
Interop.Windows.ShowWindow(hwnd, Interop.Windows.SW_HIDE);
170-
}
171-
}
174+
Interop.Windows.TryAllocConsoleNoWindow();
172175
}
173176
else
174177
{
175-
// Normal interactive launch: allocate a visible console.
176-
// Use AllocConsoleWithOptions(Default) when available — it respects
177-
// DETACHED_PROCESS from the parent's CreateProcess call, whereas
178-
// plain AllocConsole() would override it and force-create a console.
179-
if (!Interop.Windows.TryAllocConsoleDefault())
180-
{
181-
Interop.Windows.AllocConsole();
182-
}
178+
Interop.Windows.TryAllocConsoleDefault();
183179
}
184180
}
185181

src/System.Management.Automation/engine/Interop/Windows/AllocConsoleWithOptions.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ internal static partial int AllocConsoleWithOptions(
5757

5858
/// <summary>
5959
/// Attempts to allocate a console without a visible window using AllocConsoleWithOptions.
60-
/// Returns false if the API is not available (older Windows) or the call fails.
60+
/// Returns false if the API is not available (older Windows), the call fails,
61+
/// or no console was allocated (e.g. process was started with DETACHED_PROCESS).
6162
/// </summary>
6263
internal static bool TryAllocConsoleNoWindow()
6364
{
@@ -66,9 +67,11 @@ internal static bool TryAllocConsoleNoWindow()
6667

6768
/// <summary>
6869
/// Attempts to allocate a console using AllocConsoleWithOptions with Default mode.
69-
/// Default mode respects DETACHED_PROCESS from the parent's CreateProcess call,
70-
/// whereas plain AllocConsole() would override it and force-create a console.
71-
/// Returns false if the API is not available (older Windows) or the call fails.
70+
/// Default mode respects DETACHED_PROCESS from the parent's CreateProcess call:
71+
/// it returns NoConsole if the parent intended this process to run without a console,
72+
/// whereas plain AllocConsole() would override that and force-create a console.
73+
/// Returns false if the API is not available (older Windows), the call fails,
74+
/// or no console was allocated.
7275
/// </summary>
7376
internal static bool TryAllocConsoleDefault()
7477
{
@@ -86,8 +89,8 @@ private static bool TryAllocConsoleWithMode(AllocConsoleMode mode)
8689
ShowWindow = 0,
8790
};
8891

89-
int hr = AllocConsoleWithOptions(ref options, out _);
90-
return hr >= 0; // S_OK
92+
int hr = AllocConsoleWithOptions(ref options, out AllocConsoleResult result);
93+
return hr >= 0 && result != AllocConsoleResult.NoConsole;
9194
}
9295
catch (EntryPointNotFoundException)
9396
{

src/System.Management.Automation/engine/NativeCommandProcessor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2508,6 +2508,7 @@ internal static bool AllocateHiddenConsole()
25082508

25092509
AlwaysCaptureApplicationIO = true;
25102510

2511+
// Restore foreground window if focus changed during console allocation.
25112512
if (savedForeground != nint.Zero && Interop.Windows.GetForegroundWindow() != savedForeground)
25122513
{
25132514
Interop.Windows.SetForegroundWindow(savedForeground);

0 commit comments

Comments
 (0)