docs(next-steps): root cause was explorer-spawn elevation, fix shipped in 191b2c5
Some checks failed
CI / build-and-test (push) Failing after 27s
Some checks failed
CI / build-and-test (push) Failing after 27s
This commit is contained in:
parent
191b2c5f52
commit
0e73746b58
1 changed files with 123 additions and 93 deletions
214
NEXT_STEPS.md
214
NEXT_STEPS.md
|
|
@ -1,101 +1,129 @@
|
||||||
# Where we left off — cold-start launch fix shipped (2026-05-16 morning)
|
# Where we left off — explorer-spawn de-elevation shipped (2026-05-16)
|
||||||
|
|
||||||
## What just landed (verified on this machine)
|
## The actual root cause (finally)
|
||||||
|
|
||||||
**Origin tip: `09e5b59` — fix: cold-start discovery + installer shortcuts +
|
When TeamsISO is spawned by an **elevated File Explorer**, NDI Find returns
|
||||||
single-instance hardening.**
|
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.
|
||||||
|
|
||||||
The "I install, I click the shortcut, no participants" bug is fixed. Three
|
I reproduced this multiple times with the same install:
|
||||||
independent changes in one commit because all three were chasing the same
|
|
||||||
operator report:
|
|
||||||
|
|
||||||
1. **`NdiDiscoveryService.RunAsync` — immediate first poll + fast ramp.**
|
| Launch parent | Integrity | Result |
|
||||||
`PeriodicTimer.WaitForNextTickAsync` waits the full interval before its
|
|---|---|---|
|
||||||
first tick, so for a 500ms discovery interval the operator stared at
|
| non-elevated PowerShell | medium | OK — 2 participants |
|
||||||
"no ndi sources yet" for half a second on every cold start. Now: poll
|
| elevated PowerShell (via `-Verb RunAs`) | high | OK — 2 participants |
|
||||||
once up front (picks up whatever the NDI runtime has already cached),
|
| `runas /trustlevel:0x20000` | medium | OK — 2 participants |
|
||||||
then run a 200ms inner loop for ~3 seconds while mDNS replies trickle
|
| elevated Explorer (operator click) | high | **EMPTY** — 0 participants |
|
||||||
in, then settle to the operator-configured interval. Both loops share
|
|
||||||
a try/finally so the NDI finder is always disposed.
|
|
||||||
2. **`MainViewModel.IsDiscovering` + new empty-state copy.** New boolean
|
|
||||||
property, true for 8s after engine start as long as no participants
|
|
||||||
have arrived. `MainWindow.xaml` swaps the empty-state copy on this
|
|
||||||
binding:
|
|
||||||
- `IsDiscovering=true` → "scanning for ndi sources…" with a cyan dot
|
|
||||||
- `IsDiscovering=false` → "no ndi sources visible — is teams in a
|
|
||||||
meeting?" + Refresh CTA
|
|
||||||
The old copy was being shown immediately at launch even when discovery
|
|
||||||
just hadn't run yet, which read as "broken."
|
|
||||||
3. **Single-instance mutex moved from `Local\` to `Global\`.** On admin-user
|
|
||||||
boxes with UAC effectively disabled, launches from different parents
|
|
||||||
can land in slightly different security contexts and a `Local\` named
|
|
||||||
mutex can be invisible to a sibling. `Global\` is system-wide and
|
|
||||||
integrity-agnostic; both processes see the same mutex regardless of
|
|
||||||
how they were spawned.
|
|
||||||
4. **`installer/Package.wxs` — Desktop shortcut component added.** Now
|
|
||||||
installs both `C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Wild Dragon\TeamsISO.lnk`
|
|
||||||
AND `C:\Users\Public\Desktop\TeamsISO.lnk`. Both point at the installed
|
|
||||||
exe under `C:\Program Files\Wild Dragon\TeamsISO\TeamsISO.exe`. (Previously
|
|
||||||
only the Start Menu component existed; on this machine even that wasn't
|
|
||||||
visibly materializing because the older WiX `Advertise` default left it
|
|
||||||
as a stub.)
|
|
||||||
|
|
||||||
**MSI:** `installer/bin/x64/Release/TeamsISO-Setup-0.9.0-rc5.msi` (372 KB).
|
Same exe, same install path, same user, same NDI runtime, same Teams
|
||||||
Already installed on this machine. Force-reinstall over an existing copy
|
meeting. The only differentiator is `parent.ImageName == "explorer.exe"`
|
||||||
needs `REINSTALL=ALL REINSTALLMODE=amus` flags on msiexec because the wxs
|
combined with elevation. The suspicion is a window-station / desktop-handle
|
||||||
`Version` is pinned to `1.0.0.0` and MajorUpgrade no-ops on same-version.
|
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.
|
||||||
|
|
||||||
## Verified launch paths (2026-05-16 11:22)
|
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.
|
||||||
|
|
||||||
Tested by killing every TeamsISO process, then launching via each
|
## The fix (`191b2c5`)
|
||||||
mechanism in turn and probing `http://localhost:9755/participants` after
|
|
||||||
4 seconds:
|
|
||||||
|
|
||||||
| Mechanism | Result |
|
`App.OnStartup` now runs an elevation check before any other startup work:
|
||||||
|---|---|
|
|
||||||
| Start Menu shortcut (`…\Wild Dragon\TeamsISO.lnk`) | OK — 2 participants |
|
|
||||||
| Public Desktop shortcut (`C:\Users\Public\Desktop\TeamsISO.lnk`) | OK — 2 participants |
|
|
||||||
| Direct `.exe` double-click in Program Files | OK — 2 participants |
|
|
||||||
|
|
||||||
All three discover the Teams meeting's NDI sources (Active Speaker +
|
1. If `--relaunched` is in args, skip the check (loop guard).
|
||||||
Brendon Power in today's testing) within the cold-start grace window.
|
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.
|
||||||
|
|
||||||
## Open items
|
`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.
|
||||||
|
|
||||||
**Pre-1.0 cut is gated on:**
|
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<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`
|
1. Code-signing the MSI. `SIGN_CERT_PFX_BASE64` + `SIGN_CERT_PASSWORD`
|
||||||
need to be added to Forgejo Actions Secrets for the release.yml
|
need to go into Forgejo Actions Secrets for `release.yml` to start
|
||||||
workflow to start producing signed MSIs. Without that, downstream
|
producing signed MSIs. Without that, downstream operators get the
|
||||||
users get the "Windows protected your PC" SmartScreen warning.
|
"Windows protected your PC" SmartScreen warning.
|
||||||
2. Real-meeting smoke pass on a non-dev host with a live NDI runtime.
|
2. Real-meeting smoke pass on a non-dev host with a live NDI runtime.
|
||||||
|
|
||||||
**Other items still on the queue (from issue #1 polish-pass status):**
|
## Outstanding from issue #1
|
||||||
|
|
||||||
- **Item 21** — `TeamsLauncher` fallback chain test coverage. Needs an
|
- **Item 21** — `TeamsLauncher` fallback chain test coverage. Still
|
||||||
`IProcessLauncher` seam refactor before the URI handler → AppX → process-exe
|
needs the `IProcessLauncher` seam refactor before the URI handler →
|
||||||
order can be unit-pinned. Half-day of work; not blocking.
|
AppX → process-exe order can be unit-pinned. Half-day of work.
|
||||||
|
|
||||||
## Where the layout sits
|
## Build / install cheatsheet
|
||||||
|
|
||||||
**Shell:** Default Windows title bar; 32px header (Wild Dragon mark +
|
|
||||||
wordmark + ⌘K / theme / settings icons); 40px transport strip
|
|
||||||
(`● 02:14:32 PART 4 · LIVE 2 CTRL :9755`); body = alert/update banners +
|
|
||||||
action toolbar + participants DataGrid + conditional meeting bar.
|
|
||||||
|
|
||||||
**Participants DataGrid — 7 columns:** State LED (24) · Preview (106) ·
|
|
||||||
Participant (*) · Audio (110) · Output (130) · CFG/gear (56) ·
|
|
||||||
ISO toggle (124, rounded-rect, "Enable" / "● LIVE").
|
|
||||||
|
|
||||||
**Theme:** `Themes/Theme.Dark.xaml` + `Themes/Theme.Light.xaml` split from
|
|
||||||
`WildDragonTheme.xaml`; `ThemeManager` runtime-swaps the merged dictionary
|
|
||||||
and follows `HKCU\...\Personalize\AppsUseLightTheme`.
|
|
||||||
|
|
||||||
**Hotkeys:** F1 help, Ctrl+K / Ctrl+P command palette, Ctrl+T theme
|
|
||||||
toggle, Ctrl+R refresh, Ctrl+Shift+S panic stop, 1–9 / NumPad 1–9 toggle
|
|
||||||
Nth participant ISO.
|
|
||||||
|
|
||||||
## Build, install, run cheatsheet
|
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
cd "C:\Users\zacga\Documents\Claude\Projects\Teams ISO"
|
cd "C:\Users\zacga\Documents\Claude\Projects\Teams ISO"
|
||||||
|
|
@ -111,17 +139,19 @@ dotnet publish src/TeamsISO.App/TeamsISO.App.csproj `
|
||||||
-o publish/TeamsISO /p:Version=$v
|
-o publish/TeamsISO /p:Version=$v
|
||||||
dotnet build installer/TeamsISO.Installer.wixproj -c Release /p:Version=$v
|
dotnet build installer/TeamsISO.Installer.wixproj -c Release /p:Version=$v
|
||||||
|
|
||||||
# Install (force overwrite same-version)
|
# 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 `
|
Start-Process msiexec.exe -Verb RunAs -Wait -ArgumentList `
|
||||||
'/i', '"installer\bin\x64\Release\TeamsISO-Setup-' + $v + '.msi"', `
|
'/i', '"installer\bin\x64\Release\TeamsISO-Setup-' + $v + '.msi"', '/qn', '/norestart'
|
||||||
'/qn', '/norestart', 'REINSTALL=ALL', 'REINSTALLMODE=amus'
|
|
||||||
|
|
||||||
# Launch (any of these)
|
|
||||||
Start-Process "C:\Program Files\Wild Dragon\TeamsISO\TeamsISO.exe"
|
|
||||||
Start-Process "C:\Users\Public\Desktop\TeamsISO.lnk"
|
|
||||||
Start-Process "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Wild Dragon\TeamsISO.lnk"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If anything regresses the v2 shell, the rollback point for the WPF v1 shell
|
## Rollback
|
||||||
(recording axed) is `1d1ce6a`; the v2-shell-without-table-redesign rollback
|
|
||||||
point is `c271303`.
|
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.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue