diff --git a/installers/windows/cleanuparr-installer.iss b/installers/windows/cleanuparr-installer.iss index d7a530f4..6e534477 100644 --- a/installers/windows/cleanuparr-installer.iss +++ b/installers/windows/cleanuparr-installer.iss @@ -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; \ No newline at end of file