Advertisement
Guest User

Untitled

a guest
Mar 22nd, 2018
92
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 38.85 KB | None | 0 0
  1. procedure InitializeWizard();
  2. begin
  3. HasElevateSwitch := CmdLineParamExists('/ELEVATE');
  4. Log(Format('HasElevateSwitch: %d', [HasElevateSwitch]));
  5. Log(Format('IsElevated: %d', [IsElevated()]));
  6.  
  7. //Assume we are installing for all users if we were elevated
  8. ShouldInstallAllUsers := HasElevateSwitch;
  9.  
  10. InstallForWhoOptionPage :=
  11. CreateInputOptionPage(
  12. wpWelcome,
  13. ExpandConstant('{cm:InstallPerUserOrAllUsersCaption}'),
  14. ExpandConstant('{cm:InstallPerUserOrAllUsersMessage}'),
  15. ExpandConstant('{cm:InstallPerUserOrAllUsersAdminDescription}'),
  16. True, False);
  17. InstallForWhoOptionPage.Add(ExpandConstant('{cm:InstallPerUserOrAllUsersAdminButtonCaption}'));
  18. InstallForWhoOptionPage.Add(ExpandConstant('{cm:InstallPerUserOrAllUsersUserButtonCaption}'));
  19.  
  20. if PerUserOnly then
  21. begin
  22. InstallForWhoOptionPage.Values[1] := true;
  23. InstallForWhoOptionPage.CheckListBox.ItemEnabled[0] := false;
  24. end
  25. else if IsElevated() then
  26. begin
  27. InstallForWhoOptionPage.Values[0] := true;
  28. InstallForWhoOptionPage.CheckListBox.ItemEnabled[1] := false;
  29. end
  30. else
  31. begin
  32. InstallForWhoOptionPage.Values[1] := true;
  33. end;
  34.  
  35. RegisterAddInOptionPage :=
  36. CreateInputOptionPage(
  37. wpInstalling,
  38. ExpandConstant('{cm:RegisterAddInCaption}'),
  39. ExpandConstant('{cm:RegisterAddInMessage}'),
  40. ExpandConstant('{cm:RegisterAddInDescription}'),
  41. False, False);
  42.  
  43. RegisterAddInOptionPage.Add(ExpandConstant('{cm:RegisterAddInButtonCaption}'));
  44. RegisterAddInOptionPage.CheckListBox.ItemEnabled[0] := not IsElevated();
  45. RegisterAddInOptionPage.Values[0] := not IsElevated();
  46. end;
  47.  
  48. function GetUninstallString(AppId: string): String;
  49. var
  50. sAppId: string;
  51. sUnInstPath: String;
  52. sUnInstallString: String;
  53. begin
  54. if AppId = '' then
  55. sAppId := GetAppId('')
  56. else
  57. sAppId := AppId;
  58.  
  59. sUnInstPath := 'SoftwareMicrosoftWindowsCurrentVersionUninstall' + sAppId + '_is1';
  60. Log('Looking in registry: ' + sUnInstPath);
  61. sUnInstallString := '';
  62. if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
  63. RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
  64. Log('Result of registry query: ' + sUnInstallString);
  65. Result := sUnInstallString;
  66. end;
  67.  
  68. ...
  69.  
  70. function UnInstallOldVersion(AppId: string): integer;
  71. var
  72. sUnInstallString: string;
  73. iResultCode: integer;
  74. begin
  75. // default return value
  76. Result := 0;
  77.  
  78. // get the uninstall string of the old app
  79. sUnInstallString := GetUninstallString(AppId);
  80. if sUnInstallString <> '' then begin
  81. sUnInstallString := RemoveQuotes(sUnInstallString);
  82. if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
  83. Result := 3
  84. else
  85. Result := 2;
  86. end else
  87. Result := 1;
  88. end;
  89.  
  90. EveryoneAppMode = 'Everyone';
  91. EveryoneAppId = '{979AFF96-DD9E-4FC2-802D-9E0C36A60D09}';
  92. PerUserAppMode = 'PerUser';
  93. PerUserAppId = '{DF0E0E6F-2CED-482E-831C-7E9721EB66AA}';
  94.  
  95. ...
  96.  
  97. function GetAppId(AppMode: string): string;
  98. begin
  99. if AppMode = EveryoneAppMode then
  100. result := EveryoneAppId
  101. else if AppMode = PerUserAppMode then
  102. result := PerUserAppId
  103. else
  104. if ShouldInstallAllUsers then
  105. result := EveryoneAppId
  106. else
  107. result := PerUserAppId
  108. end;
  109.  
  110. #pragma include __INCLUDE__ + ";" + SourcePath + "Includes"
  111.  
  112. #define protected
  113. #ifndef Config
  114. #define Config "Debug"
  115. #endif
  116. #define BuildDir ExtractFileDir(ExtractFileDir(SourcePath)) + "bin" + Config + ""
  117. #define IncludesDir SourcePath + "Includes"
  118. #define AppName "Rubberduck"
  119. #define AddinDLL "Rubberduck.dll"
  120. #define Tlb32bit "Rubberduck.x32.tlb"
  121. #define Tlb64bit "Rubberduck.x64.tlb"
  122. #define DllFullPath BuildDir + AddinDLL
  123. #define Tlb32bitFullPath BuildDir + Tlb32bit
  124. #define Tlb64bitFullPath BuildDir + Tlb64bit
  125. #define AppVersion GetFileVersion(BuildDir + "Rubberduck.dll")
  126. #define AppPublisher "Rubberduck"
  127. #define AppURL "http://rubberduckvba.com"
  128. #define License SourcePath + "IncludesLicense.rtf"
  129. #define OutputDirectory SourcePath + "Installers"
  130. #define AddinProgId "Rubberduck.Extension"
  131. #define AddinCLSID "8D052AD8-BBD2-4C59-8DEC-F697CA1F8A66"
  132.  
  133. ; Output the defined constants to aid in verification
  134. #pragma message "Include: " + __INCLUDE__
  135. #pragma message "Config: " + Config
  136. #pragma message "SourcePath: " + SourcePath
  137. #pragma message "BuildDir: " + BuildDir
  138. #pragma message "AppName: " + AppName
  139. #pragma message "AddinDLL: " + AddinDLL
  140. #pragma message "DllFullPath: " + DllFullPath
  141. #pragma message "Tlb32bitFullPath: " + Tlb32bitFullPath
  142. #pragma message "Tlb64bitFullPath: " + Tlb64bitFullPath
  143. #pragma message "AppVersion: " + AppVersion
  144. #pragma message "AppPublisher: " + AppPublisher
  145. #pragma message "AppURL: " + AppURL
  146. #pragma message "License: " + License
  147. #pragma message "OutputDirectory: " + OutputDirectory
  148. #pragma message "AddinProgId: " + AddinProgId
  149. #pragma message "AddinCLSID: " + AddInCLSID
  150.  
  151. [Setup]
  152. ; The Previous language must be no when AppId uses constants
  153. UsePreviousLanguage=no
  154. AppId={code:GetAppId}
  155. AppName={#AppName}
  156. AppVersion={#AppVersion}
  157. AppPublisher={#AppPublisher}
  158. AppPublisherURL={#AppURL}
  159. AppSupportURL={#AppURL}
  160. AppUpdatesURL={#AppURL}
  161. DefaultDirName={code:GetDefaultDirName}
  162. DefaultGroupName=Rubberduck
  163. AllowNoIcons=yes
  164. LicenseFile={#License}
  165. OutputDir={#OutputDirectory}
  166. OutputBaseFilename=Rubberduck.Setup
  167. Compression=lzma
  168. SolidCompression=yes
  169.  
  170. ArchitecturesAllowed=x86 x64
  171. ArchitecturesInstallIn64BitMode=x64
  172.  
  173. SetupLogging=yes
  174. PrivilegesRequired=lowest
  175.  
  176. UninstallFilesDir={app}Installers
  177. UninstallDisplayName={code:GetUninstallDisplayName}
  178. UninstallDisplayIcon=ducky.ico
  179. Uninstallable=ShouldCreateUninstaller()
  180. CreateUninstallRegKey=ShouldCreateUninstaller()
  181.  
  182. ; should be 55 x 58 bitmap; use the included non-default bitamp from IS
  183. WizardSmallImageFile=compiler:WizModernSmallImage-IS.bmp
  184.  
  185. ; should be a 164 x 314 bitmap
  186. WizardImageFile=InstallerBitMap.bmp
  187.  
  188. [Languages]
  189. Name: "English"; MessagesFile: "compiler:Default.isl"
  190. Name: "French"; MessagesFile: "compiler:LanguagesFrench.isl"
  191. Name: "German"; MessagesFile: "compiler:LanguagesGerman.isl"
  192.  
  193. [Files]
  194. ; Install the correct bitness binaries.
  195. Source: "{#BuildDir}*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs replacesameversion; Excludes: "Rubberduck.Deployment.*,Rubberduck.dll.xml,Rubberduck.x32.tlb.xml,{#AddinDLL},NativeBinaries"; Check: CheckShouldInstallFiles
  196. Source: "{#BuildDir}{#AddinDLL}"; DestDir: "{app}"; Flags: ignoreversion replacesameversion; Check: CheckShouldInstallFiles;
  197.  
  198. ; Included only if installed for all users to enable self-registration for other users on machine
  199. Source: "{#IncludesDir}Rubberduck.RegisterAddIn.bat"; DestDir: "{app}"; Flags: ignoreversion replacesameversion; Check: InstallAllUsers;
  200. Source: "{#IncludesDir}Rubberduck.RegisterAddIn.reg"; DestDir: "{app}"; Flags: ignoreversion replacesameversion; Check: InstallAllUsers;
  201.  
  202. [Registry]
  203. ; DO NOT attempt to register VBE Add-In with this section. It doesn't work
  204. ; Use [Code] section (RegisterAddIn procedure) to register the entries instead.
  205. #include <Rubberduck.reg.iss>
  206.  
  207. [UninstallDelete]
  208. Type: filesandordirs; Name: "{userappdata}{#AppName}"
  209.  
  210. [CustomMessages]
  211. ; TODO add additional languages here by adding include files in Includes folder
  212. ; and uncomment or add lines to include the file.
  213. #include <English.CustomMessages.iss>
  214. ; #include <French.CustomMessages.iss>
  215. ; #include <German.CustomMessages.iss>
  216.  
  217. [Icons]
  218. Name: "{group}{cm:ProgramOnTheWeb,{#AppName}}"; Filename: "{#AppURL}"
  219. Name: "{group}{cm:UninstallProgram,{#AppName}}"; Filename: "{uninstallexe}"
  220.  
  221. ; Included only if installed for all users to enable self-registration for other users on machine
  222. Name: "{group}{cm:RegisterAddin, {#AppName}}"; Filename: "{app}Rubberduck.RegisterAddIn.bat"; WorkingDir: "{app}"; Check: InstallAllUsers;
  223.  
  224. [Code]
  225. ///<remarks>
  226. /// The code section is divided into subsections:
  227. /// global declarations of const and variables
  228. /// external functions
  229. /// helper functions
  230. /// event functions
  231. ///</remarks>
  232.  
  233. // Global declarations Section
  234.  
  235. type
  236. HINSTANCE = THandle;
  237.  
  238. const
  239. ///<remarks>
  240. ///Identifiers for installation in everyone mode to support
  241. ///functions that needs to distinguish between install modes.
  242. ///</remarks>
  243. EveryoneAppMode = 'Everyone';
  244. EveryoneAppId = '{979AFF96-DD9E-4FC2-802D-9E0C36A60D09}';
  245. PerUserAppMode = 'PerUser';
  246. PerUserAppId = '{DF0E0E6F-2CED-482E-831C-7E9721EB66AA}';
  247.  
  248. ///<remarks>
  249. ///Pseudo-Bitwise enum to support functions for
  250. ///checking previous versions of different modes.
  251. ///Pascal scripting doesn't have bitwise operators
  252. ///so we improvise....
  253. ///<remarks>
  254. NoneIsInstalled = 0;
  255. PerUserIsInstalled = 1;
  256. EveryoneIsInstalled = 2;
  257. BothAreInstalled = 3;
  258.  
  259. var
  260. ///<remarks>
  261. ///Flag to indicate that we automatically skipped pages during an
  262. ///elevated install.
  263. ///
  264. ///The flag should be only changed within the <see cref="ShouldSkipPage" />
  265. ///event function. Treat it as read-only in all other contexts.
  266. ///</remarks>
  267. PagesSkipped: Boolean;
  268.  
  269. ///<remarks>
  270. ///Custom page to select whether the installer should run for the
  271. ///current user only or for all users (requiring elevation).
  272. ///Configured in the WizardInitialize event function.
  273. ///</remarks>
  274. InstallForWhoOptionPage: TInputOptionWizardPage;
  275.  
  276. ///<remarks>
  277. ///Custom page to indicate whether the installer should create per-user
  278. ///registry key to make VBE addin available to the application.
  279. ///<see cref="RegisterAddIn" />
  280. ///</remakrs>
  281. RegisterAddInOptionPage: TInputOptionWizardPage;
  282.  
  283. ///<remarks>
  284. ///Set by the <see cref="RegisterAddInOptionPage" />; should be
  285. ///read-only normally. When set to true, it is used to check if
  286. ///elevation is needed for the installer.
  287. ///</remarks>
  288. ShouldInstallAllUsers: Boolean;
  289.  
  290. ///<remarks>
  291. ///When the non-elevated installer launches the elevated installer
  292. ///via the <see cref="Elevate" />, it will pass in a switch. This
  293. ///helps us to know that the elevated installer was started in this
  294. ///manner rather than user right-clicking and choosing "Run As Administrator".
  295. ///This mainly influences whether we should skip the pages. Treat it
  296. ///as read-only in all contexts other than <see cref="InitializeWizard" />.
  297. ///<remarks>
  298. HasElevateSwitch : Boolean;
  299.  
  300. ///<remarks>
  301. ///Indicates that the installer can only run in per-user only. This is typically
  302. ///when there is a previous version of Rubberduck and the user either won't or
  303. ///can't uninstall it. Therefore, we cannot safely install using all-user mode
  304. ///in this context. This is set in the <see cref="SetupInitialize" /> event
  305. ///function and should be read-only in all other contexts.
  306. ///</remarks>
  307. PerUserOnly : Boolean;
  308.  
  309. // External functions section
  310.  
  311. ///<remarks>
  312. ///Used to select correct subtype of Win32 API
  313. ///based on the installer's encoding.
  314. ///<remarks>
  315. #ifdef UNICODE
  316. #define AW "W"
  317. #else
  318. #define AW "A"
  319. #endif
  320.  
  321. ///<remarks>
  322. ///Win32 API function used to launch a 2nd instance of the installer
  323. ///under elevated context, used by <see cref="Elevate" />.
  324. ///</remarks>
  325. function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string;
  326. lpParameters: string; lpDirectory: string; nShowCmd: Integer): HINSTANCE;
  327. external 'ShellExecute{#AW}@shell32.dll stdcall';
  328.  
  329. // Helper functions section
  330.  
  331. ///<remarks>
  332. ///Encapuslate the check whether the installer is running in an
  333. ///elevated context or not. This does not indicate whether the
  334. ///installer was launched by a non-elevated installer, however.
  335. ///<see cref="HasElevateSwitch" />
  336. ///</remarks>
  337. function IsElevated: Boolean;
  338. begin
  339. Result := IsAdminLoggedOn or IsPowerUserLoggedOn;
  340. end;
  341.  
  342. ///<remarks>
  343. ///Generic helper function to parse the command line arguments,
  344. ///and indicate whether a particular argument was passed in.
  345. ///</remarks>
  346. function CmdLineParamExists(const Value: string): Boolean;
  347. var
  348. I: Integer;
  349. begin
  350. Result := False;
  351. for I := 1 to ParamCount do
  352. if CompareText(ParamStr(I), Value) = 0 then
  353. begin
  354. Result := True;
  355. Exit;
  356. end;
  357. end;
  358.  
  359. ///<remarks>
  360. ///Used to determine whether a install directory that user
  361. ///selected is in fact writable by the user, especially
  362. ///for non-elevated installation.
  363. ///</remarks>
  364. function HaveWriteAccessToApp: Boolean;
  365. var
  366. FileName: string;
  367. begin
  368. FileName := AddBackslash(WizardDirValue) + 'writetest.tmp';
  369. Result := SaveStringToFile(FileName, 'test', False);
  370. if Result then
  371. begin
  372. Log(Format(
  373. 'Have write aElevationRequiredForSelectedFolderWarningccess to the last installation path [%s]', [WizardDirValue]));
  374. DeleteFile(FileName);
  375. end
  376. else
  377. begin
  378. Log(Format('Does not have write access to the last installation path [%s]', [
  379. WizardDirValue]));
  380. end;
  381. end;
  382.  
  383. ///<remarks>
  384. ///When user requests install for all users or into a protected directory,
  385. ///and the current installer isn't elevated, it will create a second instance
  386. ///of itself, passing in all the switches, and adding the `/ELEVATE` switch
  387. ///to complete the installation of the add-in under the elevated context.
  388. ///The original installer does not terminate but rather remains open for the
  389. ///elevated install to complete so that the user can then proceed with VBE
  390. ///addin registration on the <see cref="RegisterAddInOptionPage" /> page.
  391. ///<remarks>
  392. function Elevate: Boolean;
  393. var
  394. I: Integer;
  395. instance: HINSTANCE;
  396. Params: string;
  397. S: string;
  398. begin
  399. { Collect current instance parameters }
  400. for I := 1 to ParamCount do
  401. begin
  402. S := ParamStr(I);
  403. Log('Parameter: ' + S);
  404. { Unique log file name for the elevated instance }
  405. if CompareText(Copy(S, 1, 5), '/LOG=') = 0 then
  406. begin
  407. S := S + 'elevated';
  408. end;
  409. { Do not pass our /SL5 switch }
  410. if CompareText(Copy(S, 1, 5), '/SL5=') <> 0 then
  411. begin
  412. Params := Params + AddQuotes(S) + ' ';
  413. end;
  414. end;
  415.  
  416. if Pos('/DIR', Params) = 0 then
  417. Params := Params + ExpandConstant('/DIR="{app}" ');
  418. { ... and add selected language }
  419. if Pos('/LANG', Params) = 0 then
  420. Params := Params + '/LANG=' + ActiveLanguage + ' ';
  421. if Pos('/ELEVATE', Params) = 0 then
  422. Params := Params + '/ELEVATE';
  423.  
  424. Log(Format('Elevating setup with parameters [%s]', [Params]));
  425. instance := ShellExecute(0, 'runas', ExpandConstant('{srcexe}'), Params, '', SW_SHOW);
  426. Log(Format('Running elevated setup returned [%d]', [instance]));
  427. Result := (instance > 32);
  428. { if elevated executing of this setup succeeded, then... }
  429. if Result then
  430. begin
  431. Log('Elevation succeeded');
  432. end
  433. else
  434. begin
  435. Log(Format('Elevation failed [%s]', [SysErrorMessage(instance)]));
  436. end;
  437. end;
  438.  
  439. ///<remarks>
  440. ///Adapted from http://kynosarges.org/DotNetVersion.html; used during
  441. ///the <see cref="InitializeSetup"> event function to ensure that the
  442. ///.NET framework is present on the computer.
  443. ///</remarks>
  444. function IsDotNetDetected(version: string; service: cardinal): boolean;
  445. // Indicates whether the specified version and service pack of the .NET Framework is installed.
  446. //
  447. // version -- Specify one of these strings for the required .NET Framework version:
  448. // 'v1.1.4322' .NET Framework 1.1
  449. // 'v2.0.50727' .NET Framework 2.0
  450. // 'v3.0' .NET Framework 3.0
  451. // 'v3.5' .NET Framework 3.5
  452. // 'v4Client' .NET Framework 4.0 Client Profile
  453. // 'v4Full' .NET Framework 4.0 Full Installation
  454. // 'v4.5' .NET Framework 4.5
  455. //
  456. // service -- Specify any non-negative integer for the required service pack level:
  457. // 0 No service packs required
  458. // 1, 2, etc. Service pack 1, 2, etc. required
  459. var
  460. key: string;
  461. install, release, serviceCount: cardinal;
  462. check45, success: boolean;
  463. begin
  464. // .NET 4.5 installs as update to .NET 4.0 Full
  465. if version = 'v4.5' then begin
  466. version := 'v4Full';
  467. check45 := true;
  468. end else
  469. check45 := false;
  470.  
  471. // installation key group for all .NET versions
  472. key := 'SOFTWAREMicrosoftNET Framework SetupNDP' + version;
  473.  
  474. // .NET 3.0 uses value InstallSuccess in subkey Setup
  475. if Pos('v3.0', version) = 1 then begin
  476. success := RegQueryDWordValue(HKLM, key + 'Setup', 'InstallSuccess', install);
  477. end else begin
  478. success := RegQueryDWordValue(HKLM, key, 'Install', install);
  479. end;
  480.  
  481. // .NET 4.0/4.5 uses value Servicing instead of SP
  482. if Pos('v4', version) = 1 then begin
  483. success := success and RegQueryDWordValue(HKLM, key, 'Servicing', serviceCount);
  484. end else begin
  485. success := success and RegQueryDWordValue(HKLM, key, 'SP', serviceCount);
  486. end;
  487.  
  488. // .NET 4.5 uses additional value Release
  489. if check45 then begin
  490. success := success and RegQueryDWordValue(HKLM, key, 'Release', release);
  491. success := success and (release >= 378389);
  492. end;
  493.  
  494. result := success and (install = 1) and (serviceCount >= service);
  495. end;
  496.  
  497. ///<remarks>
  498. ///Helper function used in non-code sections to set the default directory
  499. ///to be installed, based on the elevation context.
  500. ///</remarks>
  501. function GetDefaultDirName(Param: string): string;
  502. begin
  503. if IsElevated() then
  504. begin
  505. Result := ExpandConstant('{commonappdata}{}{#AppName}');
  506. end
  507. else
  508. begin
  509. Result := ExpandConstant('{localappdata}{}{#AppName}')
  510. end;
  511. end;
  512.  
  513. ///<remarks>
  514. ///Helper function used in non-code sections to provide the path that was
  515. ///either set by installer by default or customized by the user.
  516. ///</remarks>
  517. function GetInstallPath(Unused: string): string;
  518. begin
  519. result := ExpandConstant('{app}');
  520. end;
  521.  
  522. ///<remarks>
  523. ///Helper function used in non-code sections to provide the full path to
  524. ///the Rubberduck main DLL, based on the same path as <see cref="GetInstallPath" />
  525. ///</remarks>
  526. function GetDllPath(Unused: string): string;
  527. begin
  528. result := ExpandConstant('{app}') + 'Rubberduck.dll';
  529. end;
  530.  
  531. ///<remarks>
  532. ///Helper function used in non-code sections to provide the full path to
  533. ///the 32-bit type library for Rubberduck main DLL, based on teh same path
  534. ///as <cref="GetInstallPath" />
  535. ///</remarks>
  536. function GetTlbPath32(Unused: string): string;
  537. begin
  538. result := ExpandConstant('{app}') + 'Rubberduck.x32.tlb';
  539. end;
  540.  
  541. ///<remarks>
  542. ///Same as <see cref="GetTlbPath32" /> but for 64-bit.
  543. ///</remarks>
  544. function GetTlbPath64(Unused: string): string;
  545. begin
  546. result := ExpandConstant('{app}') + 'Rubberduck.x64.tlb';
  547. end;
  548.  
  549. ///<remarks>
  550. ///Helper function used in the Registry section to indicate whether
  551. ///the item should be installed. For example, HKLM registry requires
  552. ///elevated context, so the function must return true, while HKCU
  553. ///counterpart will expect the opposite to be installed. This prevents
  554. ///comparable item being installed into both places.
  555. ///</remarks>
  556. function InstallAllUsers():boolean;
  557. begin
  558. result := ShouldInstallAllUsers and IsElevated();
  559. end;
  560.  
  561. ///<remarks>
  562. ///Helper function used in the File section to assess whether an
  563. ///file(s) should be installed based on whether there's privilege
  564. ///to do so. This guards against the case of both the non-elevated
  565. ///installer and the elevated installer installing same files into
  566. ///same place, which will cause problems. This function ensures only
  567. ///one or other mode will actually install the files.
  568. ///</remarks>
  569. function CheckShouldInstallFiles():boolean;
  570. begin
  571. if ShouldInstallAllUsers then
  572. result := IsElevated()
  573. else
  574. result := true;
  575. end;
  576.  
  577. ///<remarks>
  578. ///Used by <see cref="RegisterAddIn" />, passing in parameters to actually create
  579. ///the per-user registry entries to enable VBE addin for that user.
  580. ///<remarks>
  581. procedure RegisterAddinForIDE(const iRootKey: Integer; const sAddinSubKey: String; const sProgIDConnect: String);
  582. begin
  583. RegWriteStringValue(iRootKey, sAddinSubKey + '' + sProgIDConnect, 'FriendlyName', '{#AppName}');
  584. RegWriteStringValue(iRootKey, sAddinSubKey + '' + sProgIDConnect, 'Description' , '{#AppName}');
  585. RegWriteDWordValue (iRootKey, sAddinSubKey + '' + sProgIDConnect, 'LoadBehavior', 3);
  586. end;
  587.  
  588. ///<remarks>
  589. ///Unregisters the same keys, similar to <see cref="RegisterAddinForIDE" />
  590. ///</remarks>
  591. procedure UnregisterAddinForIDE(const iRootKey: Integer; const sAddinSubKey: String; const sProgIDConnect: String);
  592. begin
  593. if RegKeyExists(iRootKey, sAddinSubKey + '' + sProgIDConnect) then
  594. RegDeleteKeyIncludingSubkeys(iRootKey, sAddinSubKey + '' + sProgIDConnect);
  595. end;
  596.  
  597. ///<remarks>
  598. ///Called after successfully installing, including via the elevated installer
  599. ///to register the VBE addin. Should be never run under elevated context
  600. ///or the registration may not work as expected.
  601. ///</remarks>
  602. procedure RegisterAddin();
  603. begin
  604. if not IsElevated() then
  605. begin
  606. RegisterAddinForIDE(HKCU32, 'SoftwareMicrosoftVBAVBE6.0Addins', '{#AddinProgId}');
  607.  
  608. if IsWin64() then
  609. RegisterAddinForIDE(HKCU64, 'SoftwareMicrosoftVBAVBE6.0Addins64', '{#AddinProgId}');
  610. end;
  611. end;
  612.  
  613. ///<remarks>
  614. ///Delete registry keys created by <see cref="RegisterAddin" />
  615. ///</remarks>
  616. procedure UnregisterAddin();
  617. begin
  618. UnregisterAddinForIDE(HKCU32, 'SoftwareMicrosoftVBAVBE6.0Addins', '{#AddinProgId}');
  619. if IsWin64() then
  620. UnregisterAddinForIDE(HKCU64, 'SoftwareMicrosoftVBAVBE6.0Addins64', '{#AddinProgId}');
  621. end;
  622.  
  623. ///<remarks>
  624. ///Generate AppId based on whether it's going to be
  625. ///per-user or for all users. This enable separate
  626. ///install/uninstall of each mode. Used by AppId
  627. ///directive in the [Setup] section.
  628. ///</remarks>
  629. ///<param name="AppMode">
  630. ///If value is 'peruser', then returns per-user AppId
  631. ///If value is 'everyone', then returns everyone AppId
  632. ///Otherwise if left blank or contains invalid value, returns
  633. ///the AppId based on ShouldInstallAllUsers
  634. ///(e.g. based on user's selection.)
  635. ///</param>
  636. function GetAppId(AppMode: string): string;
  637. begin
  638. if AppMode = EveryoneAppMode then
  639. result := EveryoneAppId
  640. else if AppMode = PerUserAppMode then
  641. result := PerUserAppId
  642. else
  643. if ShouldInstallAllUsers then
  644. result := EveryoneAppId
  645. else
  646. result := PerUserAppId
  647. end;
  648.  
  649. ///<remarks>
  650. ///Used to help disambiguate multiple installs of Rubberduck
  651. ///by providing a suffix to indicate which mode it is
  652. ///</remarks>
  653. function GetAppSuffix(): string;
  654. begin
  655. if ShouldInstallAllUsers then
  656. result := ExpandConstant('{cm:Everyone}')
  657. else
  658. result := ExpandConstant('{cm:PerUser}');
  659. end;
  660.  
  661. ///<remakrs>
  662. ///Provide a suffixed name of uninstaller to help identify what mode
  663. ///the version is installed in.
  664. ///</remarks>
  665. function GetUninstallDisplayName(Unused: string): string;
  666. begin
  667. result := ExpandConstant('{#AppName} (') + GetAppSuffix() + ') {#AppVersion}';
  668. end;
  669.  
  670. ///<remarks>
  671. ///Prevent creating an uninstaller from the non-elevated installer
  672. ///The elevated installer will do it instead. Otherwise, we get
  673. ///weird behavior & errors when uninstalling mixed mode.
  674. ///</remarks>
  675. function ShouldCreateUninstaller(): boolean;
  676. begin
  677. if not IsElevated() and ShouldInstallAllUsers then
  678. result:= false
  679. else
  680. result:= true;
  681. end;
  682.  
  683. ///<remarks>
  684. ///Deterimine if there is a previous version of Rubberduck installed
  685. ///All uninstaller will store a registry key with the AppId, so we can
  686. ///use AppId to detect previous versions.
  687. ///</remarks>
  688. ///<param name="AppId">
  689. ///If contains a valid AppId, attempt to get uninstaller for that.
  690. ///If left blank, attempt to get uninstaller for current mode.
  691. ///</param>
  692. function GetUninstallString(AppId: string): String;
  693. var
  694. sAppId: string;
  695. sUnInstPath: String;
  696. sUnInstallString: String;
  697. begin
  698. if AppId = '' then
  699. sAppId := GetAppId('')
  700. else
  701. sAppId := AppId;
  702.  
  703. sUnInstPath := 'SoftwareMicrosoftWindowsCurrentVersionUninstall' + sAppId + '_is1';
  704. Log('Looking in registry: ' + sUnInstPath);
  705. sUnInstallString := '';
  706. if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
  707. RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
  708. Log('Result of registry query: ' + sUnInstallString);
  709. Result := sUnInstallString;
  710. end;
  711.  
  712. ///<remarks>
  713. ///Encapuslates the check for previous versions
  714. ///from <see cref="GetUninstallString" /> as a
  715. ///boolean result
  716. ///</remarks>
  717. function IsUpgrade(): boolean;
  718. begin
  719. result := (GetUninstallString('') <> '');
  720. end;
  721.  
  722. ///<reamrks>
  723. ///An expanded version of IsUpgrade function, usuable only
  724. ///within [Code] section that additional provides information
  725. ///for all modes of installation.
  726. ///</remarks>
  727. ///<returns>
  728. ///An integer representing the fake ***IsInstalled enum
  729. ///</returns>
  730. function IsUpgradeApp(): integer;
  731. var
  732. PerUserExists: boolean;
  733. EveryoneExists: boolean;
  734. begin
  735. PerUserExists := (GetUninstallString(PerUserAppId) <> '');
  736. EveryoneExists := (GetUninstallString(EveryoneAppId) <> '')
  737.  
  738. if PerUserExists and EveryoneExists then
  739. result := BothAreInstalled
  740. else if PerUserExists and not EveryoneExists then
  741. result := PerUserIsInstalled
  742. else if not PerUserExists and EveryoneExists then
  743. result := EveryoneIsInstalled
  744. else
  745. result := NoneIsInstalled;
  746. end;
  747.  
  748. ///<remarks>
  749. ///Perform uninstall of old versions. Called in the
  750. ///<see cref="CurStepChanged" /> event function and only when
  751. ///a previous version was detected.
  752. ///</remarks>
  753. ///<param name="AppId">
  754. ///If non-blank, uninstall the specific AppId.
  755. ///Otherwise, use the selected mode to uninstall.
  756. ///</param>
  757. ///<returns>
  758. /// Return Values:
  759. /// 1 - uninstall string is empty
  760. /// 2 - error executing the UnInstallString
  761. /// 3 - successfully executed the UnInstallString
  762. ///</returns>
  763. function UnInstallOldVersion(AppId: string): integer;
  764. var
  765. sUnInstallString: string;
  766. iResultCode: integer;
  767. begin
  768. // default return value
  769. Result := 0;
  770.  
  771. // get the uninstall string of the old app
  772. sUnInstallString := GetUninstallString(AppId);
  773. if sUnInstallString <> '' then begin
  774. sUnInstallString := RemoveQuotes(sUnInstallString);
  775. if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
  776. Result := 3
  777. else
  778. Result := 2;
  779. end else
  780. Result := 1;
  781. end;
  782.  
  783. ///<remarks>
  784. ///Encapuslates the UI interaction with user to see whether to uninstall
  785. ///the previous verison of a given mode.
  786. ///<remakrs>
  787. ///<param name = "AppMode">
  788. ///Indicate which app mode to uninstall
  789. ///</param>
  790. ///<returns>
  791. ///Boolean indicating whether it was uninstalled succesffully.
  792. ///</returns>
  793. function PromptToUninstall(AppMode: string): boolean;
  794. var
  795. ErrorCode: integer;
  796. begin
  797. Log('A previous version of ' + AppMode + ' was detected; prompting the user whether to uninstall');
  798. if IDYES = MsgBox(Format(ExpandConstant('{cm:UninstallOldVersionPrompt}'), [AppMode]), mbConfirmation, MB_YESNO) then
  799. begin
  800. ErrorCode := -1;
  801. ErrorCode := UnInstallOldVersion(GetAppId(AppMode));
  802. Log(Format('The result of UninstallOldVersion for %s was %d.', [AppMode, ErrorCode]));
  803.  
  804. if ErrorCode <> 3 then
  805. MsgBox(ExpandConstant('{cm:UninstallOldVersionFail}'), mbError, MB_OK);
  806. result := (ErrorCode = 3);
  807. end
  808. else
  809. begin
  810. Log('Uninstall of previous version (%s) was declined by the user.');
  811. result := false;
  812. end;
  813. end;
  814.  
  815. // Event functions called by Inno Setup
  816. //
  817. // NOTE: the ordering should be preserved to indicate the general sequence
  818. // that the Inno Setup will undergo for each event it calls. Note that
  819. // for certain events, it may be called more than once.
  820.  
  821. ///<remarks>
  822. ///This is the first event of the installer, fires prior to the wizard
  823. ///being initialized. This is primarily used to validate that the
  824. ///pre-requisites are met, in this case, pre-existence of .NET framework.
  825. ///</remarks>
  826. function InitializeSetup(): Boolean;
  827. var
  828. ErrorCode: Integer;
  829. begin
  830. // MS .NET Framework 4.5 must be installed for this application to work.
  831. if not IsDotNetDetected('v4.5', 0) then
  832. begin
  833. Log('User does not have the prerequisite .NET framework installed');
  834. MsgBox(ExpandConstant('{cm:NETFramework40NotInstalled}'), mbCriticalError, mb_Ok);
  835. ShellExec('open', 'http://msdn.microsoft.com/en-us/netframework/aa731542', '', '', SW_SHOW, ewNoWait, ErrorCode);
  836. Result := False;
  837. end
  838. else
  839. begin
  840. Log('.Net v4.5 Framework was found on the system');
  841. Result := True;
  842. end;
  843. end;
  844.  
  845. ///<remarks>
  846. ///The second event of installer allow us to customize the wizard by
  847. ///assessing whether we were launched in elevated context from an
  848. ///non-elevated installer; <see cref="HasElevateSwitch" />. We then
  849. ///set up the <see cref="InstallForWhoOptionPage" /> and
  850. ///<see cref="RegisterAddInOptionPage" /> pages. In both cases, their
  851. ///behavior differs depending on whether we are elevated, and need to be
  852. ///configured accordingly.
  853. ///</remarks>
  854. procedure InitializeWizard();
  855. begin
  856. HasElevateSwitch := CmdLineParamExists('/ELEVATE');
  857. Log(Format('HasElevateSwitch: %d', [HasElevateSwitch]));
  858. Log(Format('IsElevated: %d', [IsElevated()]));
  859.  
  860. //Assume we are installing for all users if we were elevated
  861. ShouldInstallAllUsers := HasElevateSwitch;
  862.  
  863. InstallForWhoOptionPage :=
  864. CreateInputOptionPage(
  865. wpWelcome,
  866. ExpandConstant('{cm:InstallPerUserOrAllUsersCaption}'),
  867. ExpandConstant('{cm:InstallPerUserOrAllUsersMessage}'),
  868. ExpandConstant('{cm:InstallPerUserOrAllUsersAdminDescription}'),
  869. True, False);
  870.  
  871. InstallForWhoOptionPage.Add(ExpandConstant('{cm:InstallPerUserOrAllUsersAdminButtonCaption}'));
  872. InstallForWhoOptionPage.Add(ExpandConstant('{cm:InstallPerUserOrAllUsersUserButtonCaption}'));
  873.  
  874. if PerUserOnly then
  875. begin
  876. InstallForWhoOptionPage.Values[1] := true;
  877. InstallForWhoOptionPage.CheckListBox.ItemEnabled[0] := false;
  878. end
  879. else if IsElevated() then
  880. begin
  881. InstallForWhoOptionPage.Values[0] := true;
  882. InstallForWhoOptionPage.CheckListBox.ItemEnabled[1] := false;
  883. end
  884. else
  885. begin
  886. InstallForWhoOptionPage.Values[1] := true;
  887. end;
  888.  
  889. RegisterAddInOptionPage :=
  890. CreateInputOptionPage(
  891. wpInstalling,
  892. ExpandConstant('{cm:RegisterAddInCaption}'),
  893. ExpandConstant('{cm:RegisterAddInMessage}'),
  894. ExpandConstant('{cm:RegisterAddInDescription}'),
  895. False, False);
  896.  
  897. RegisterAddInOptionPage.Add(ExpandConstant('{cm:RegisterAddInButtonCaption}'));
  898. RegisterAddInOptionPage.CheckListBox.ItemEnabled[0] := not IsElevated();
  899. RegisterAddInOptionPage.Values[0] := not IsElevated();
  900. end;
  901.  
  902. ///<remarks>
  903. ///This is called prior to load of the next page in question
  904. ///and is fired for each page we are about to visit.
  905. ///Normally we don't skip unless we have the elevate switch in
  906. ///which case the elevated installer already has all the user's
  907. ///input so it needs not ask users for those again, making it quick
  908. ///to run the elevated installer when elevated from the non-elevated
  909. ///installer. Otherwise, we verify whether we need to show the
  910. ///<see cref="RegisterAddInOptionPage" /> which won't be if the installer
  911. ///is elevated (irrespective whether by the switch or directly by user)
  912. ///as installing addin registry keys will not work under an elevated context.
  913. ///</remarks>
  914. function ShouldSkipPage(PageID: Integer): Boolean;
  915. begin
  916. // if we've executed this instance as elevated, skip pages unless we're
  917. // on the directory selection page
  918. Result := not PagesSkipped and HasElevateSwitch and IsElevated() and (PageID <> wpReady);
  919. // if we've reached the Ready page, set our flag variable to avoid skipping further pages.
  920. if not Result then
  921. begin
  922. Log('PageSkipped set to true now');
  923. PagesSkipped := True;
  924. end;
  925.  
  926. // If the installer is elevated, we cannot register the addin so we must skip the
  927. // custom page.
  928. if (PageID = RegisterAddInOptionPage.ID) and IsElevated() then
  929. begin
  930. Log('RegisterAddInOptionPage skipped because we are running elevated.');
  931. Result := true;
  932. end;
  933.  
  934. // We don't need to show the users finished panel from the elevated installer
  935. // if they already have the non-elevated installer running.
  936. if (PageID = wpFinished) and HasElevateSwitch then
  937. begin
  938. Log('Skipping Finished page because we are running with /ELEVATE switch');
  939. Result := true;
  940. end;
  941. end;
  942.  
  943. ///<remarks>
  944. ///This is called once the user has clicked on the Next button on the wizard
  945. ///and is called for each page. Thus, we have basically a switch for different
  946. ///page, then we assess what we need to do.
  947. ///<remarks>
  948. function NextButtonClick(CurPageID: Integer): Boolean;
  949. var
  950. RetVal: HINSTANCE;
  951. UpgradeResult: integer;
  952. begin
  953. // Prevent accidental extra clicks
  954. Wizardform.NextButton.Enabled := False;
  955.  
  956. // We should assume true because a false value will cause the
  957. // installer to stay on the same page, which may not be desirable
  958. // due to several branching in this prcocedure.
  959. Result := true;
  960.  
  961. // We need to assess whether user might have selected some other directory
  962. // and whether we need to elevate in order to write to it. If elevation is
  963. // required, we need to confirm with the users. The actual elevation comes later.
  964. if CurPageID = wpSelectDir then
  965. begin
  966. if not ShouldInstallAllUsers then
  967. begin
  968. if not HaveWriteAccessToApp() then
  969. begin
  970. if IDYES = MsgBox(ExpandConstant('{cm:ElevationRequiredForSelectedFolderWarning}'), mbConfirmation, MB_YESNO) then
  971. begin
  972. Log('Setting ShouldIntallAllUsers to true because we need elevation to write to selected directory');
  973. ShouldInstallAllUsers := true;
  974. end
  975. else
  976. begin
  977. Log('User declined to elevate the permission so we will remain on wpSelectDir page to allow user to change the selection');
  978. Result := false
  979. end;
  980. end;
  981. end;
  982. end
  983. // If we need elevation, we will invoke the <see cref="Elevate" />
  984. // and verify we are able to do so. Failure should keep user on the
  985. // same page as the user can retry or cancel out from the non-elevated
  986. // installer.
  987. //
  988. // We also need to verify there are no previous versions and if there are,
  989. // to uninstall them.
  990. else if CurPageID = wpReady then
  991. begin
  992. // Log all output of functions called by non-code sections
  993. Log('GetInstallPath: ' + GetInstallPath(''));
  994. Log('GetDllPath: ' + GetDllPath(''));
  995. Log('GetTlbPath32: ' + GetTlbPath32(''));
  996. Log('GetTlbPath64: ' + GetTlbPath64(''));
  997. Log(Format('InstallAllUsers: %d', [InstallAllUsers()]));
  998. Log(Format('CheckShouldInstallFiles: %d', [CheckShouldInstallFiles()]));
  999. Log(Format('GetAppId: %s', [GetAppId('')]));
  1000. Log(Format('AppSuffix: %s', [GetAppSuffix()]));
  1001. Log(Format('ShouldCreateUninstaller: %d', [ShouldCreateUninstaller()]));
  1002. Log(Format('ShouldInstallAllUsers variable: %d', [ShouldInstallAllUsers]));
  1003.  
  1004. if not IsElevated() and ShouldInstallAllUsers then
  1005. begin
  1006. Log('All-users install is required but we don''t have privilege; requesting elevation...');
  1007. if not Elevate() then
  1008. begin
  1009. Log('Elevation failed or was cancelled; we cannot continue.');
  1010. Result := False;
  1011. MsgBox(Format(ExpandConstant('{cm:ElevationRequestFailMessage}'), [RetVal]), mbError, MB_OK);
  1012. end;
  1013. end;
  1014. end
  1015. // We should set the selected directory (WizardForm.DirEdit) with
  1016. // appropriate default directory depending on whether the user is
  1017. // running the installer as elevated or not.
  1018. else if CurPageID = InstallForWhoOptionPage.ID then
  1019. begin
  1020. if InstallForWhoOptionPage.Values[1] then
  1021. begin
  1022. ShouldInstallAllUsers := False;
  1023. WizardForm.DirEdit.Text := ExpandConstant('{localappdata}{}{#AppName}')
  1024. Log('ShouldInstallAllUsers set to false because we chose You Only option');
  1025. end
  1026. else
  1027. begin
  1028. ShouldInstallAllUsers := True;
  1029. WizardForm.DirEdit.Text := ExpandConstant('{commonappdata}{}{#AppName}');
  1030. Log('ShouldInstallAllUsers set to true because we chose All Users options');
  1031. end;
  1032. Log(Format('Selected default directory: %s', [WizardForm.DirEdit.Text]));
  1033.  
  1034. //We need to check whether there's previous version to be uninstalled
  1035. //We have to do it early enough or the installer will try to use the
  1036. //previous version's directory, which will basically ignore the directory
  1037. //being selected.
  1038. UpgradeResult := IsUpgradeApp();
  1039.  
  1040. if (UpgradeResult > NoneIsInstalled) then
  1041. begin
  1042. if IsElevated() then
  1043. begin
  1044. // Uninstall per user; continue regardless of result
  1045. if (UpgradeResult = PerUserIsInstalled) or (UpgradeResult = BothAreInstalled) then
  1046. PromptToUninstall(PerUserAppMode);
  1047.  
  1048. // Uninstall all users; must succeed to continue
  1049. if (UpgradeResult = EveryoneIsInstalled) or (UpgradeResult = BothAreInstalled) then
  1050. result := PromptToUninstall(EveryoneAppMode);
  1051. end
  1052. else
  1053. begin
  1054. if ShouldInstallAllUsers then
  1055. begin
  1056. // We're asking to install for all users; so we must uninstall old version to continue
  1057. if (UpgradeResult = EveryoneIsInstalled) or (UpgradeResult = BothAreInstalled) then
  1058. result := PromptToUninstall(EveryoneAppMode);
  1059. end
  1060. else
  1061. begin
  1062. // Warn RE: multiple install (if both is installed, they already were warned)
  1063. if (UpgradeResult = EveryoneIsInstalled) then
  1064. result := (IDYES = MsgBox(ExpandConstant('{cm:WarnInstallPerUserOverEveryone}'), mbConfirmation, MB_YESNO));
  1065. end;
  1066.  
  1067. // Uninstall per user; must succeed to continue
  1068. if result and ((UpgradeResult = PerUserIsInstalled) or (UpgradeResult = BothAreInstalled)) then
  1069. result := PromptToUninstall(PerUserAppMode);
  1070. end;
  1071. end;
  1072. end
  1073. // if the user has allowed registration of the IDE (default) from our
  1074. // custom page we should run RegisterAdd()
  1075. else if CurPageID = RegisterAddInOptionPage.ID then
  1076. begin
  1077. if not IsElevated() and RegisterAddInOptionPage.Values[0] then
  1078. begin
  1079. Log('Addin registration was requested and will be performed');
  1080. RegisterAddIn();
  1081. end
  1082. else
  1083. begin
  1084. Log('Addin registration was declined because either we are elevated or the user unchecked the checkbox');
  1085. end;
  1086. end;
  1087.  
  1088. // Re-enable the button disabled at start of procedure
  1089. Wizardform.NextButton.Enabled := True;
  1090. end;
  1091.  
  1092. ///<remarks>
  1093. ///The event function is called when wizard reaches the ready to install page.
  1094. ///Because we may or may not launch an elevated installer which will show similar
  1095. ///page, we need to help to make clear to the user what the installer(s) will be
  1096. ///doing by adding the extra custom messages accordingly to the page.
  1097. ///</remarks>
  1098. function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
  1099. var
  1100. output: String;
  1101. begin
  1102. if IsElevated() then
  1103. begin
  1104. output := output + ExpandConstant('{cm:WillExecuteAdminInstall}') + NewLine + NewLine;
  1105. end
  1106. else
  1107. begin
  1108. if ShouldInstallAllUsers then
  1109. begin
  1110. output := output + ExpandConstant('{cm:WillLaunchAdminInstall}') + NewLine + NewLine;
  1111. end
  1112. else
  1113. begin
  1114. output := output + ExpandConstant('{cm:WillInstallForCurrentUser}') + NewLine + NewLine;
  1115. end;
  1116. end;
  1117.  
  1118. output := output + MemoDirInfo + NewLine;
  1119.  
  1120. result := output;
  1121. end;
  1122.  
  1123. ///<remarks>
  1124. ///Called during uninstall, once for each step but for our purpose, we are
  1125. ///interested in only one step doing the actual uninstall.
  1126. ///
  1127. ///As a rule, the addin registration should be always uninstalled; there is no
  1128. ///purpose in having the addin registered when the DLL gets uninstalled. Note
  1129. ///this is also unconditional - it will uninstall the related registry keys.
  1130. ///</remarks>
  1131. procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
  1132. begin
  1133. if CurUninstallStep = usUninstall then UnregisterAddin();
  1134. end;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement