# Where we left off — explorer-spawn de-elevation shipped (2026-05-16) ## The actual root cause (finally) When TeamsISO is spawned by an **elevated File Explorer**, NDI Find returns zero discovered sources even though Teams is broadcasting. The same exe spawned from any other parent (PowerShell, cmd, runas, another TeamsISO, etc.) discovers sources fine — even when that parent is itself elevated. I reproduced this multiple times with the same install: | Launch parent | Integrity | Result | |---|---|---| | non-elevated PowerShell | medium | OK — 2 participants | | elevated PowerShell (via `-Verb RunAs`) | high | OK — 2 participants | | `runas /trustlevel:0x20000` | medium | OK — 2 participants | | elevated Explorer (operator click) | high | **EMPTY** — 0 participants | Same exe, same install path, same user, same NDI runtime, same Teams meeting. The only differentiator is `parent.ImageName == "explorer.exe"` combined with elevation. The suspicion is a window-station / desktop-handle inheritance quirk in NDI's mDNS implementation — explorer spawns with shell-specific STARTUPINFOEX attributes that NDI Find apparently can't work through. Not fixable from inside TeamsISO at the runtime layer. This is the actual reason every "I clicked the shortcut and saw no participants" report happened. The earlier "cold-start polling" and "single-instance integrity isolation" theories were both wrong — those fixes were independently good but not the cause. ## The fix (`191b2c5`) `App.OnStartup` now runs an elevation check before any other startup work: 1. If `--relaunched` is in args, skip the check (loop guard). 2. If we're not in the Administrators role, skip. 3. If our parent process is NOT `explorer.exe`, skip. 4. Otherwise — re-spawn ourselves via `runas.exe /trustlevel:0x20000 "" --relaunched ` and `Shutdown(0)` the current process. `runas /trustlevel:0x20000` requests a **medium-integrity restricted token** even when the caller is elevated. The new child appears with `runas.exe` as its parent (NOT explorer.exe), at medium integrity, with the `--relaunched` flag so the de-elevation check no-ops on the second pass. The check uses `System.Management.ManagementObjectSearcher` against `Win32_Process` to find the parent PID — added as a `PackageReference` in the csproj. ## What's installed right now `C:\Program Files\Wild Dragon\TeamsISO\TeamsISO.exe` — **0.9.0-rc6** with the de-elevation logic, timestamp `2026-05-16 11:36:28`. Shortcuts present at `C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Wild Dragon\TeamsISO.lnk` and `C:\Users\Public\Desktop\TeamsISO.lnk`, both pointing at the installed exe. Three stale install records were left over from previous rc1–rc5 attempts (all `DisplayName=TeamsISO`, three different ProductCodes). All three were uninstalled before -rc6 went on cleanly. Only one TeamsISO ARP entry now. ## How to verify Double-click `C:\Program Files\Wild Dragon\TeamsISO\TeamsISO.exe` or click the Start Menu / Desktop shortcut from File Explorer. The expected sequence: 1. Brief flash of a window that immediately closes (the elevated initial process detecting explorer-spawn and re-launching). 2. A second TeamsISO window appears, parented under `runas.exe`, at medium integrity. 3. Participants discover within ~3 seconds. The log file at `C:\Users\zacga\AppData\Local\TeamsISO\Logs\teamsiso.log` will show one "TeamsISO.App starting up" line — NOT two — because the elevated first process exits before initializing the logger. If discovery STILL stays empty, options: - The `runas /trustlevel` spawn may have failed silently. Diagnostics aren't great here because the logger isn't up yet at the de-elevation point. We could log to a fallback raw text file. - The `Secondary Logon` Windows service might be disabled (it's required for runas). `Get-Service seclogon | Format-List` to check; should be Running. ## All commits on origin (newest first) ``` 191b2c5 fix(wpf): de-elevate when spawned by elevated explorer (NDI mDNS isolation) e01fa36 docs(next-steps): cold-start launch fix verified — 3 launch paths green 09e5b59 fix: cold-start discovery + installer shortcuts + single-instance hardening f47edfb ISO toggle: widen column 110->124, tighten padding so 'Enable' fits 47914fc ISO toggle: square corners to match the rest of the button family dba7dcc gear icon: swap Path glyph for U+2699 + bump column to 56px 6c9bee7 fix(wpf): catch participant-left race in ToggleIsoAsync, toast instead of crash 84861da test: integration — App+MainWindow STA smoke, control-surface live VM, theme XAML load 6505a3c test: services — NotesService, UpdateChecker, PresetApplier, OscBridge, IsoController d91f953 test: ControlSurfaceServer route table smoke coverage fbcc562 test: ThemeManager + CommandPaletteViewModel.Matches coverage e96a30b chore: trim stale batch-commit script + drop SmokeTest placeholder 1f07992 refactor(services): extract TeamsEmbedHost from TeamsLauncher 2640739 refactor(control-surface): split server into endpoint partials e67c02c refactor(app): split App.xaml.cs into themed partial files d02a2c0 refactor(viewmodels): split MainViewModel into themed partial classes 33fca8e polish(mainwindow): empty state, table widths, strings, theme tooltip 3739002 chore(docs): reconcile to WPF-only after WinUI 3 was abandoned 5a43c9c feat: per-ISO framerate/resolution/aspect/audio overrides + thumbnail BMP ``` 246/246 tests passing on the merged main. ## Pre-1.0 cut still gated on 1. Code-signing the MSI. `SIGN_CERT_PFX_BASE64` + `SIGN_CERT_PASSWORD` need to go into Forgejo Actions Secrets for `release.yml` to start producing signed MSIs. Without that, downstream operators get the "Windows protected your PC" SmartScreen warning. 2. Real-meeting smoke pass on a non-dev host with a live NDI runtime. ## Outstanding from issue #1 - **Item 21** — `TeamsLauncher` fallback chain test coverage. Still needs the `IProcessLauncher` seam refactor before the URI handler → AppX → process-exe order can be unit-pinned. Half-day of work. ## Build / install cheatsheet ```powershell cd "C:\Users\zacga\Documents\Claude\Projects\Teams ISO" # Build + test dotnet build TeamsISO.sln -c Release # 0 warnings / 0 errors dotnet test TeamsISO.sln -c Release --no-build # 246/246 passing # Publish + MSI $v = "0.9.0-rcN" dotnet publish src/TeamsISO.App/TeamsISO.App.csproj ` -c Release -r win-x64 --self-contained false ` -o publish/TeamsISO /p:Version=$v dotnet build installer/TeamsISO.Installer.wixproj -c Release /p:Version=$v # Install (uninstall first if upgrading from same Version="1.0.0.0"!) Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' | Where-Object DisplayName -like '*TeamsISO*' | ForEach-Object { Start-Process msiexec.exe -Verb RunAs -Wait -ArgumentList "/x $($_.PSChildName) /qn /norestart" } Start-Process msiexec.exe -Verb RunAs -Wait -ArgumentList ` '/i', '"installer\bin\x64\Release\TeamsISO-Setup-' + $v + '.msi"', '/qn', '/norestart' ``` ## Rollback If the de-elevation logic breaks on a different machine config, revert just commit `191b2c5` — the earlier `e01fa36` build (with cold-start polling + Global mutex + dual shortcuts but no de-elevation) is the safe-fallback baseline.