teamsiso/NEXT_STEPS.md
2026-05-16 11:39:31 -04:00

7.2 KiB
Raw Blame History

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 "<exe path>" --relaunched <forwarded args> 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.exe0.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 rc1rc5 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<date>.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 21TeamsLauncher 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

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.