Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 123 additions & 4 deletions windows/packaging/exe/inno_setup.iss
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ ArchitecturesAllowed=x64compatible arm64
ArchitecturesInstallIn64BitMode=x64compatible arm64
SetupLogging=yes
UninstallLogging=yes
CloseApplications=yes
RestartApplications=no

[Languages]
{% for locale in LOCALES %}
Expand All @@ -291,10 +293,6 @@ Name: "{autoprograms}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}"
Name: "{autodesktop}\\{{DISPLAY_NAME}}"; Filename: "{app}\\{{EXECUTABLE_NAME}}"; Tasks: desktopicon

[Run]
; Stop/delete any existing service
Filename: "{sys}\sc.exe"; Parameters: "stop ""{#SvcName}"""; Flags: runhidden
Filename: "{sys}\sc.exe"; Parameters: "delete ""{#SvcName}"""; Flags: runhidden

; Create service
Filename: "{sys}\sc.exe"; \
Parameters: "create ""{#SvcName}"" binPath= ""{code:ServiceExecutablePath}"" start= delayed-auto DisplayName= ""{#SvcDisplayName}"""; \
Expand All @@ -318,6 +316,124 @@ Filename: "{sys}\sc.exe"; Parameters: "delete ""{#SvcName}"""; Flags: runhidden
Type: filesandordirs; Name: "{#ProgramDataDir}"

[Code]
const
ServiceStopTimeoutMs = 20000;
ServicePollIntervalMs = 250;
UninstallRegSubKey = 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#SetupSetting("AppId")}_is1';
Comment on lines +320 to +322
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ServiceStopTimeoutMs is used as the timeout for waiting on service deletion (WaitForServiceDelete), not for waiting on the service to stop. Renaming this constant (or splitting stop vs delete timeouts) would make the intent clearer and reduce confusion for future changes.

Copilot uses AI. Check for mistakes.

function ExtractExecutablePath(const CommandLine: String): String;
var
S: String;
EndQuote: Integer;
FirstSpace: Integer;
begin
S := Trim(CommandLine);
if S = '' then begin
Result := '';
exit;
end;

if S[1] = '"' then begin
Delete(S, 1, 1);
EndQuote := Pos('"', S);
if EndQuote > 0 then begin
Result := Copy(S, 1, EndQuote - 1);
end else begin
Result := S;
end;
exit;
end;

FirstSpace := Pos(' ', S);
if FirstSpace > 0 then begin
Result := Copy(S, 1, FirstSpace - 1);
end else begin
Result := S;
end;
Comment on lines +324 to +352
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExtractExecutablePath will incorrectly parse an unquoted executable path that contains spaces (e.g., C:\Program Files\...\unins*.exe /SILENT), returning C:\Program and causing RemoveStaleUninstallEntry to delete a valid uninstall registry key. Consider extracting up to the first .exe (or using a more robust command-line parser) before deciding the entry is stale.

Copilot uses AI. Check for mistakes.
end;

procedure RemoveStaleUninstallEntry(const RootKey: Integer; const RootName: String);
var
UninstallString: String;
UninstallExePath: String;
begin
if not RegQueryStringValue(RootKey, UninstallRegSubKey, 'UninstallString', UninstallString) then begin
exit;
end;

UninstallExePath := ExtractExecutablePath(UninstallString);
if (UninstallExePath = '') or FileExists(UninstallExePath) then begin
exit;
end;

Log(
'Removing stale uninstall entry at root=' + RootName +
' key=' + UninstallRegSubKey +
' (missing uninstaller: ' + UninstallExePath + ')'
);
if not RegDeleteKeyIncludingSubkeys(RootKey, UninstallRegSubKey) then begin
Log('Failed to remove stale uninstall entry');
end;
end;

function ExecSc(const Parameters: String; var ExitCode: Integer): Boolean;
begin
Result := Exec(
ExpandConstant('{sys}\sc.exe'),
Parameters,
'',
SW_HIDE,
ewWaitUntilTerminated,
ExitCode
);
if Result then begin
Log('sc.exe ' + Parameters + ' (exit=' + IntToStr(ExitCode) + ')');
end else begin
Log('failed to launch sc.exe ' + Parameters);
end;
end;

procedure StopAndDeleteService;
var
ExitCode: Integer;
begin
ExecSc('stop "{#SvcName}"', ExitCode);
ExecSc('delete "{#SvcName}"', ExitCode);
end;

function WaitForServiceDelete(const TimeoutMs: Integer): Boolean;
var
ExitCode: Integer;
ElapsedMs: Integer;
begin
ElapsedMs := 0;
while ElapsedMs <= TimeoutMs do begin
if ExecSc('query "{#SvcName}"', ExitCode) then begin
// SERVICE_DOES_NOT_EXIST
if ExitCode = 1060 then begin
Result := True;
exit;
end;
end;
Sleep(ServicePollIntervalMs);
ElapsedMs := ElapsedMs + ServicePollIntervalMs;
end;
Result := False;
end;

procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep <> ssInstall then begin
exit;
end;

Log('Pre-install service cleanup started');
StopAndDeleteService;
if not WaitForServiceDelete(ServiceStopTimeoutMs) then begin
Log('Timed out waiting for service deletion; continuing install');
end;
end;

function ServiceExecutablePath(_Param: String): String;
var
Arm64ServicePath: String;
Expand All @@ -331,6 +447,9 @@ end;

function InitializeSetup: Boolean;
begin
RemoveStaleUninstallEntry(HKLM, 'HKLM');
RemoveStaleUninstallEntry(HKCU, 'HKCU');

Dependency_AddWebView2;
Dependency_AddVC2015To2022;
Result := True;
Expand Down
Loading