7.2 KiB
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:
- If
--relaunchedis in args, skip the check (loop guard). - If we're not in the Administrators role, skip.
- If our parent process is NOT
explorer.exe, skip. - Otherwise — re-spawn ourselves via
runas.exe /trustlevel:0x20000 "<exe path>" --relaunched <forwarded args>andShutdown(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:
- Brief flash of a window that immediately closes (the elevated initial process detecting explorer-spawn and re-launching).
- A second TeamsISO window appears, parented under
runas.exe, at medium integrity. - 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 /trustlevelspawn 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 LogonWindows service might be disabled (it's required for runas).Get-Service seclogon | Format-Listto 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
- Code-signing the MSI.
SIGN_CERT_PFX_BASE64+SIGN_CERT_PASSWORDneed to go into Forgejo Actions Secrets forrelease.ymlto start producing signed MSIs. Without that, downstream operators get the "Windows protected your PC" SmartScreen warning. - Real-meeting smoke pass on a non-dev host with a live NDI runtime.
Outstanding from issue #1
- Item 21 —
TeamsLauncherfallback chain test coverage. Still needs theIProcessLauncherseam 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.