common.iss 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. #define AppExeName "SyncTrayzor.exe"
  2. #define AppRoot "..\.."
  3. #define AppSrc AppRoot + "\src\SyncTrayzor"
  4. #define AppBin AppRoot +"\bin\" + Arch + "\Release"
  5. #define AppExe AppBin + "\SyncTrayzor.exe"
  6. #define AppName GetStringFileInfo(AppExe, "ProductName")
  7. #define AppVersion GetFileVersion(AppExe)
  8. #define AppPublisher "SyncTrayzor"
  9. #define AppURL "https://github.com/canton7/SyncTrayzor"
  10. #define AppDataFolder "SyncTrayzor"
  11. #define RunRegKey "Software\Microsoft\Windows\CurrentVersion\Run"
  12. #define DotNetInstallerExe "dotNet451Setup.exe"
  13. #define DonateUrl "https://synctrayzor.antonymale.co.uk/donate"
  14. #define SurveyUrl "https://synctrayzor.antonymale.co.uk/survey.php"
  15. [Setup]
  16. AppId={{#AppId}
  17. AppName={#AppName} ({#Arch})
  18. AppVersion={#AppVersion}
  19. VersionInfoVersion={#AppVersion}
  20. ;AppVerName={#AppName} {#AppVersion}
  21. AppPublisher={#AppPublisher}
  22. AppPublisherURL={#AppURL}
  23. AppSupportURL={#AppURL}
  24. AppUpdatesURL={#AppURL}
  25. DefaultDirName={pf}\{#AppName}
  26. DefaultGroupName={#AppName}
  27. AllowNoIcons=yes
  28. LicenseFile={#AppRoot}\LICENSE.txt
  29. OutputDir="."
  30. OutputBaseFilename={#AppName}Setup-{#Arch}
  31. SetupIconFile={#AppSrc}\Icons\default.ico
  32. WizardSmallImageFile=..\icon.bmp
  33. Compression=lzma2/max
  34. ;Compression=None
  35. SolidCompression=yes
  36. PrivilegesRequired=admin
  37. CloseApplications=yes
  38. RestartApplications=no
  39. ; If we try and close CefSharp.BrowserSubprocess.exe we'll fail - it doesn't respond well
  40. ; However if we close *just* SyncTrayzor, that will take care of shutting down CefSharp and syncthing
  41. CloseApplicationsFilter=SyncTrayzor.exe
  42. TouchDate=current
  43. #if "x64" == Arch
  44. ArchitecturesInstallIn64BitMode=x64
  45. ArchitecturesAllowed=x64
  46. #endif
  47. [Languages]
  48. Name: "english"; MessagesFile: "compiler:Default.isl"
  49. [CustomMessages]
  50. InstallingDotNetFramework=Installing .NET Framework. This might take a few minutes...
  51. DotNetFrameworkFailedToLaunch=Failed to launch .NET Framework Installer with error "%1". Please fix the error then run this installer again.
  52. DotNetFrameworkFailed1602=.NET Framework installation was cancelled. This installation can continue, but be aware that this application may not run unless the .NET Framework installation is completed successfully.
  53. DotNetFrameworkFailed1603=A fatal error occurred while installing the .NET Framework. Please fix the error, then run the installer again.
  54. DotNetFrameworkFailed5100=Your computer does not meet the requirements of the .NET Framework. Please consult the documentation.
  55. DotNetFrameworkFailedOther=The .NET Framework installer exited with an unexpected status code "%1". Please review any other messages shown by the installer to determine whether the installation completed successfully, and abort this installation and fix the problem if it did not.
  56. [Tasks]
  57. Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
  58. [Dirs]
  59. Name: "{userappdata}\{#AppDataFolder}"
  60. [Files]
  61. ; Near the beginning, as it's extracted first and this makes it cheaper
  62. Source: "..\{#DotNetInstallerExe}"; DestDir: {tmp}; Flags: dontcopy nocompression noencryption
  63. Source: "{#AppBin}\*"; DestDir: "{app}"; Excludes: "*.xml,*.vshost.*,*.config,*.log,FluentValidation.resources.dll,System.Windows.Interactivity.resources.dll,syncthing.exe,data,logs,ffmpegsumo.dll,d3dcompiler_43.dll,d3dcompiler_47.dll,libEGL.dll,libGLESv2.dll,pdf.dll"; Flags: ignoreversion recursesubdirs
  64. Source: "{#AppBin}\SyncTrayzor.exe.Installer.config"; DestDir: "{app}"; DestName: "SyncTrayzor.exe.config"; Flags: ignoreversion
  65. Source: "{#AppSrc}\Icons\default.ico"; DestDir: "{app}"; Flags: ignoreversion
  66. Source: "{#AppRoot}\*.md"; DestDir: "{app}"; Flags: ignoreversion
  67. Source: "{#AppRoot}\*.txt"; DestDir: "{app}"; Flags: ignoreversion
  68. Source: "*.dll"; DestDir: "{app}"; Flags: ignoreversion
  69. Source: "syncthing.exe"; DestDir: "{app}"; DestName: "syncthing.exe"; Flags: ignoreversion
  70. [Icons]
  71. Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"
  72. Name: "{group}\{cm:UninstallProgram,{#AppName}}"; Filename: "{uninstallexe}"
  73. Name: "{commondesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon
  74. [Run]
  75. Filename: "{app}\{#AppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(AppName, '&', '&&')}}"; Flags: nowait postinstall; Parameters: {code:SyncTrayzorStartFlags}; Check: ShouldStartSyncTrayzor
  76. [Code]
  77. var
  78. GlobalRestartRequired: boolean;
  79. UninstallPollPage: TNewNotebookPage;
  80. UninstallNextButton: TNewButton;
  81. function DotNetIsMissing(): Boolean;
  82. var
  83. Exists: Boolean;
  84. Release: Cardinal;
  85. begin
  86. Exists := RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', Release);
  87. Result := not Exists or (Release < 378758);
  88. end;
  89. // Adapted from https://blogs.msdn.microsoft.com/davidrickard/2015/07/17/installing-net-framework-4-5-automatically-with-inno-setup/
  90. function InstallDotNet(): String;
  91. var
  92. StatusText: string;
  93. ResultCode: Integer;
  94. begin
  95. StatusText := WizardForm.StatusLabel.Caption;
  96. WizardForm.StatusLabel.Caption := CustomMessage('InstallingDotNetFramework');
  97. WizardForm.ProgressGauge.Style := npbstMarquee;
  98. try
  99. ExtractTemporaryFile('{#DotNetInstallerExe}');
  100. if not Exec(ExpandConstant('{tmp}\{#DotNetInstallerExe}'), '/passive /norestart /showrmui /showfinalerror', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
  101. begin
  102. Result := FmtMessage(CustomMessage('DotNetFrameworkFailedToLaunch'), [SysErrorMessage(ResultCode)]);
  103. end
  104. else
  105. begin
  106. // See https://msdn.microsoft.com/en-us/library/ee942965(v=vs.110).aspx#return_codes
  107. case resultCode of
  108. 0: begin
  109. // Successful
  110. end;
  111. 1602 : begin
  112. MsgBox(CustomMessage('DotNetFrameworkFailed1602'), mbInformation, MB_OK);
  113. end;
  114. 1603: begin
  115. Result := CustomMessage('DotNetFrameworkFailed1603');
  116. end;
  117. 1641: begin
  118. GlobalRestartRequired := True;
  119. end;
  120. 3010: begin
  121. GlobalRestartRequired := True;
  122. end;
  123. 5100: begin
  124. Result := CustomMessage('DotNetFrameworkFailed5100');
  125. end;
  126. else begin
  127. MsgBox(FmtMessage(CustomMessage('DotNetFrameworkFailedOther'), [IntToStr(ResultCode)]), mbError, MB_OK);
  128. end;
  129. end;
  130. end;
  131. finally
  132. WizardForm.StatusLabel.Caption := StatusText;
  133. WizardForm.ProgressGauge.Style := npbstNormal;
  134. end;
  135. end;
  136. procedure BumpInstallCount;
  137. var
  138. FileContents: AnsiString;
  139. InstallCount: integer;
  140. begin
  141. { Increment the install count in InstallCount.txt if it exists, or create it with the contents '1' if it doesn't }
  142. if LoadStringFromFile(ExpandConstant('{app}\InstallCount.txt'), FileContents) then
  143. begin
  144. InstallCount := StrTointDef(Trim(string(FileContents)), 0) + 1;
  145. end
  146. else
  147. begin
  148. InstallCount := 1;
  149. end;
  150. SaveStringToFile(ExpandConstant('{app}\InstallCount.txt'), IntToStr(InstallCount), False);
  151. end;
  152. procedure URLLabelOnClick(Sender: TObject);
  153. var
  154. ErrorCode: Integer;
  155. begin
  156. ShellExec('open', '{#DonateUrl}', '', '', SW_SHOWNORMAL, ewNoWait, ErrorCode);
  157. end;
  158. procedure InitializeWizard;
  159. var
  160. URLLabel: TNewStaticText;
  161. begin
  162. URLLabel := TNewStaticText.Create(WizardForm);
  163. URLLabel.Caption := 'Donate';
  164. URLLabel.Cursor := crHand;
  165. URLLabel.Parent := WizardForm;
  166. URLLabel.Font.Style := URLLabel.Font.Style + [fsUnderline];
  167. URLLabel.Font.Color := clBlue;
  168. URLLabel.Top := WizardForm.ClientHeight - URLLabel.Height - 15;
  169. URLLabel.Left := ScaleX(10)
  170. URLLabel.OnClick := @URLLabelOnClick;
  171. end;
  172. procedure CurStepChanged(CurStep: TSetupStep);
  173. var
  174. FindRec: TFindRec;
  175. FolderPath: String;
  176. FilePath: String;
  177. ExeConfig: String;
  178. begin
  179. if CurStep = ssInstall then
  180. begin
  181. BumpInstallCount();
  182. { We might be being run from ProcessRunner.exe, *and* we might be trying to update it. Funsies. Let's rename it (which Windows lets us do) }
  183. DeleteFile(ExpandConstant('{app}\ProcessRunner.exe.old'));
  184. RenameFile(ExpandConstant('{app}\ProcessRunner.exe'), ExpandConstant('{app}\ProcessRunner.exe.old'));
  185. Log(ExpandConstant('Looking for resource files in {app}\*'));
  186. { Remove resource files. This means that out-of-date languages will be removed, which (as a last-ditch resore) will alert maintainers that something's wrong }
  187. if FindFirst(ExpandConstant('{app}\*'), FindRec) then
  188. begin
  189. try
  190. repeat
  191. if (FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY <> 0) and (FindRec.Name <> '.') and (FindRec.Name <> '..') then
  192. begin
  193. FolderPath := ExpandConstant('{app}\') + FindRec.Name;
  194. FilePath := FolderPath + '\SyncTrayzor.resources.dll';
  195. if DeleteFile(FilePath) then
  196. begin
  197. Log('Deleted ' + FilePath);
  198. if DelTree(FolderPath, True, False, False) then
  199. Log('Deleted ' + FolderPath);
  200. end;
  201. end;
  202. until not FindNext(FindRec);
  203. finally
  204. FindClose(FindRec);
  205. end;
  206. end;
  207. end
  208. else if CurStep = ssPostInstall then
  209. begin
  210. ExeConfig := ExpandConstant('{param:SyncTrayzorExeConfig}');
  211. if ExeConfig <> '' then
  212. begin
  213. if FileExists(ExeConfig) then
  214. begin
  215. FileCopy(ExeConfig, ExpandConstant('{app}\SyncTrayzor.exe.config'), false);
  216. end
  217. else
  218. begin
  219. MsgBox('Could not find SyncTrayzorExeConfig file: ' + ExeConfig + '. Using default.', mbError, MB_OK);
  220. end
  221. end
  222. end
  223. end;
  224. procedure CurPageChanged(CurPageID: Integer);
  225. begin
  226. end;
  227. function PrepareToInstall(var NeedsRestart: Boolean): String;
  228. begin
  229. // 'NeedsRestart' only has an effect if we return a non-empty string, thus aborting the installation.
  230. // If the installers indicate that they want a restart, this should be done at the end of installation.
  231. // Therefore we set the global 'restartRequired' if a restart is needed, and return this from NeedRestart()
  232. if DotNetIsMissing() then
  233. begin
  234. Result := InstallDotNet();
  235. end;
  236. end;
  237. function NeedRestart(): Boolean;
  238. begin
  239. Result := GlobalRestartRequired;
  240. end;
  241. function ShouldStartSyncTrayzor(): Boolean;
  242. var
  243. flagPassed: Boolean;
  244. i: Integer;
  245. begin
  246. // Can't use {param}, as it doesn't match flags with no value
  247. flagPassed := False;
  248. for i := 0 to ParamCount do begin
  249. if ParamStr(i) = '/StartSyncTrayzor' then begin
  250. flagPassed := True;
  251. break;
  252. end;
  253. end;
  254. Result := (not WizardSilent()) or flagPassed;
  255. end;
  256. function SyncTrayzorStartFlags(param: String): String;
  257. begin
  258. if WizardSilent() then begin
  259. Result := '-minimized'
  260. end else begin
  261. Result := ''
  262. end;
  263. end;
  264. function SerializeBool(value: Boolean): String;
  265. begin
  266. if value then begin
  267. Result := 'true';
  268. end else begin
  269. Result := 'false';
  270. end
  271. end;
  272. function EscapeJsonString(value: String): String;
  273. var
  274. c: Char;
  275. i: Integer;
  276. begin
  277. // http://www.jrsoftware.org/ishelp/index.php?topic=isxfunc_charlength
  278. i := 1;
  279. while i <= Length(value) do
  280. begin
  281. c := value[i];
  282. if c = #10 then begin
  283. Result := Result + '\n';
  284. end else if c = #13 then begin
  285. Result := Result + '\r';
  286. end else if c = #9 then begin
  287. Result := Result + '\t';
  288. end else if Ord(c) < 32 then begin
  289. Result := Result + Format('\x%.4x', [Ord(c)]);
  290. end else if c = '"' then begin
  291. Result := Result + '\"';
  292. end else if c = '\' then begin
  293. Result := Result + '\\'
  294. end else begin
  295. Result := Result + c;
  296. end;
  297. i := i + CharLength(value, i);
  298. end;
  299. end;
  300. // See https://stackoverflow.com/a/42550055/1086121
  301. procedure UpdateUninstallWizard;
  302. begin
  303. if UninstallProgressForm.InnerNotebook.ActivePage = UninstallPollPage then
  304. begin
  305. UninstallProgressForm.PageNameLabel.Caption := 'Please Tell Us Why You''re Leaving';
  306. UninstallProgressForm.PageDescriptionLabel.Caption := '';
  307. end;
  308. UninstallNextButton.Caption := 'Uninstall';
  309. // Make the "Uninstall" button break the ShowModal loop
  310. UninstallNextButton.ModalResult := mrOK;
  311. end;
  312. procedure InitializeUninstallProgressForm();
  313. var
  314. PageText: TNewStaticText;
  315. PageNameLabel: string;
  316. PageDescriptionLabel: string;
  317. CancelButtonEnabled: Boolean;
  318. CancelButtonModalResult: Integer;
  319. Checklist: TNewCheckListBox;
  320. CommentsText: TNewStaticText;
  321. CommentsBox: TNewMemo;
  322. WinHttpReq: Variant;
  323. begin
  324. if not UninstallSilent then
  325. begin
  326. // Create the poll page and make it active
  327. UninstallPollPage := TNewNotebookPage.Create(UninstallProgressForm);
  328. UninstallPollPage.Notebook := UninstallProgressForm.InnerNotebook;
  329. UninstallPollPage.Parent := UninstallProgressForm.InnerNotebook;
  330. UninstallPollPage.Align := alClient;
  331. PageText := TNewStaticText.Create(UninstallProgressForm);
  332. PageText.Parent := UninstallPollPage;
  333. PageText.AutoSize := True;
  334. PageText.WordWrap := True;
  335. PageText.SetBounds(UninstallProgressForm.StatusLabel.Left, UninstallProgressForm.StatusLabel.Top, UninstallProgressForm.StatusLabel.Width, UninstallProgressForm.StatusLabel.Height);
  336. PageText.ShowAccelChar := False;
  337. PageText.Caption := 'Sorry you''re leaving! Please tell us what you didn''t like so we can improve it.' + #13#10 +
  338. 'No personal data will be sent. You can skip this step if you want.';
  339. Checklist := TNewCheckListBox.Create(UninstallProgressForm);
  340. Checklist.Parent := UninstallPollPage;
  341. Checklist.SetBounds(PageText.Left, PageText.Top + PageText.Height + ScaleY(10), PageText.Width, ScaleY(20) * 5);
  342. Checklist.BorderStyle := bsNone;
  343. Checklist.Color := clBtnFace;
  344. Checklist.WantTabs := True;
  345. Checklist.MinItemHeight := ScaleY(20);
  346. Checklist.AddCheckBox('I couldn''t get Syncthing to work', '', 0, False, True, False, False, nil);
  347. Checklist.AddCheckBox('Syncthing doesn''t do what I need', '', 0, False, True, False, False, nil);
  348. Checklist.AddCheckBox('I prefer another sync tool (please say which below)', '', 0, False, True, False, False, nil);
  349. Checklist.AddCheckBox('I don''t like SyncTrayzor - I''m going to use another wrapper', '', 0, False, True, False, False, nil);
  350. Checklist.AddCheckBox('Other (please expand below)', '', 0, False, True, False, False, nil);
  351. CommentsText := TNewStaticText.Create(UninstallProgressForm);
  352. CommentsText.Parent := UninstallPollPage;
  353. CommentsText.AutoSize := True;
  354. CommentsText.WordWrap := True;
  355. CommentsText.SetBounds(PageText.Left, Checklist.Top + Checklist.Height + ScaleY(10), PageText.Width, ScaleY(15));
  356. CommentsText.AutoSize := False;
  357. CommentsText.ShowAccelChar := False;
  358. CommentsText.Caption := 'More Details / Complaints:';
  359. CommentsBox := TNewMemo.Create(UninstallProgressForm);
  360. CommentsBox.Parent := UninstallPollPage;
  361. CommentsBox.SetBounds(PageText.Left, CommentsText.Top + CommentsText.Height + ScaleY(5), PageText.Width, ScaleY(60));
  362. CommentsBox.ScrollBars := ssVertical;
  363. UninstallProgressForm.InnerNotebook.ActivePage := UninstallPollPage;
  364. PageNameLabel := UninstallProgressForm.PageNameLabel.Caption;
  365. PageDescriptionLabel := UninstallProgressForm.PageDescriptionLabel.Caption;
  366. UninstallNextButton := TNewButton.Create(UninstallProgressForm);
  367. UninstallNextButton.Parent := UninstallProgressForm;
  368. UninstallNextButton.Left := UninstallProgressForm.CancelButton.Left - UninstallProgressForm.CancelButton.Width - ScaleX(10);
  369. UninstallNextButton.Top := UninstallProgressForm.CancelButton.Top;
  370. UninstallNextButton.Width := UninstallProgressForm.CancelButton.Width;
  371. UninstallNextButton.Height := UninstallProgressForm.CancelButton.Height;
  372. UninstallProgressForm.CancelButton.TabOrder := UninstallNextButton.TabOrder + 1;
  373. // Run our wizard pages
  374. UpdateUninstallWizard;
  375. CancelButtonEnabled := UninstallProgressForm.CancelButton.Enabled
  376. UninstallProgressForm.CancelButton.Enabled := True;
  377. CancelButtonModalResult := UninstallProgressForm.CancelButton.ModalResult;
  378. UninstallProgressForm.CancelButton.ModalResult := mrCancel;
  379. if UninstallProgressForm.ShowModal = mrCancel then Abort;
  380. if Checklist.Checked[0] or Checklist.Checked[1] or Checklist.Checked[2] or Checklist.Checked[3] or Checklist.Checked[4] or (CommentsBox.Text <> '') then begin
  381. try
  382. begin
  383. WinHttpReq := CreateOleObject('WinHttp.WinHttpRequest.5.1');
  384. WinHttpReq.Open('POST', '{#SurveyUrl}', false);
  385. WinHttpReq.Send('{' +
  386. ' "version": "{#AppVersion}"' +
  387. ', "comment": "' + EscapeJsonString(CommentsBox.Text) + '"' +
  388. ', "checklist": {' +
  389. ' "wontWork": '+ SerializeBool(Checklist.Checked[0]) +
  390. ', "notWhatINeed": '+ SerializeBool(Checklist.Checked[1]) +
  391. ', "preferAnotherSyncTool": '+ SerializeBool(Checklist.Checked[2]) +
  392. ', "dontLikeSyncTrayzor": '+ SerializeBool(Checklist.Checked[3]) +
  393. ', "other": '+ SerializeBool(Checklist.Checked[4]) +
  394. ' }' +
  395. ' }');
  396. end except
  397. end;
  398. end;
  399. // Restore the standard page payout
  400. UninstallProgressForm.CancelButton.Enabled := CancelButtonEnabled;
  401. UninstallProgressForm.CancelButton.ModalResult := CancelButtonModalResult;
  402. UninstallProgressForm.PageNameLabel.Caption := PageNameLabel;
  403. UninstallProgressForm.PageDescriptionLabel.Caption := PageDescriptionLabel;
  404. UninstallProgressForm.InnerNotebook.ActivePage := UninstallProgressForm.InstallingPage;
  405. end;
  406. end;
  407. [UninstallDelete]
  408. Type: files; Name: "{app}\ProcessRunner.exe.old"
  409. Type: files; Name: "{app}\InstallCount.txt"
  410. Type: filesandordirs; Name: "{userappdata}\{#AppDataFolder}"
  411. Type: filesandordirs; Name: "{localappdata}\{#AppDataFolder}"