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:
@@ -186,7 +186,7 @@ internal static class AppInstaller
|
||||
if (wantService == DialogResult.Yes)
|
||||
{
|
||||
log?.Invoke("install: user opted to install the service too");
|
||||
var rc = ServiceControl.RunElevated(ServiceControl.InstallVerb);
|
||||
var rc = RunElevatedResponsive(owner, ServiceControl.InstallVerb, "Installing the RemSound service...");
|
||||
if (rc == 0)
|
||||
{
|
||||
// Offer to start it now — otherwise it only comes up at the next boot, so a
|
||||
@@ -199,7 +199,7 @@ internal static class AppInstaller
|
||||
if (startNow == DialogResult.Yes)
|
||||
{
|
||||
log?.Invoke("install: user opted to start the service now");
|
||||
var startRc = ServiceControl.RunElevated(ServiceControl.StartVerb);
|
||||
var startRc = RunElevatedResponsive(owner, ServiceControl.StartVerb, "Starting the RemSound service...");
|
||||
MessageBox.Show(owner,
|
||||
startRc == 0
|
||||
? "The RemSound service is running."
|
||||
@@ -226,6 +226,45 @@ internal static class AppInstaller
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
/// <summary>Run an elevated service verb while keeping the UI thread ALIVE. The install flow is
|
||||
/// sequential (offer → install → offer start → start → relaunch), so a fire-and-forget task doesn't
|
||||
/// fit — but blocking the UI thread in WaitForExit froze the window and any live audio (the same
|
||||
/// hang class as the service install-freeze). This runs the elevated helper on a worker while a tiny
|
||||
/// modal "working…" shell pumps messages: the app stays responsive, the user can't double-trigger
|
||||
/// anything, NVDA announces what's happening, and the call still returns the exit code in-line.</summary>
|
||||
private static int RunElevatedResponsive(IWin32Window owner, string verb, string statusText)
|
||||
{
|
||||
var rc = -1;
|
||||
using var wait = new Form
|
||||
{
|
||||
Text = "RemSound",
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog,
|
||||
StartPosition = FormStartPosition.CenterParent,
|
||||
MinimizeBox = false,
|
||||
MaximizeBox = false,
|
||||
ControlBox = false,
|
||||
ShowInTaskbar = false,
|
||||
AutoSize = true,
|
||||
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
Padding = new Padding(24),
|
||||
AccessibleName = statusText,
|
||||
};
|
||||
wait.Controls.Add(new Label
|
||||
{
|
||||
Text = statusText + Environment.NewLine + "Windows may ask for administrator permission.",
|
||||
AutoSize = true,
|
||||
AccessibleName = statusText,
|
||||
});
|
||||
wait.Shown += (_, _) => Task.Run(() =>
|
||||
{
|
||||
try { rc = ServiceControl.RunElevated(verb); }
|
||||
catch { rc = -1; }
|
||||
try { wait.BeginInvoke(new Action(wait.Close)); } catch { /* already closed */ }
|
||||
});
|
||||
wait.ShowDialog(owner);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// ---------------- uninstall ----------------
|
||||
|
||||
/// <summary>Options → "Uninstall RemSound from this PC", chosen from inside the running installed
|
||||
|
||||
Reference in New Issue
Block a user