The console-window class (the project lead, 5 October 2026: "Windows Command Processor" windows on PC 1 whenever a remote job runs). Measured on PC 1 (ae432dc7, Windows 11 Pro 26200, default terminal "Let Windows decide" = Windows Terminal 1.24) with tools/windows/console-watch.ps1 (job run-20261005-182528): no child a job script starts from the app's headless console opens a window (powershell, cmd, query, curl, nvidia-smi, wsl --status, a distro, interop cmd and powershell, powershell -WindowStyle Hidden: 0 windows each); Start-Process in a new console opens a Terminal window (the known-failed case: 2 windows), the same with -WindowStyle Hidden opens none (the known-finished case). The elevated path (Start-Process -Verb RunAs -WindowStyle Hidden through the AppInfo service) is the one road left; its watcher (console-watch-elevated.ps1, job run-20261005-184610) was cancelled at the UAC prompt. - platform.rs: elevated_ps_line + elevated_command build the one PowerShell line every elevated launch uses (the NVIDIA power cap, the sweep helper, the clock sync, an elevated remote job), -WindowStyle Hidden by construction; unit tests on the line, the quoting and the Command. - jobrun.rs: the elevated job path uses it; the relaunch helper's Start-Process carries the reason it has no -WindowStyle Hidden (igneum-app.exe is a windows-subsystem program). - tools/ci/windows-spawn-check.mjs (+ ci.yml): fails when a Command::new in app/igneum-app/src is not quieted, a creation_flags is not CREATE_NO_WINDOW alone, a Start-Process the Rust code writes lacks -WindowStyle Hidden or -NoNewWindow, or host.cpp spawns without CREATE_NO_WINDOW / SW_HIDE; self-test on known-good and known-bad samples. - tools/windows/console-watch.ps1, console-watch-bg.ps1, console-watch-elevated.ps1: the watchers (run jobs). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
84 lines
5.6 KiB
PowerShell
84 lines
5.6 KiB
PowerShell
# Console-window watcher for the ELEVATED job path (app/igneum-app/src/jobrun.rs run_script with elevated=true: the
|
|
# app's headless powershell runs `Start-Process powershell.exe -Verb RunAs -Wait -WindowStyle Hidden`, the AppInfo
|
|
# service creates this process after the UAC prompt). The engine's power cap, the sweep helper and the clock sync take
|
|
# the same road with cmd.exe (platform.rs run_elevated, sync_clock; app/windows/host.cpp runElevated). This script
|
|
# runs INSIDE the elevated process and reports whether its own console has a window, which host serves it, and
|
|
# whether a Windows Terminal window appeared for it. One UAC prompt on the PC; a few seconds.
|
|
# packaging/ota/publish-jobs.sh add --kind run --target ae432dc7 --elevated --timeout-minutes 3 \
|
|
# --script tools/windows/console-watch-elevated.ps1 --title "PC 1: elevated console watcher" --deploy
|
|
$ErrorActionPreference = 'Continue'
|
|
$src = @'
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
public static class IgWin3 {
|
|
public delegate bool EnumProc(IntPtr h, IntPtr l);
|
|
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumProc p, IntPtr l);
|
|
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h);
|
|
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
|
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int GetWindowText(IntPtr h, StringBuilder s, int n);
|
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int GetClassName(IntPtr h, StringBuilder s, int n);
|
|
[DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow();
|
|
public static List<string> Visible() {
|
|
var list = new List<string>();
|
|
EnumWindows((h, l) => {
|
|
if (!IsWindowVisible(h)) return true;
|
|
uint pid; GetWindowThreadProcessId(h, out pid);
|
|
var t = new StringBuilder(512); GetWindowText(h, t, 512);
|
|
var c = new StringBuilder(256); GetClassName(h, c, 256);
|
|
list.Add(((long)h).ToString() + "|" + pid + "|" + c + "|" + t);
|
|
return true;
|
|
}, IntPtr.Zero);
|
|
return list;
|
|
}
|
|
}
|
|
'@
|
|
Add-Type -TypeDefinition $src
|
|
function Say([string] $m) { Write-Output $m }
|
|
function ProcName([int] $procId) { try { (Get-Process -Id $procId -ErrorAction Stop).ProcessName } catch { 'gone' } }
|
|
function Chain([int] $procId) {
|
|
$out = @(); $seen = @{}; $p = $procId
|
|
for ($i = 0; $i -lt 6 -and $p -gt 0 -and -not $seen.ContainsKey($p); $i++) {
|
|
$seen[$p] = 1
|
|
$ci = Get-CimInstance Win32_Process -Filter "ProcessId=$p" -ErrorAction SilentlyContinue
|
|
if (-not $ci) { $out += ("pid " + $p + " gone"); break }
|
|
$cl = [string]$ci.CommandLine; if ($cl.Length -gt 140) { $cl = $cl.Substring(0, 140) + '...' }
|
|
$out += ($ci.Name + " pid " + $p + " [" + $cl + "]")
|
|
$p = $ci.ParentProcessId
|
|
}
|
|
return ($out -join ' <- ')
|
|
}
|
|
$me = [System.Diagnostics.Process]::GetCurrentProcess()
|
|
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
$admin = (New-Object Security.Principal.WindowsPrincipal($id)).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
Say ("RESULT elevated: " + $admin + " user " + $id.Name + " session " + $me.SessionId + " chain " + (Chain $me.Id))
|
|
$hwnd = [IgWin3]::GetConsoleWindow()
|
|
$cls = ''
|
|
if ($hwnd -ne [IntPtr]::Zero) { $sb = New-Object System.Text.StringBuilder 256; [void][IgWin3]::GetClassName($hwnd, $sb, 256); $cls = $sb.ToString() }
|
|
$vis = if ($hwnd -ne [IntPtr]::Zero) { [IgWin3]::IsWindowVisible($hwnd) } else { 'no window' }
|
|
Say ("RESULT self-console: hwnd " + $hwnd + " class [" + $cls + "] visible " + $vis)
|
|
# the hosts that serve this process: a conhost with this pid as parent (classic), or an OpenConsole + WindowsTerminal
|
|
# pair started by svchost in the last seconds (the default-terminal handoff)
|
|
$since = (Get-Date).AddSeconds(-20)
|
|
foreach ($h in @(Get-CimInstance Win32_Process -Filter "Name='conhost.exe' OR Name='OpenConsole.exe' OR Name='WindowsTerminal.exe'" -ErrorAction SilentlyContinue)) {
|
|
$created = try { [Management.ManagementDateTimeConverter]::ToDateTime($h.CreationDate) } catch { $null }
|
|
if ($h.ParentProcessId -eq $me.Id -or ($created -and $created -gt $since)) {
|
|
Say ("RESULT host: " + $h.Name + " pid " + $h.ProcessId + " parent " + (ProcName $h.ParentProcessId) + " started " + $(if ($created) { $created.ToUniversalTime().ToString('HH:mm:ss') } else { '?' }) + " cmd " + $h.CommandLine)
|
|
}
|
|
}
|
|
$wins = @([IgWin3]::Visible() | Where-Object { $f = $_.Split('|', 4); $f[2] -eq 'CASCADIA_HOSTING_WINDOW_CLASS' -or $f[2] -eq 'ConsoleWindowClass' -or $f[2] -eq 'PseudoConsoleWindow' })
|
|
Say ("RESULT console-windows-now: " + $wins.Count)
|
|
foreach ($w in $wins) { $f = $w.Split('|', 4); Say ("RESULT window: " + (ProcName ([int]$f[1])) + " pid " + $f[1] + " [" + $f[2] + "] " + $f[3]) }
|
|
# a child the elevated script starts the way the sweep helper and the power cap do (cmd, inherited console), watched
|
|
$before = @{}; foreach ($w in [IgWin3]::Visible()) { $before[$w.Split('|', 4)[0]] = 1 }
|
|
$p = Start-Process -FilePath cmd.exe -ArgumentList '/c ping -n 3 127.0.0.1 >nul' -NoNewWindow -PassThru
|
|
$seen = @{}
|
|
for ($i = 0; $i -lt 30; $i++) {
|
|
foreach ($w in [IgWin3]::Visible()) { $f = $w.Split('|', 4); if (-not $before.ContainsKey($f[0]) -and -not $seen.ContainsKey($f[0])) { $seen[$f[0]] = (ProcName ([int]$f[1])) + " pid " + $f[1] + " [" + $f[2] + "] " + $f[3] } }
|
|
Start-Sleep -Milliseconds 100
|
|
}
|
|
try { $p.WaitForExit(10000) | Out-Null } catch {}
|
|
Say ("RESULT probe cmd-inherit-elevated: " + $seen.Count + " new window(s)")
|
|
foreach ($s in $seen.Values) { Say ("RESULT window: " + $s + " (probe cmd-inherit-elevated)") }
|
|
exit 0
|