From 16ce906044976aac2acc29480650d51b7631f995 Mon Sep 17 00:00:00 2001 From: Justin Paul Date: Fri, 8 May 2026 10:18:01 -0400 Subject: [PATCH] Installer: synchronous service stop + kill stray GUI/Service processes The previous sc.exe stop is fire-and-forget; on slower machines the file-copy step started before the service had actually released its binaries, leaving the upgrade in a broken state. Switch to net.exe stop which blocks until the service reports STOPPED. Also taskkill any running WebhookServer.Gui.exe (the user might have left the tray running) and any orphan WebhookServer.Service.exe (from deploy.ps1 dev runs) so all copies of the binaries are unlocked before [Files] runs. Pre-flight ServiceExists() check via sc query so the installer only calls "net stop" when there is actually a service to stop, rather than relying on net's error code. Co-Authored-By: Claude Opus 4.7 (1M context) --- installer/webhook-server.iss | 37 +++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/installer/webhook-server.iss b/installer/webhook-server.iss index 5e940c8..b97f5ef 100644 --- a/installer/webhook-server.iss +++ b/installer/webhook-server.iss @@ -79,16 +79,39 @@ Filename: "powershell.exe"; \ RunOnceId: "RemoveWebhookService" [Code] +function ServiceExists(): Boolean; +var + ResultCode: Integer; +begin + // sc.exe query returns 0 when the service exists, 1060 when it does not. + Exec(ExpandConstant('{sys}\sc.exe'), 'query WebhookServer', '', SW_HIDE, + ewWaitUntilTerminated, ResultCode); + Result := (ResultCode = 0); +end; + function PrepareToInstall(var NeedsRestart: Boolean): String; var ResultCode: Integer; begin Result := ''; - // Stop the running service so its binaries are unlocked before file copy. - // Ignore failure - sc returns non-zero if the service doesn't exist (fresh - // install) or is already stopped, both of which are fine. - Exec(ExpandConstant('{sys}\sc.exe'), 'stop WebhookServer', '', SW_HIDE, - ewWaitUntilTerminated, ResultCode); - // Give the SCM a moment to actually release the executable. - Sleep(2000); + + // 1. If the service exists, stop it so its binaries are unlocked before file + // copy. net stop is synchronous (blocks until the service is actually + // stopped), unlike sc stop which is fire-and-forget. Non-zero exit - + // already stopped, missing, dependency error - we ignore; the file copy + // will fail loudly if the binaries are still locked. + if ServiceExists() then + begin + WizardForm.PreparingLabel.Caption := 'Stopping the WebhookServer service...'; + Exec(ExpandConstant('{sys}\net.exe'), 'stop WebhookServer', '', SW_HIDE, + ewWaitUntilTerminated, ResultCode); + end; + + // 2. Kill any running GUI / tray instances so their binaries are unlocked too. + // /f forces termination, /im matches by image name, "*" wildcard would be + // risky so we name them explicitly. + Exec(ExpandConstant('{sys}\taskkill.exe'), '/f /im WebhookServer.Gui.exe', + '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Exec(ExpandConstant('{sys}\taskkill.exe'), '/f /im WebhookServer.Service.exe', + '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end;