Fix the two remaining UI-thread elevation freezes (profile save + installer)
Review finding, same class as the shipped install-hang fix: two paths still ran the elevated service helper synchronously on the UI thread, freezing the window and live audio for the duration (worst case minutes across two UAC prompts). 1) Service-profile save (MainForm.ConfigureServiceProfile): saving while the service runs did RunElevated(stop) + RunElevated(start) inline. Now: the restart runs on a background task, and tries a NO-UAC restart first - ServiceControl.TryRestartNoAdmin uses the start/stop rights the installer grants the installing account, so the normal case has no elevation prompt at all. Elevated verbs remain the fallback (service installed by a different account). Success is silent; only a failed restart reports back. The save popup now says the service is restarting. 2) App installer's optional service step (AppInstaller): the install + start-now calls ran RunElevated inline. The flow is sequential (can't fire-and-forget - the installer relaunches and exits afterwards), so RunElevatedResponsive runs the helper on a worker while a small modal "working..." shell pumps messages: UI and audio stay live, nothing can be double-triggered, NVDA announces the step, and the exit code still returns inline. Test: "No-admin service restart fails safe" - TryRestartNoAdmin against a missing service returns false promptly without throwing (that false routes callers onto the elevated fallback). The success path needs the real SCM + grant, covered by hand-test. Gate 47/47. Part of the review-fix batch; no release until the whole plan lands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2367,12 +2367,42 @@ public sealed class MainForm : Form
|
||||
logFile.Event($"service: profile saved (service logging {(dlg.ServiceLoggingEnabled ? "on" : "off")})");
|
||||
// Remove any stray copy the old design left in the user's profiles folder.
|
||||
try { profileStore?.Delete(ServiceControl.ServiceProfileTitle); } catch { /* best-effort */ }
|
||||
if (ServiceControl.Query() == ServiceState.Running)
|
||||
var restartNeeded = ServiceControl.Query() == ServiceState.Running;
|
||||
if (restartNeeded)
|
||||
{
|
||||
ServiceControl.RunElevated(ServiceControl.StopVerb);
|
||||
ServiceControl.RunElevated(ServiceControl.StartVerb);
|
||||
// Restart OFF the UI thread — the old inline RunElevated stop+start pair here blocked the
|
||||
// window (and its audio) through two UAC prompts; same bug class as the install hang.
|
||||
// No-UAC first: the installer granted this account start/stop rights, so a plain SCM
|
||||
// restart normally needs no elevation at all. Elevated verbs are the fallback (service
|
||||
// installed by a different account, grant missing). Only a FAILURE is reported back;
|
||||
// success needs no second popup.
|
||||
ServiceStore.AppendServiceEvent("restart requested (service profile changed)");
|
||||
Task.Run(() =>
|
||||
{
|
||||
var ok = ServiceControl.TryRestartNoAdmin();
|
||||
if (!ok)
|
||||
{
|
||||
ServiceControl.RunElevated(ServiceControl.StopVerb);
|
||||
ok = ServiceControl.RunElevated(ServiceControl.StartVerb) == 0;
|
||||
}
|
||||
ServiceStore.AppendServiceEvent(ok
|
||||
? "restart finished (new service profile is live)"
|
||||
: "restart FAILED after profile change");
|
||||
if (ok || IsDisposed) return;
|
||||
try
|
||||
{
|
||||
BeginInvoke(new Action(() => MessageBox.Show(this,
|
||||
"The service profile was saved, but the running service could not be restarted to pick it up. Use the Service menu to stop and start it.",
|
||||
AppName, MessageBoxButtons.OK, MessageBoxIcon.Warning)));
|
||||
}
|
||||
catch { /* window closing */ }
|
||||
});
|
||||
}
|
||||
MessageBox.Show(this, "Service profile saved.", AppName, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
MessageBox.Show(this,
|
||||
restartNeeded
|
||||
? "Service profile saved. The running service is restarting to pick it up."
|
||||
: "Service profile saved.",
|
||||
AppName, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user