igneum/tools/windows/console-watch-elevated.ps1
igneum-labs 3afb051727 app: every elevated launch through one hidden-console builder; CI check for Windows spawns; PC 1 console watchers
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>
2026-10-06 08:21:46 +00:00

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