Improve Windows installer (#674)

This commit is contained in:
Flaminel authored and GitHub committed 2026-07-24 15:07:03 +03:00
1 parent a28caf1bc9
commit 282ec1d325
1 file changed
+130 -47
+130 -47
View File
@@ -52,25 +52,25 @@ Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; IconFilename: "{app}\favicon.ico"; Tasks: desktopicon
[Run]
; Create service only if it doesn't exist (fresh install)
Filename: "{sys}\sc.exe"; Parameters: "create ""{#MyServiceName}"" binPath= ""\""{app}\{#MyAppExeName}\"""" DisplayName= ""{#MyAppName}"" start= auto"; Tasks: installservice; Flags: runhidden; Check: not ServiceExists('{#MyServiceName}')
Filename: "{sys}\sc.exe"; Parameters: "description ""{#MyServiceName}"" ""Cleanuparr download management service"""; Tasks: installservice; Flags: runhidden; Check: not ServiceExists('{#MyServiceName}')
; Start service (both fresh install and update)
Filename: "{sys}\sc.exe"; Parameters: "start ""{#MyServiceName}"""; Tasks: installservice; Flags: runhidden
; Open web interface
Filename: "http://localhost:11011"; Description: "Open Cleanuparr Web Interface"; Flags: postinstall shellexec nowait; Check: IsTaskSelected('installservice')
; Run directly (if not installed as service)
Filename: "{app}\{#MyAppExeName}"; Description: "Run {#MyAppName} Application"; Flags: nowait postinstall skipifsilent; Check: not IsTaskSelected('installservice')
[UninstallRun]
Filename: "{sys}\sc.exe"; Parameters: "stop ""{#MyServiceName}"""; Flags: runhidden; Check: ServiceExists('{#MyServiceName}')
Filename: "{sys}\timeout.exe"; Parameters: "/t 5"; Flags: runhidden; Check: ServiceExists('{#MyServiceName}')
Filename: "{sys}\sc.exe"; Parameters: "delete ""{#MyServiceName}"""; Flags: runhidden; Check: ServiceExists('{#MyServiceName}')
[Code]
var
DeleteUserData: Boolean;
procedure LogInstaller(const Msg: string);
var
LogFile, Line: string;
begin
LogFile := ExpandConstant('{app}\cleanuparr-installer.log');
Line := '[' + GetDateTimeString('yyyy/mm/dd hh:nn:ss', '-', ':') + '] ' + Msg + #13#10;
SaveStringToFile(LogFile, Line, True);
end;
function ServiceExists(ServiceName: string): Boolean;
var
ResultCode: Integer;
@@ -81,23 +81,10 @@ end;
function IsServiceRunning(ServiceName: string): Boolean;
var
ResultCode: Integer;
TempFile: string;
StatusOutput: AnsiString;
begin
Result := False;
TempFile := ExpandConstant('{tmp}\service_status.txt');
// Use PowerShell to get service status
if Exec(ExpandConstant('{sys}\WindowsPowerShell\v1.0\powershell.exe'),
'-Command "try { (Get-Service -Name ''' + ServiceName + ''' -ErrorAction Stop).Status } catch { ''NotFound'' }" > "' + TempFile + '"',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0) then
begin
if LoadStringFromFile(TempFile, StatusOutput) then
begin
Result := (Pos('Running', StatusOutput) > 0);
end;
DeleteFile(TempFile);
end;
Result := Exec(ExpandConstant('{sys}\WindowsPowerShell\v1.0\powershell.exe'),
'-Command "if ((Get-Service -Name ''' + ServiceName + ''' -ErrorAction SilentlyContinue).Status -eq ''Running'') { exit 0 } else { exit 1 }"',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
end;
function WaitForServiceStop(ServiceName: string; TimeoutSeconds: Integer): Boolean;
@@ -118,24 +105,100 @@ begin
Result := False;
end;
procedure StopAndDeleteService(const Name: string);
var
ResultCode, i: Integer;
begin
if not ServiceExists(Name) then
begin
LogInstaller('Service ' + Name + ' not present, nothing to remove');
Exit;
end;
LogInstaller('Stopping service ' + Name);
Exec(ExpandConstant('{sys}\sc.exe'), 'stop "' + Name + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
if not WaitForServiceStop(Name, 30) then
LogInstaller('WARNING: service did not report STOPPED within 30s');
LogInstaller('Force-killing the service process');
Exec(ExpandConstant('{sys}\taskkill.exe'), '/f /t /fi "SERVICES eq ' + Name + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
for i := 1 to 10 do
begin
Exec(ExpandConstant('{sys}\sc.exe'), 'delete "' + Name + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
if not ServiceExists(Name) then
begin
LogInstaller('Service ' + Name + ' deleted (attempt ' + IntToStr(i) + ')');
Exit;
end;
Sleep(1000);
end;
LogInstaller('WARNING: service ' + Name + ' still present after 10 delete attempts (last sc delete ResultCode=' + IntToStr(ResultCode) + ')');
end;
procedure CreateOrUpdateService();
var
ResultCode: Integer;
Ok: Boolean;
begin
if not IsTaskSelected('installservice') then
begin
if ServiceExists('{#MyServiceName}') then
begin
LogInstaller('Service task deselected on upgrade, removing existing service');
StopAndDeleteService('{#MyServiceName}');
end;
Exit;
end;
if ServiceExists('{#MyServiceName}') then
begin
LogInstaller('Refreshing existing service definition');
Ok := Exec(ExpandConstant('{sys}\sc.exe'), ExpandConstant('config "{#MyServiceName}" binPath= "\"{app}\{#MyAppExeName}\"" start= auto'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
end
else
begin
LogInstaller('Creating service');
Ok := Exec(ExpandConstant('{sys}\sc.exe'), ExpandConstant('create "{#MyServiceName}" binPath= "\"{app}\{#MyAppExeName}\"" DisplayName= "{#MyAppName}" start= auto'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
end;
if not Ok then
begin
LogInstaller('ERROR: failed to create or configure the service, ResultCode=' + IntToStr(ResultCode));
if not WizardSilent() then
MsgBox('Failed to install the Cleanuparr service (error ' + IntToStr(ResultCode) + '). See ' + ExpandConstant('{app}\cleanuparr-installer.log') + ' for details.', mbError, MB_OK);
Exit;
end;
Exec(ExpandConstant('{sys}\sc.exe'), 'description "{#MyServiceName}" "Cleanuparr download management service"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
LogInstaller('Starting service');
if not (Exec(ExpandConstant('{sys}\sc.exe'), 'start "{#MyServiceName}"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and ((ResultCode = 0) or (ResultCode = 1056))) then
begin
LogInstaller('ERROR: failed to start the service, ResultCode=' + IntToStr(ResultCode));
if not WizardSilent() then
MsgBox('The Cleanuparr service was installed but failed to start (error ' + IntToStr(ResultCode) + '). See ' + ExpandConstant('{app}\cleanuparr-installer.log') + ' for details.', mbError, MB_OK);
end;
end;
function InitializeSetup(): Boolean;
var
ResultCode: Integer;
begin
Result := True;
// If service exists and is running, stop it for the update
if ServiceExists('{#MyServiceName}') and IsServiceRunning('{#MyServiceName}') then
begin
if MsgBox('Cleanuparr service is currently running and needs to be stopped for the installation. Continue?',
if MsgBox('Cleanuparr service is currently running and needs to be stopped for the installation. Continue?',
mbConfirmation, MB_YESNO) = IDYES then
begin
LogInstaller('Stopping running service for update');
Exec(ExpandConstant('{sys}\sc.exe'), 'stop "{#MyServiceName}"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
if not WaitForServiceStop('{#MyServiceName}', 30) then
begin
MsgBox('Warning: Service took longer than expected to stop. Installation will continue but you may need to restart the service manually.',
mbInformation, MB_OK);
LogInstaller('Service did not stop in time, force-killing the service process');
Exec(ExpandConstant('{sys}\taskkill.exe'), '/f /t /fi "SERVICES eq {#MyServiceName}"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
end
else
@@ -146,25 +209,45 @@ begin
end;
function InitializeUninstall(): Boolean;
var
ResultCode: Integer;
begin
Result := True;
if ServiceExists('{#MyServiceName}') then
DeleteUserData := False;
if not UninstallSilent() then
begin
if MsgBox('Cleanuparr service will be stopped and removed. Continue with uninstallation?',
mbConfirmation, MB_YESNO) = IDYES then
if MsgBox('Do you want to keep Cleanuparr''s configuration and logs?' + #13#10#13#10 +
'Click Yes to keep them, or No to delete all data.',
mbConfirmation, MB_YESNO) = IDNO then
begin
if IsServiceRunning('{#MyServiceName}') then
begin
Exec(ExpandConstant('{sys}\sc.exe'), 'stop "{#MyServiceName}"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
WaitForServiceStop('{#MyServiceName}', 30);
end;
end
DeleteUserData := True;
end;
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
CreateOrUpdateService();
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usUninstall then
begin
LogInstaller('=== Uninstall started ===');
StopAndDeleteService('{#MyServiceName}');
if RegDeleteKeyIncludingSubkeys(HKLM, 'SYSTEM\CurrentControlSet\Services\EventLog\Application\{#MyServiceName}') then
LogInstaller('Removed EventLog source key')
else
LogInstaller('EventLog source key not present');
end
else if CurUninstallStep = usPostUninstall then
begin
if DeleteUserData then
begin
Result := False;
LogInstaller('Deleting configuration and logs');
DelTree(ExpandConstant('{app}\config'), True, True, True);
DeleteFile(ExpandConstant('{app}\cleanuparr-installer.log'));
end;
end;
end;