fix(desktop-update): give Windows update steps NUL stdin, not the hand-off console
Steps inherited the hand-off console's stdin, so any step that asks a question blocked forever. Its prompt went to the captured stdout, which is shown only after the step exits. `gateway start --all` did exactly this: it saw an interactive console and asked "Install it now so the gateway starts on login?". The update stopped after `hermes update exit code: 0` and never relaunched the app. Steps now read NUL. Prompts see a non-interactive stdin and take their defaults. The working-directory self-test also checks that a step's stdin is not a console, and a new test runs it under a real console.
This commit is contained in:
@@ -898,6 +898,12 @@ public static class HermesUpdateJob {
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool SetHandleInformation(IntPtr handle, int mask, int flags);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern IntPtr CreateFile(
|
||||
string fileName, uint desiredAccess, uint shareMode, ref SecurityAttributes attributes,
|
||||
uint creationDisposition, uint flagsAndAttributes, IntPtr templateFile
|
||||
);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern bool CreateProcess(
|
||||
string applicationName, StringBuilder commandLine,
|
||||
@@ -912,9 +918,6 @@ public static class HermesUpdateJob {
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool TerminateProcess(IntPtr process, uint exitCode);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr GetStdHandle(int standardHandle);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool TerminateJobObject(IntPtr job, uint exitCode);
|
||||
|
||||
@@ -934,6 +937,7 @@ public static class HermesUpdateJob {
|
||||
IntPtr job = IntPtr.Zero;
|
||||
IntPtr outRead = IntPtr.Zero, outWrite = IntPtr.Zero;
|
||||
IntPtr errRead = IntPtr.Zero, errWrite = IntPtr.Zero;
|
||||
IntPtr nullInput = new IntPtr(-1);
|
||||
ProcessInformation pi = new ProcessInformation();
|
||||
try {
|
||||
job = CreateJobObject(IntPtr.Zero, null);
|
||||
@@ -946,11 +950,17 @@ public static class HermesUpdateJob {
|
||||
throw new InvalidOperationException("CreatePipe failed");
|
||||
if (!SetHandleInformation(outRead, 1, 0) || !SetHandleInformation(errRead, 1, 0))
|
||||
throw new InvalidOperationException("SetHandleInformation failed");
|
||||
// Steps read NUL, never the hand-off console. A step that sees a
|
||||
// console asks its question into the captured stdout, where the
|
||||
// user cannot see it, and waits for an answer that never comes.
|
||||
nullInput = CreateFile("NUL", 0x80000000, 0x00000003, ref sa, 3, 0, IntPtr.Zero);
|
||||
if (nullInput == new IntPtr(-1))
|
||||
throw new InvalidOperationException("CreateFile(NUL) failed");
|
||||
|
||||
StartupInfo si = new StartupInfo();
|
||||
si.Size = Marshal.SizeOf(typeof(StartupInfo));
|
||||
si.Flags = 0x00000100; // STARTF_USESTDHANDLES
|
||||
si.StdInput = GetStdHandle(-10);
|
||||
si.StdInput = nullInput;
|
||||
si.StdOutput = outWrite;
|
||||
si.StdError = errWrite;
|
||||
StringBuilder commandLine = new StringBuilder("\"" + executable + "\" " + arguments);
|
||||
@@ -989,6 +999,7 @@ public static class HermesUpdateJob {
|
||||
if (outWrite != IntPtr.Zero) CloseHandle(outWrite);
|
||||
if (errRead != IntPtr.Zero) CloseHandle(errRead);
|
||||
if (errWrite != IntPtr.Zero) CloseHandle(errWrite);
|
||||
if (nullInput != new IntPtr(-1)) CloseHandle(nullInput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1500,13 +1511,19 @@ try {
|
||||
if ($SelfTestWorkingDirectory) {
|
||||
$expectedRoot = [System.IO.Path]::GetFullPath($InstallRoot)
|
||||
$probeExe = Join-Path $PSHOME "powershell.exe"
|
||||
$probe = Invoke-HermesStep $probeExe @("-NoProfile", "-Command", "[Environment]::CurrentDirectory") "cwd"
|
||||
$observed = $probe.Output.Trim()
|
||||
$probe = Invoke-HermesStep $probeExe @("-NoProfile", "-Command", "[Environment]::CurrentDirectory; [Console]::IsInputRedirected") "cwd"
|
||||
$observed, $stdinRedirected = @($probe.Output.Trim() -split "`r?`n" | ForEach-Object { $_.Trim() })
|
||||
if ($probe.Code -ne 0 -or -not [string]::Equals($observed, $expectedRoot, [StringComparison]::OrdinalIgnoreCase)) {
|
||||
$finalMsg = "WORKING-DIRECTORY SELF-TEST: FAIL expected=$expectedRoot observed=$observed code=$($probe.Code)"
|
||||
Write-Host $finalMsg
|
||||
exit 1
|
||||
}
|
||||
# A step that can read the hand-off console can block on a prompt nobody sees.
|
||||
if ($stdinRedirected -ne "True") {
|
||||
$finalMsg = "WORKING-DIRECTORY SELF-TEST: FAIL step stdin is an interactive console"
|
||||
Write-Host $finalMsg
|
||||
exit 1
|
||||
}
|
||||
$finalCode = 0
|
||||
$finalMsg = "WORKING-DIRECTORY SELF-TEST: PASS $observed"
|
||||
Write-Host $finalMsg
|
||||
|
||||
Reference in New Issue
Block a user