winscpsetup.iss 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637
  1. #define AppId "winscp3"
  2. #define AppMutex "WinSCP"
  3. #define ParentRegistryKey "Software\Martin Prikryl"
  4. #define RegistryKey ParentRegistryKey+"\WinSCP 2"
  5. #define DefaultLang "en"
  6. #define WebRoot "https://winscp.net/"
  7. #define WebForum WebRoot+"forum/"
  8. #define WebDocumentation WebRoot+"eng/docs/"
  9. #define WebReport "http://winscp.net/install.php"
  10. #define Year 2018
  11. #define EnglishLang "English"
  12. #define SetupTypeData "SetupType"
  13. #define InnoSetupReg "Software\Microsoft\Windows\CurrentVersion\Uninstall\" + AppId + "_is1"
  14. #define InnoSetupAppPathReg "Inno Setup: App Path"
  15. #ifndef CompletenessThreshold
  16. #define CompletenessThreshold 100
  17. #else
  18. #define CompletenessThreshold Int(CompletenessThreshold)
  19. #endif
  20. #ifndef InclusionThreshold
  21. #define InclusionThreshold 100
  22. #else
  23. #define InclusionThreshold Int(InclusionThreshold)
  24. #endif
  25. #ifndef PuttySourceDir
  26. #if DirExists("c:\Program Files (x86)\PuTTY")
  27. #define PuttySourceDir "c:\Program Files (x86)\PuTTY"
  28. #else
  29. #define PuttySourceDir "c:\Program Files\PuTTY"
  30. #endif
  31. #endif
  32. #ifndef Status
  33. #define Status "unofficial"
  34. #endif
  35. #ifndef SourceDir
  36. #define SourceDir "..\source"
  37. #endif
  38. #ifndef BinariesDir
  39. #define BinariesDir SourceDir + "\Win32\Release"
  40. #endif
  41. #ifndef BinariesDir64
  42. #define BinariesDir64 SourceDir + "\Win64\Release"
  43. #endif
  44. #ifndef BinariesDirAssembly
  45. #define BinariesDirAssembly "..\dotnet\Win32\Release"
  46. #endif
  47. #ifndef AllTranslations
  48. #define AllTranslations
  49. #endif
  50. #define TranslationDir "translations"
  51. #define OutputDir "."
  52. #define TranslationFileMask "WinSCP.???"
  53. #define MainFileName "WinSCP.exe"
  54. #define MainFileSource BinariesDir+"\"+MainFileName
  55. #define ShellExtFileName "DragExt.dll"
  56. #define ShellExtFileSource BinariesDir+"\"+ShellExtFileName
  57. #define ShellExt64FileName "DragExt64.dll"
  58. #define ShellExt64FileSource BinariesDir64+"\"+ShellExt64FileName
  59. #define ConsoleFileSource BinariesDir+"\WinSCP.com"
  60. #define MapFileSource BinariesDir+"\WinSCP.map"
  61. #define AssemblyFileSource BinariesDirAssembly+"\WinSCPnet.dll"
  62. #ifdef Donations
  63. #define PayPalCardImage "PayPalCard.bmp"
  64. #endif
  65. #define Major
  66. #define Minor
  67. #define Rev
  68. #define Build
  69. #expr ParseVersion(MainFileSource, Major, Minor, Rev, Build)
  70. #define VersionOnly Str(Major)+"."+Str(Minor)+(Rev > 0 ? "."+Str(Rev) : "")
  71. #define Version VersionOnly+(Status != "" ? " "+Status : "")
  72. #define FTag VersionOnly+(Status != "" ? "."+Status : "")
  73. #define WebArguments "ver=" +VersionOnly + "&lang={language}&utm_source=winscp&utm_medium=setup&utm_campaign=" + VersionOnly
  74. #define WebGettingStarted WebRoot + "eng/installed.php?" + WebArguments + "&prevver="
  75. #define MessagesPath(L) TranslationDir + "\" + "WinSCP." + L + ".islu"
  76. #define ExplorerFileBase "Explorer"
  77. #define CommanderFileBase "Commander"
  78. #define WizardImageFileBase "Tall"
  79. #define WizardSmallImageFileBase "Square"
  80. #define SelectDirFileBase "Opened bookmark folder-stored session folder"
  81. [Setup]
  82. AppId={#AppId}
  83. AppName=WinSCP
  84. AppPublisher=Martin Prikryl
  85. AppPublisherURL={#WebRoot}
  86. AppSupportURL={#WebForum}
  87. AppUpdatesURL={#WebRoot}eng/download.php
  88. VersionInfoCompany=Martin Prikryl
  89. VersionInfoDescription=Setup for WinSCP {#Version} (SFTP, FTP, WebDAV and SCP client)
  90. VersionInfoVersion={#Major}.{#Minor}.{#Rev}.{#Build}
  91. VersionInfoTextVersion={#Version}
  92. VersionInfoCopyright=(c) 2000-{#Year} Martin Prikryl
  93. DefaultDirName={pf}\WinSCP
  94. LicenseFile=license.setup.txt
  95. UninstallDisplayIcon={app}\WinSCP.exe
  96. OutputDir={#OutputDir}
  97. DisableStartupPrompt=yes
  98. AppVersion={#Version}
  99. AppVerName=WinSCP {#Version}
  100. OutputBaseFilename=WinSCP-{#FTag}-Setup
  101. SolidCompression=yes
  102. #ifdef ImagesDir
  103. WizardImageFile={#ImagesDir}\{#WizardImageFileBase} 100.bmp
  104. WizardSmallImageFile={#ImagesDir}\{#WizardSmallImageFileBase} 100.bmp
  105. #endif
  106. ShowTasksTreeLines=yes
  107. PrivilegesRequired=none
  108. ShowLanguageDialog=auto
  109. UsePreviousLanguage=yes
  110. DisableProgramGroupPage=yes
  111. MinVersion=0,5.1
  112. SetupIconFile=winscpsetup.ico
  113. DisableDirPage=no
  114. ; We do not want the Explorer restarts as that is not pleasant to the user
  115. CloseApplications=no
  116. #ifdef Sign
  117. SignTool=sign $f "WinSCP Installer" https://winscp.net/eng/docs/installation
  118. #endif
  119. [Languages]
  120. ; English has to be first so that it is pre-selected
  121. ; on Setup Select Language window, when no translation matching
  122. ; Windows UI locale is available
  123. Name: {#DefaultLang}; MessagesFile: {#MessagesPath(DefaultLang)}
  124. #define FindHandle
  125. #dim Languages[200]
  126. #define LanguageCount 0
  127. #define AnyLanguageComplete 0
  128. #define LangI
  129. ; For some reason the variable cannot be defined near the code where we use it
  130. #define AllTranslationsBuf
  131. #sub ProcessTranslationFile
  132. #define FileName FindGetFileName(FindHandle)
  133. #define Lang Copy(FileName, Pos(".", FileName)+1)
  134. #define LangNameFull ReadIni(MessagesPath(Lang), "LangOptions", "LanguageName")
  135. #define Sep Pos(" -", LangNameFull)
  136. #if Sep > 0
  137. #define LangName Copy(LangNameFull, 1, Sep - 1)
  138. #else
  139. #define LangName LangNameFull
  140. #endif
  141. #define LangID ReadIni(MessagesPath(Lang), "LangOptions", "LanguageID")
  142. #define LangCompleteness Int(ReadIni(MessagesPath(Lang), "CustomOptions", "TranslationCompleteness"))
  143. #expr Languages[LanguageCount*4] = Lang
  144. ; Not used atm
  145. #expr Languages[LanguageCount*4+1] = LangName
  146. ; Not used atm
  147. #expr Languages[LanguageCount*4+2] = LangID
  148. #expr Languages[LanguageCount*4+3] = LangCompleteness
  149. #expr LanguageCount++
  150. #if LangCompleteness >= CompletenessThreshold
  151. Name: {#Lang}; MessagesFile: {#MessagesPath(Lang)}
  152. #expr AnyLanguageComplete = 1
  153. #endif
  154. #endsub /* sub ProcessTranslationFile */
  155. #if FindHandle = FindFirst(TranslationDir + "\" + TranslationFileMask, 0)
  156. #define FResult 1
  157. #for {0; FResult; FResult = FindNext(FindHandle)} ProcessTranslationFile
  158. #expr FindClose(FindHandle)
  159. #endif
  160. ; Types are not used anymore, they are preserved only to let setup
  161. ; detect previous installation type and decide between typical/custom setup
  162. [Types]
  163. Name: full; Description: "full"
  164. Name: compact; Description: "compact"
  165. Name: custom; Description: "custom"; Flags: iscustom
  166. [Components]
  167. Name: main; Description: {cm:ApplicationComponent}; \
  168. Types: full custom compact; Flags: fixed
  169. Name: shellext; Description: {cm:ShellExtComponent}; \
  170. Types: full compact
  171. Name: pageant; Description: {cm:PageantComponent}; \
  172. Types: full
  173. Name: puttygen; Description: {cm:PuTTYgenComponent}; \
  174. Types: full
  175. #if AnyLanguageComplete == 1
  176. Name: transl; Description: {cm:TranslationsComponent}; \
  177. Types: full
  178. #endif
  179. [Tasks]
  180. Name: enableupdates; Description: {cm:EnableUpdates}
  181. Name: enableupdates\enablecollectusage; Description: {cm:EnableCollectUsage}
  182. ; Windows integration
  183. Name: desktopicon; Description: {cm:DesktopIconTask}
  184. Name: desktopicon\user; Description: {cm:DesktopIconUserTask}; \
  185. Flags: exclusive unchecked
  186. Name: desktopicon\common; Description: {cm:DesktopIconCommonTask}; \
  187. Flags: exclusive
  188. ; No Quick Launch on Win7
  189. Name: quicklaunchicon; Description: {cm:QuickLaunchIconTask}; \
  190. Flags: unchecked; OnlyBelowVersion: 6.1.7600
  191. Name: sendtohook; Description: {cm:SendToHookTask}
  192. Name: urlhandler; Description: {cm:RegisterAsUrlHandlers}
  193. Name: searchpath; Description: {cm:AddSearchPath}; \
  194. Flags: unchecked; Check: IsAdminLoggedOn
  195. [Icons]
  196. Name: "{commonprograms}\WinSCP"; Filename: "{app}\WinSCP.exe"; Components: main; \
  197. Comment: "{cm:ProgramComment2}"
  198. ; This is created when desktopicon task is selected
  199. Name: "{userdesktop}\WinSCP"; Filename: "{app}\WinSCP.exe"; \
  200. Tasks: desktopicon\user; Comment: "{cm:ProgramComment2}"
  201. Name: "{commondesktop}\WinSCP"; Filename: "{app}\WinSCP.exe"; \
  202. Tasks: desktopicon\common; Comment: "{cm:ProgramComment2}"
  203. ; This is created when quicklaunchicon task is selected
  204. Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\WinSCP"; \
  205. Filename: "{app}\WinSCP.exe"; Tasks: quicklaunchicon
  206. ; This is created when sendtohook task is selected
  207. Name: "{sendto}\{cm:SendToHookNew}"; Filename: "{app}\WinSCP.exe"; \
  208. Parameters: "/upload"; Tasks: sendtohook
  209. [InstallDelete]
  210. ; Remove pre-5.8.2 PuTTY help file
  211. Type: files; Name: "{app}\PuTTY\putty.hlp"
  212. ; Remove pre-524 licence file (without .txt extension)
  213. Type: files; Name: "{app}\license"
  214. ; Remove pre-520 start menu folders
  215. Type: filesandordirs; Name: "{commonprograms}\WinSCP"
  216. Type: filesandordirs; Name: "{userprograms}\WinSCP"; Check: HasUserPrograms
  217. [Run]
  218. Filename: "{app}\WinSCP.exe"; Parameters: "/RegisterForDefaultProtocols"; \
  219. StatusMsg: {cm:RegisteringAsUrlHandlers}; Tasks: urlhandler
  220. Filename: "{app}\WinSCP.exe"; Parameters: "/AddSearchPath"; \
  221. StatusMsg: {cm:AddingSearchPath}; Tasks: searchpath
  222. Filename: "{app}\WinSCP.exe"; Parameters: "/ImportSitesIfAny"; \
  223. StatusMsg: {cm:ImportSites}; Flags: skipifsilent
  224. [UninstallDelete]
  225. ; These additional files are created by application
  226. Type: files; Name: "{app}\WinSCP.ini"
  227. Type: files; Name: "{app}\WinSCP.cgl"
  228. [Files]
  229. #ifdef ImagesDir
  230. ; Put these to the top as we extract them on demand and
  231. ; that can take long with solid compression enabled
  232. Source: "{#ImagesDir}\{#ExplorerFileBase} *.bmp"; Flags: dontcopy
  233. Source: "{#ImagesDir}\{#CommanderFileBase} *.bmp"; Flags: dontcopy
  234. ; We do not need 100% images here, they are embedded already automatically
  235. ; by WizardImageFile and WizardSmallImageFile
  236. Source: "{#ImagesDir}\{#WizardImageFileBase} *.bmp"; Excludes: "* 100.bmp"; \
  237. Flags: dontcopy
  238. Source: "{#ImagesDir}\{#WizardSmallImageFileBase} *.bmp"; Excludes: "* 100.bmp"; \
  239. Flags: dontcopy
  240. Source: "{#ImagesDir}\{#SelectDirFileBase} *.bmp"; Flags: dontcopy
  241. #ifdef Donations
  242. Source: "{#ImagesDir}\{#PayPalCardImage}"; Flags: dontcopy
  243. #endif
  244. #endif
  245. Source: "{#MainFileSource}"; DestDir: "{app}"; \
  246. Components: main; Flags: ignoreversion
  247. Source: "{#ConsoleFileSource}"; DestDir: "{app}"; \
  248. Components: main; Flags: ignoreversion
  249. Source: "{#MapFileSource}"; DestDir: "{app}"; \
  250. Components: main; Flags: ignoreversion
  251. Source: "{#AssemblyFileSource}"; DestDir: "{app}"; \
  252. Components: main; Flags: ignoreversion
  253. Source: "license.txt"; DestDir: "{app}"; \
  254. Components: main; Flags: ignoreversion
  255. Source: "{#ShellExtFileSource}"; DestDir: "{app}"; \
  256. Components: shellext; \
  257. Flags: regserver restartreplace uninsrestartdelete ignoreversion; \
  258. Check: not IsWin64 and ShouldInstallShellExt(ExpandConstant('{app}\{#ShellExtFileName}'), '{#GetFileVersion(ShellExtFileSource)}')
  259. Source: "{#ShellExt64FileSource}"; DestDir: "{app}"; \
  260. Components: shellext; \
  261. Flags: regserver restartreplace uninsrestartdelete ignoreversion; \
  262. Check: IsWin64 and ShouldInstallShellExt(ExpandConstant('{app}\{#ShellExt64FileName}'), '{#GetFileVersion(ShellExt64FileSource)}')
  263. Source: "{#PuttySourceDir}\LICENCE"; DestDir: "{app}\PuTTY"; \
  264. Components: pageant puttygen; Flags: ignoreversion
  265. Source: "{#PuttySourceDir}\putty.chm"; DestDir: "{app}\PuTTY"; \
  266. Components: pageant puttygen; Flags: ignoreversion
  267. Source: "{#PuttySourceDir}\pageant.exe"; DestDir: "{app}\PuTTY"; \
  268. Components: pageant; Flags: ignoreversion
  269. Source: "{#PuttySourceDir}\puttygen.exe"; DestDir: "{app}\PuTTY"; \
  270. Components: puttygen; Flags: ignoreversion
  271. #ifdef ExtensionsDir
  272. Source: "{#ExtensionsDir}\*.*"; DestDir: "{app}\Extensions"
  273. #endif
  274. [Registry]
  275. Root: HKCU; Subkey: "{#ParentRegistryKey}"; Flags: uninsdeletekeyifempty
  276. Root: HKCU; Subkey: "{#RegistryKey}"; Flags: uninsdeletekeyifempty
  277. ; Norton Commander interface
  278. Root: HKCU; SubKey: "{#RegistryKey}\Configuration\Interface"; ValueType: dword; \
  279. ValueName: "Interface"; ValueData: 0; Check: UserSettings(1)
  280. Root: HKLM; SubKey: "{#RegistryKey}"; ValueType: dword; \
  281. ValueName: "DefaultInterfaceInterface"; ValueData: 0; \
  282. Check: UserSettings(1); Flags: noerror
  283. ; Explorer-like interface
  284. Root: HKCU; SubKey: "{#RegistryKey}\Configuration\Interface"; ValueType: dword; \
  285. ValueName: "Interface"; ValueData: 1; Check: not UserSettings(1)
  286. Root: HKLM; SubKey: "{#RegistryKey}"; ValueType: dword; \
  287. ValueName: "DefaultInterfaceInterface"; ValueData: 1; \
  288. Check: not UserSettings(1); Flags: noerror
  289. ; If installer enabled ddext, let it reset the settings on uninstall,
  290. ; so the default is used on the next run
  291. Root: HKCU; SubKey: "{#RegistryKey}\Configuration\Interface"; ValueType: dword; \
  292. ValueName: "DDExtEnabled"; ValueData: 1; Components: shellext; \
  293. Flags: uninsdeletevalue
  294. ; Updates
  295. Root: HKCU; SubKey: "{#RegistryKey}\Configuration\Interface\Updates"; \
  296. ValueType: dword; ValueName: "Period"; ValueData: 7; \
  297. Tasks: enableupdates; Check: not UpdatesEnabled
  298. Root: HKLM; SubKey: "{#RegistryKey}"; \
  299. ValueType: dword; ValueName: "DefaultUpdatesPeriod"; ValueData: 7; \
  300. Tasks: enableupdates; Flags: noerror
  301. Root: HKLM; SubKey: "{#RegistryKey}"; \
  302. ValueType: dword; ValueName: "DefaultCollectUsage"; ValueData: 1; \
  303. Tasks: enableupdates\enablecollectusage; Flags: noerror
  304. #sub EmitLang
  305. #if Languages[LangI*4+3] >= InclusionThreshold
  306. [Files]
  307. Source: "{#TranslationDir}\WinSCP.{#Languages[LangI*4]}"; DestDir: "{app}\Translations"; \
  308. Components: transl; Flags: ignoreversion
  309. #endif
  310. #endsub /* sub EmitLang */
  311. #for {LangI = 0; LangI < LanguageCount; LangI++} EmitLang
  312. ; Delete translations from installation root folder (pre-5.10)
  313. [InstallDelete]
  314. #expr AllTranslationsBuf = AllTranslations + '-'
  315. #sub DeleteRootTranslation
  316. #define P Pos('-', AllTranslationsBuf)
  317. #define Lang Copy(AllTranslationsBuf, 1, P - 1)
  318. #expr AllTranslationsBuf = Copy(AllTranslationsBuf, P + 1)
  319. Type: files; Name: "{app}\WinSCP.{#Lang}"
  320. #endsub
  321. #for { 0; Len(AllTranslationsBuf) > 0; 0 } DeleteRootTranslation
  322. [UninstallRun]
  323. ; Make sure no later uninstall task recreate the configuration
  324. Filename: "{app}\WinSCP.exe"; Parameters: "/UninstallCleanup"; \
  325. RunOnceId: "UninstallCleanup"
  326. Filename: "{app}\WinSCP.exe"; Parameters: "/RemoveSearchPath"; \
  327. RunOnceId: "RemoveSearchPath"
  328. Filename: "{app}\WinSCP.exe"; Parameters: "/UnregisterForProtocols"; \
  329. RunOnceId: "UnregisterForProtocols"
  330. [Code]
  331. const
  332. NewLine = #13#10;
  333. var
  334. TypicalTypeButton: TRadioButton;
  335. CustomTypeButton: TRadioButton;
  336. CommanderRadioButton: TRadioButton;
  337. ExplorerRadioButton: TRadioButton;
  338. LaunchCheckbox: TCheckbox;
  339. OpenGettingStartedCheckbox: TCheckbox;
  340. AreUpdatesEnabled: Boolean;
  341. AutomaticUpdate: Boolean;
  342. Upgrade: Boolean;
  343. PrevVersion: string;
  344. ShellExtNewerCacheFileName: string;
  345. ShellExtNewerCacheResult: Boolean;
  346. ShellExtNoRestart: Boolean;
  347. #ifdef Donations
  348. DonationPanel: TPanel;
  349. AboutDonationCaption: TLabel;
  350. #endif
  351. InstallationDone: Boolean;
  352. LicenseAccepted: Boolean;
  353. InitDir: string;
  354. InitComponents: string;
  355. InitTasks: string;
  356. InitInterface: Integer;
  357. Donated: Boolean;
  358. InterfacePage: TWizardPage;
  359. SetupTypePage: TWizardPage;
  360. procedure ShowMessage(Text: string);
  361. begin
  362. MsgBox(Text, mbInformation, MB_OK);
  363. end;
  364. function IsWinVista: Boolean;
  365. begin
  366. Result := (GetWindowsVersion >= $06000000);
  367. end;
  368. procedure CutVersionPart(var VersionString: string; var VersionPart: Word);
  369. var
  370. P: Integer;
  371. begin
  372. P := Pos('.', VersionString);
  373. if P > 0 then
  374. begin
  375. VersionPart := StrToIntDef(Copy(VersionString, 1, P - 1), 0);
  376. Delete(VersionString, 1, P);
  377. end
  378. else
  379. begin
  380. VersionPart := StrToIntDef(VersionString, 0);
  381. VersionString := '';
  382. end;
  383. end;
  384. function ShouldInstallShellExt(FileName: string; InstalledVersion: string): Boolean;
  385. var
  386. ExistingMS, ExistingLS: Cardinal;
  387. ExistingMajor, ExistingMinor, ExistingRev, ExistingBuild: Cardinal;
  388. InstalledMajor, InstalledMinor, InstalledRev, InstalledBuild: Word;
  389. begin
  390. if ShellExtNewerCacheFileName = FileName then
  391. begin
  392. if ShellExtNewerCacheResult then
  393. begin
  394. Log(Format('Allowing installation of shell extension %s as already decided', [FileName]));
  395. Result := True;
  396. end
  397. else
  398. begin
  399. Log(Format('Skipping installation of shell extension %s as already decided', [FileName]));
  400. Result := False;
  401. end;
  402. // Keeping ShellExtNoRestart value
  403. end
  404. else
  405. if not FileExists(FileName) then
  406. begin
  407. Log(Format('Shell extension %s does not exist yet, allowing installation', [FileName]));
  408. ShellExtNoRestart := False;
  409. Result := True;
  410. end
  411. else
  412. if not GetVersionNumbers(FileName, ExistingMS, ExistingLS) then
  413. begin
  414. Log(Format('Cannot retrieve version of existing shell extension %s, allowing installation', [FileName]));
  415. ShellExtNoRestart := False;
  416. Result := True;
  417. end
  418. else
  419. begin
  420. ExistingMajor := ExistingMS shr 16;
  421. ExistingMinor := ExistingMS and $FFFF;
  422. ExistingRev := ExistingLS shr 16;
  423. ExistingBuild := ExistingLS and $FFFF;
  424. Log(Format('Existing shell extension %s version: %d.%d.%d[.%d]', [FileName, ExistingMajor, ExistingMinor, ExistingRev, ExistingBuild]));
  425. Log(Format('Installed extension version string: %s', [InstalledVersion]));
  426. CutVersionPart(InstalledVersion, InstalledMajor);
  427. CutVersionPart(InstalledVersion, InstalledMinor);
  428. CutVersionPart(InstalledVersion, InstalledRev);
  429. CutVersionPart(InstalledVersion, InstalledBuild);
  430. Log(Format('Installed extension version: %d.%d.%d[.%d]', [InstalledMajor, InstalledMinor, InstalledRev, InstalledBuild]));
  431. if (InstalledMajor <> ExistingMajor) or
  432. ((ExistingMajor = 1) and (ExistingMinor <= 1)) then
  433. begin
  434. // Still on 1.x, so this won't be used when upgrading,
  435. // but it will be useful, if downgrading from future version with a different major version.
  436. if InstalledMajor <> ExistingMajor then
  437. begin
  438. Log('Existing extension has different major version, allowing installation, and will require restart, if it is locked.')
  439. end
  440. else
  441. begin
  442. // 1.1 uses Ansi encoding, and is incompatible with 1.2 and newer which uses Unicode
  443. Log('Existing extension is 1.1 or older, allowing installation, and will require restart, if it is locked.');
  444. end;
  445. Result := True;
  446. ShellExtNoRestart := False;
  447. end
  448. else
  449. if (InstalledMinor > ExistingMinor) or
  450. ((InstalledMinor = ExistingMinor) and (InstalledRev > ExistingRev)) then
  451. begin
  452. Log('Installed extension is newer than existing extension, but major version is the same, allowing installation, but we will delay replacing the extension until the next system start, if it is locked.');
  453. Result := True;
  454. ShellExtNoRestart := True;
  455. end
  456. else
  457. begin
  458. Log('Installed extension is same or older than existing extension (but the same major version), skipping installation');
  459. ShellExtNoRestart := False;
  460. Result := False;
  461. end;
  462. end;
  463. ShellExtNewerCacheFileName := FileName;
  464. ShellExtNewerCacheResult := Result;
  465. end;
  466. function UpdatesEnabled: Boolean;
  467. begin
  468. Result := AreUpdatesEnabled;
  469. end;
  470. function UserSettings(Settings: Integer): Boolean;
  471. begin
  472. case Settings of
  473. 1: Result := CommanderRadioButton.Checked;
  474. else Result := False;
  475. end;
  476. end;
  477. function LanguageCompleteness(Lang: string): Integer;
  478. begin
  479. #sub EmitLang4
  480. if (Lang = '{#Languages[LangI*4]}') then
  481. begin
  482. Result := {#Languages[LangI*4+3]};
  483. end
  484. else
  485. #endsub /* sub EmitLang4 */
  486. #for {LangI = 0; LangI < LanguageCount; LangI++} EmitLang4
  487. // used also for the default language
  488. Result := 100;
  489. end;
  490. procedure OpenBrowser(Url: string);
  491. var
  492. ErrorCode: Integer;
  493. begin
  494. ShellExec('open', Url, '', '', SW_SHOWNORMAL, ewNoWait, ErrorCode);
  495. end;
  496. function IsRestartPage: Boolean;
  497. begin
  498. Result := WizardForm.YesRadio.Visible;
  499. end;
  500. procedure OpenHelp;
  501. var
  502. HelpKeyword: string;
  503. begin
  504. HelpKeyword := 'ui_installer'; // default
  505. case WizardForm.CurPageID of
  506. wpLicense:
  507. HelpKeyword := 'ui_installer_license';
  508. wpSelectDir:
  509. HelpKeyword := 'ui_installer_selectdir';
  510. wpSelectComponents:
  511. HelpKeyword := 'ui_installer_selectcomponents';
  512. wpSelectTasks:
  513. HelpKeyword := 'ui_installer_selecttasks';
  514. wpReady:
  515. HelpKeyword := 'ui_installer_ready';
  516. wpFinished:
  517. HelpKeyword := 'ui_installer_finished';
  518. SetupTypePage.ID:
  519. HelpKeyword := 'ui_installer_setuptype';
  520. InterfacePage.ID:
  521. HelpKeyword := 'ui_installer_interface';
  522. end;
  523. OpenBrowser('{#WebDocumentation}' + HelpKeyword + '?' + ExpandConstant('{#WebArguments}'));
  524. end;
  525. procedure HelpButtonClick(Sender: TObject);
  526. begin
  527. OpenHelp;
  528. end;
  529. procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  530. begin
  531. if Key = 112 { VK_F1 } then
  532. begin
  533. OpenHelp;
  534. Key := 0;
  535. end;
  536. end;
  537. procedure CaptionClick(Sender: TObject);
  538. begin
  539. WizardForm.ActiveControl := TLabel(Sender).FocusControl;
  540. end;
  541. procedure ImageClick(Sender: TObject);
  542. begin
  543. WizardForm.ActiveControl := TWinControl(TControl(Sender).Tag);
  544. end;
  545. function WillRestart: Boolean;
  546. begin
  547. Result := WizardForm.YesRadio.Visible and WizardForm.YesRadio.Checked;
  548. end;
  549. procedure UpdatePostInstallRunCheckboxes(Sender: TObject);
  550. begin
  551. LaunchCheckbox.Enabled := not WillRestart;
  552. OpenGettingStartedCheckbox.Enabled :=
  553. LaunchCheckbox.Enabled
  554. end;
  555. procedure LinkLabel(Control: TLabel);
  556. begin
  557. Control.ParentFont := True;
  558. Control.Font.Style := Control.Font.Style + [fsUnderline];
  559. Control.Font.Color := clBlue;
  560. Control.Cursor := crHand;
  561. end;
  562. function IsTypicalInstallation: Boolean;
  563. begin
  564. Result := TypicalTypeButton.Checked;
  565. end;
  566. #ifdef Donations
  567. procedure AboutDonationsLinkClick(Sender: TObject);
  568. begin
  569. OpenBrowser('{#WebRoot}eng/donate.php?' + ExpandConstant('{#WebArguments}'));
  570. Donated := true;
  571. end;
  572. procedure DonateLinkClick(Sender: TObject);
  573. var
  574. Control: TControl;
  575. begin
  576. Control := TControl(Sender);
  577. OpenBrowser('{#WebRoot}eng/donate.php?amount=' + IntToStr(Control.Tag) + '&currency=' + CustomMessage('Currency') + '&' + ExpandConstant('{#WebArguments}'));
  578. Donated := true;
  579. end;
  580. procedure CreateDonateLink(Amount: Integer; var Top: Integer);
  581. var
  582. Caption: TLabel;
  583. begin
  584. Caption := TLabel.Create(DonationPanel);
  585. Caption.Left := 0;
  586. Caption.Top := Top;
  587. Caption.Tag := Amount;
  588. Caption.Parent := DonationPanel;
  589. Caption.Caption := Format(CustomMessage('Donate'), ['$' + IntToStr(Amount)]);
  590. Caption.OnClick := @DonateLinkClick;
  591. LinkLabel(Caption);
  592. Top := Top + ScaleY(16);
  593. end;
  594. #endif
  595. const
  596. fsSurface = 0;
  597. procedure LoadEmbededBitmap(Image: TBitmapImage; Name: string; BackgroundColor: TColor);
  598. var
  599. FileName: string;
  600. Bitmap: TAlphaBitmap;
  601. begin
  602. ExtractTemporaryFile(Name);
  603. Bitmap := TAlphaBitmap.Create();
  604. Bitmap.AlphaFormat := afDefined;
  605. FileName := ExpandConstant('{tmp}\' + Name);
  606. Bitmap.LoadFromFile(FileName);
  607. // we won't need this anymore
  608. DeleteFile(FileName);
  609. Image.Bitmap := Bitmap;
  610. Bitmap.Free;
  611. Image.BackColor := BackgroundColor;
  612. end;
  613. function GetScalingFactor: Integer;
  614. begin
  615. if WizardForm.Font.PixelsPerInch >= 192 then Result := 200
  616. else
  617. if WizardForm.Font.PixelsPerInch >= 144 then Result := 150
  618. else
  619. if WizardForm.Font.PixelsPerInch >= 120 then Result := 125
  620. else Result := 100;
  621. end;
  622. procedure LoadEmbededScaledBitmap(Image: TBitmapImage; NameBase: string; SizeBase: Integer; BackgroundColor: TColor);
  623. var
  624. Name: String;
  625. begin
  626. Name := Format('%s %d.bmp', [NameBase, SizeBase * GetScalingFactor div 100]);
  627. LoadEmbededBitmap(Image, Name, BackgroundColor);
  628. end;
  629. procedure LoadEmbededScaledIcon(Image: TBitmapImage; NameBase: string; SizeBase: Integer; BackgroundColor: TColor);
  630. begin
  631. LoadEmbededScaledBitmap(Image, NameBase, SizeBase, BackgroundColor);
  632. Image.AutoSize := True;
  633. end;
  634. // WORKAROUND
  635. // Checkboxes and Radio buttons created on runtime do
  636. // not scale their height automatically
  637. procedure ScaleFixedHeightControl(Control: TButtonControl);
  638. begin
  639. Control.Height := ScaleY(Control.Height);
  640. end;
  641. function GetBottom(Control: TControl): Integer;
  642. begin
  643. Result := Control.Top + Control.Height;
  644. end;
  645. function CmdLineParamExists(const Value: string): Boolean;
  646. var
  647. I: Integer;
  648. begin
  649. Result := False;
  650. for I := 1 to ParamCount do
  651. begin
  652. if CompareText(ParamStr(I), Value) = 0 then
  653. begin
  654. Result := True;
  655. Exit;
  656. end;
  657. end;
  658. end;
  659. function InitializeSetup: Boolean;
  660. var
  661. WaitInterval: Integer;
  662. Wait: Integer;
  663. begin
  664. AutomaticUpdate := CmdLineParamExists('/AutomaticUpdate');
  665. if AutomaticUpdate then
  666. begin
  667. Log('Automatic update');
  668. Wait := 10000;
  669. end
  670. else
  671. begin
  672. Wait := 0;
  673. end;
  674. WaitInterval := 250;
  675. while (Wait > 0) and CheckForMutexes('{#AppMutex}') do
  676. begin
  677. Log('Application is still running, waiting');
  678. Sleep(WaitInterval);
  679. Wait := Wait - WaitInterval;
  680. end;
  681. while CheckForMutexes('{#AppMutex}') do
  682. begin
  683. if MsgBox(
  684. FmtMessage(SetupMessage(msgSetupAppRunningError), ['WinSCP']),
  685. mbError, MB_OKCANCEL) <> IDOK then
  686. begin
  687. Abort;
  688. end;
  689. end;
  690. Result := True;
  691. end;
  692. function IsElevated: Boolean;
  693. begin
  694. Result := IsAdminLoggedOn or IsPowerUserLoggedOn;
  695. end;
  696. function HaveWriteAccessToApp: Boolean;
  697. var
  698. FileName: string;
  699. begin
  700. FileName := AddBackslash(WizardDirValue) + 'writetest.tmp';
  701. Result := SaveStringToFile(FileName, 'test', False);
  702. if Result then
  703. begin
  704. Log(Format('Have write access to the last installation path [%s]', [WizardDirValue]));
  705. DeleteFile(FileName);
  706. end
  707. else
  708. begin
  709. Log(Format('Does not have write access to the last installation path [%s]', [WizardDirValue]));
  710. end;
  711. end;
  712. procedure ExitProcess(uExitCode: UINT);
  713. external '[email protected] stdcall';
  714. function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string;
  715. lpParameters: string; lpDirectory: string; nShowCmd: Integer): THandle;
  716. external '[email protected] stdcall';
  717. function Elevate: Boolean;
  718. var
  719. I: Integer;
  720. RetVal: Integer;
  721. Params: string;
  722. S: string;
  723. begin
  724. Result := not CmdLineParamExists('/Elevated');
  725. if not Result then
  726. begin
  727. Log('Elevation already attempted and silently failed, continuing unelevated');
  728. end
  729. else
  730. begin
  731. // Collect current instance parameters
  732. for I := 1 to ParamCount do
  733. begin
  734. S := ParamStr(I);
  735. // Unique log file name for the elevated instance
  736. if CompareText(Copy(S, 1, 5), '/LOG=') = 0 then
  737. begin
  738. S := S + '-elevated';
  739. end;
  740. // Do not pass our /SL5 switch
  741. if CompareText(Copy(S, 1, 5), '/SL5=') <> 0 then
  742. begin
  743. Params := Params + AddQuotes(S) + ' ';
  744. end;
  745. end;
  746. // ... and add selected language
  747. Params := Params + '/LANG=' + ActiveLanguage + ' /Elevated';
  748. Log(Format('Elevating setup with parameters [%s]', [Params]));
  749. RetVal := ShellExecute(0, 'runas', ExpandConstant('{srcexe}'), Params, '', SW_SHOW);
  750. Log(Format('Running elevated setup returned [%d]', [RetVal]));
  751. Result := (RetVal > 32);
  752. // if elevated executing of this setup succeeded, then...
  753. if Result then
  754. begin
  755. Log('Elevation succeeded');
  756. // exit this non-elevated setup instance
  757. ExitProcess(0);
  758. end
  759. else
  760. begin
  761. Log(Format('Elevation failed [%s]', [SysErrorMessage(RetVal)]));
  762. end;
  763. end;
  764. end;
  765. procedure InitializeWizard;
  766. var
  767. DefaultLang: Boolean;
  768. UserInterface: Cardinal;
  769. UpdatesPeriod: Cardinal;
  770. Caption: TLabel;
  771. #ifdef ImagesDir
  772. Image: TBitmapImage;
  773. #endif
  774. HelpButton: TButton;
  775. #ifdef Donations
  776. P: Integer;
  777. #ifdef ImagesDir
  778. P2: Integer;
  779. #endif
  780. #endif
  781. S: string;
  782. Completeness: Integer;
  783. begin
  784. InstallationDone := False;
  785. LicenseAccepted := False;
  786. InitInterface := -1;
  787. DefaultLang := (ActiveLanguage = '{#DefaultLang}');
  788. Upgrade :=
  789. RegQueryStringValue(HKLM, '{#InnoSetupReg}', '{#InnoSetupAppPathReg}', S) or
  790. RegQueryStringValue(HKCU, '{#InnoSetupReg}', '{#InnoSetupAppPathReg}', S);
  791. if Upgrade then
  792. begin
  793. Log('Upgrade');
  794. end
  795. else
  796. begin
  797. Log('New installation');
  798. end;
  799. if Upgrade and GetVersionNumbersString(AddBackslash(WizardDirValue) + '{#MainFileName}', PrevVersion) and
  800. (PrevVersion[2] = '.') and (PrevVersion[4] = '.') and (PrevVersion[6] = '.') then
  801. begin
  802. PrevVersion := Copy(PrevVersion, 1, 5);
  803. end;
  804. WizardForm.KeyPreview := True;
  805. WizardForm.OnKeyDown := @FormKeyDown;
  806. // to accomodate one more task
  807. WizardForm.TasksList.Height := WizardForm.TasksList.Height + ScaleY(8);
  808. // allow installation without requiring user to accept license
  809. WizardForm.LicenseAcceptedRadio.Checked := True;
  810. WizardForm.LicenseAcceptedRadio.Visible := False;
  811. WizardForm.LicenseNotAcceptedRadio.Visible := False;
  812. WizardForm.LicenseMemo.Height :=
  813. GetBottom(WizardForm.LicenseNotAcceptedRadio) -
  814. WizardForm.LicenseMemo.Top - 5;
  815. // hide installation types combo
  816. WizardForm.TypesCombo.Visible := False;
  817. WizardForm.ComponentsList.Height :=
  818. GetBottom(WizardForm.ComponentsList) -
  819. WizardForm.TypesCombo.Top;
  820. WizardForm.ComponentsList.Top := WizardForm.TypesCombo.Top;
  821. // add help button
  822. HelpButton := TButton.Create(WizardForm);
  823. HelpButton.Parent := WizardForm;
  824. HelpButton.Left :=
  825. WizardForm.ClientWidth -
  826. (WizardForm.CancelButton.Left + WizardForm.CancelButton.Width);
  827. HelpButton.Top := WizardForm.CancelButton.Top;
  828. HelpButton.Width := WizardForm.CancelButton.Width;
  829. HelpButton.Height := WizardForm.CancelButton.Height;
  830. HelpButton.Caption := CustomMessage('HelpButton');
  831. HelpButton.OnClick := @HelpButtonClick;
  832. // elevate
  833. if not IsWinVista then
  834. begin
  835. Log(Format('This version of Windows [%x] does not support elevation', [GetWindowsVersion]));
  836. end
  837. else
  838. if IsElevated then
  839. begin
  840. Log('Running elevated');
  841. end
  842. else
  843. begin
  844. Log('Running non-elevated');
  845. if Upgrade then
  846. begin
  847. if not HaveWriteAccessToApp then
  848. begin
  849. Elevate;
  850. end;
  851. end
  852. else
  853. begin
  854. if not Elevate then
  855. begin
  856. WizardForm.DirEdit.Text := ExpandConstant('{localappdata}\WinSCP');
  857. Log(Format('Falling back to local application user folder [%s]', [WizardForm.DirEdit.Text]));
  858. end;
  859. end;
  860. end;
  861. // Only after elevating, not to show the message twice
  862. Completeness := LanguageCompleteness(ActiveLanguage);
  863. if (Completeness < 100) and (not WizardSilent) then
  864. begin
  865. ShowMessage(FmtMessage(CustomMessage('IncompleteTranslation'), [IntToStr(Completeness)]));
  866. end;
  867. // installation type page
  868. SetupTypePage := CreateCustomPage(wpLicense,
  869. CustomMessage('SetupTypeTitle'),
  870. CustomMessage('SetupTypePrompt'));
  871. TypicalTypeButton := TRadioButton.Create(SetupTypePage);
  872. if not Upgrade then
  873. S := CustomMessage('TypicalType')
  874. else
  875. S := CustomMessage('TypicalUpgradeType');
  876. TypicalTypeButton.Caption :=
  877. FmtMessage(CustomMessage('Recommended'), [S]);
  878. // check typical install, if typical install was installed before or
  879. // when version without setup type support was installed with
  880. // "full" installation or when there were no installation before
  881. // ("full" installation is default)
  882. TypicalTypeButton.Checked :=
  883. ((GetPreviousData('{#SetupTypeData}', '') = 'typical')) or
  884. ((GetPreviousData('{#SetupTypeData}', '') = '') and
  885. (WizardSetupType(False) = 'full'));
  886. TypicalTypeButton.Left := ScaleX(4);
  887. TypicalTypeButton.Width := SetupTypePage.SurfaceWidth -
  888. TypicalTypeButton.Left;
  889. ScaleFixedHeightControl(TypicalTypeButton);
  890. TypicalTypeButton.Parent := SetupTypePage.Surface;
  891. Caption := TLabel.Create(SetupTypePage);
  892. Caption.WordWrap := True;
  893. if not Upgrade then
  894. begin
  895. Caption.Caption :=
  896. CustomMessage('TypicalType1') + NewLine +
  897. CustomMessage('TypicalType2') + NewLine +
  898. CustomMessage('TypicalType3');
  899. end
  900. else
  901. begin
  902. Caption.Caption :=
  903. CustomMessage('TypicalUpgradeType1');
  904. end;
  905. Caption.Left := ScaleX(4) + ScaleX(20);
  906. Caption.Width := SetupTypePage.SurfaceWidth - Caption.Left;
  907. Caption.Top := GetBottom(TypicalTypeButton) + ScaleY(6);
  908. Caption.Parent := SetupTypePage.Surface;
  909. Caption.FocusControl := TypicalTypeButton;
  910. Caption.OnClick := @CaptionClick;
  911. CustomTypeButton := TRadioButton.Create(SetupTypePage);
  912. if not Upgrade then
  913. CustomTypeButton.Caption := CustomMessage('CustomType')
  914. else
  915. CustomTypeButton.Caption := CustomMessage('CustomUpgradeType');
  916. CustomTypeButton.Checked := (not TypicalTypeButton.Checked);
  917. CustomTypeButton.Left := ScaleX(4);
  918. CustomTypeButton.Width := SetupTypePage.SurfaceWidth -
  919. CustomTypeButton.Left;
  920. CustomTypeButton.Top := GetBottom(Caption) + ScaleY(10);
  921. ScaleFixedHeightControl(CustomTypeButton);
  922. CustomTypeButton.Parent := SetupTypePage.Surface;
  923. Caption := TLabel.Create(SetupTypePage);
  924. Caption.WordWrap := True;
  925. if not Upgrade then
  926. begin
  927. Caption.Caption :=
  928. CustomMessage('CustomType1');
  929. end
  930. else
  931. begin
  932. Caption.Caption :=
  933. CustomMessage('CustomUpgradeType1') + NewLine +
  934. CustomMessage('CustomUpgradeType2');
  935. end;
  936. Caption.Left := ScaleX(4) + ScaleX(20);
  937. Caption.Width := SetupTypePage.SurfaceWidth - Caption.Left;
  938. Caption.Top := GetBottom(CustomTypeButton) + ScaleY(6);
  939. Caption.Parent := SetupTypePage.Surface;
  940. Caption.FocusControl := CustomTypeButton;
  941. Caption.OnClick := @CaptionClick;
  942. // interface page
  943. InterfacePage := CreateCustomPage(wpSelectTasks,
  944. CustomMessage('UserSettingsTitle'),
  945. CustomMessage('UserSettingsPrompt'));
  946. UpdatesPeriod := 0;
  947. RegQueryDWordValue(HKCU, '{#RegistryKey}\Configuration\Interface\Updates',
  948. 'Period', UpdatesPeriod);
  949. AreUpdatesEnabled := (UpdatesPeriod <> 0);
  950. UserInterface := 0; { default is commander }
  951. RegQueryDWordValue(HKCU, '{#RegistryKey}\Configuration\Interface',
  952. 'Interface', UserInterface);
  953. Caption := TLabel.Create(InterfacePage);
  954. Caption.Caption := CustomMessage('UserInterfaceStyle');
  955. Caption.Width := InterfacePage.SurfaceWidth;
  956. Caption.Parent := InterfacePage.Surface;
  957. CommanderRadioButton := TRadioButton.Create(InterfacePage);
  958. CommanderRadioButton.Caption := CustomMessage('NortonCommanderInterfaceC');
  959. CommanderRadioButton.Checked := (UserInterface = 0);
  960. CommanderRadioButton.Left := ScaleX(4);
  961. CommanderRadioButton.Width := ScaleX(116);
  962. CommanderRadioButton.Top := GetBottom(Caption) + ScaleY(6);
  963. ScaleFixedHeightControl(CommanderRadioButton);
  964. CommanderRadioButton.Parent := InterfacePage.Surface;
  965. #ifdef ImagesDir
  966. Image := TBitmapImage.Create(InterfacePage);
  967. Image.Top := GetBottom(CommanderRadioButton) + ScaleY(6);
  968. Image.Left := CommanderRadioButton.Left + ScaleX(45);
  969. Image.Parent := InterfacePage.Surface;
  970. LoadEmbededScaledIcon(Image, '{#CommanderFileBase}', 32, InterfacePage.Surface.Color);
  971. Image.OnClick := @ImageClick;
  972. Image.Tag := Integer(CommanderRadioButton);
  973. #endif
  974. Caption := TLabel.Create(InterfacePage);
  975. Caption.WordWrap := True;
  976. Caption.Caption :=
  977. CustomMessage('NortonCommanderInterface1') + NewLine +
  978. CustomMessage('NortonCommanderInterface2') + NewLine +
  979. CustomMessage('NortonCommanderInterface3');
  980. Caption.Left := CommanderRadioButton.Left + CommanderRadioButton.Width;
  981. Caption.Width := InterfacePage.SurfaceWidth - Caption.Left;
  982. Caption.Top := CommanderRadioButton.Top;
  983. Caption.Parent := InterfacePage.Surface;
  984. Caption.FocusControl := CommanderRadioButton;
  985. Caption.OnClick := @CaptionClick;
  986. ExplorerRadioButton := TRadioButton.Create(InterfacePage);
  987. ExplorerRadioButton.Caption := CustomMessage('ExplorerInterfaceC');
  988. ExplorerRadioButton.Checked := (UserInterface <> 0);
  989. ExplorerRadioButton.Left := ScaleX(4);
  990. ExplorerRadioButton.Width := CommanderRadioButton.Width;
  991. ExplorerRadioButton.Top := GetBottom(Caption) + ScaleY(10);
  992. ScaleFixedHeightControl(ExplorerRadioButton);
  993. ExplorerRadioButton.Parent := InterfacePage.Surface;
  994. #ifdef ImagesDir
  995. Image := TBitmapImage.Create(InterfacePage);
  996. Image.Top := GetBottom(ExplorerRadioButton) + ScaleY(6);
  997. Image.Left := ExplorerRadioButton.Left + ScaleX(45);
  998. Image.Parent := InterfacePage.Surface;
  999. LoadEmbededScaledIcon(Image, '{#ExplorerFileBase}', 32, InterfacePage.Surface.Color);
  1000. Image.OnClick := @ImageClick;
  1001. Image.Tag := Integer(ExplorerRadioButton);
  1002. #endif
  1003. Caption := TLabel.Create(InterfacePage);
  1004. Caption.WordWrap := True;
  1005. Caption.Caption :=
  1006. CustomMessage('ExplorerInterface1') + NewLine +
  1007. CustomMessage('ExplorerInterface2') + NewLine +
  1008. CustomMessage('ExplorerInterface3');
  1009. Caption.Left := ExplorerRadioButton.Left + ExplorerRadioButton.Width;
  1010. Caption.Width := InterfacePage.SurfaceWidth - Caption.Left;
  1011. Caption.Top := ExplorerRadioButton.Top;
  1012. Caption.Parent := InterfacePage.Surface;
  1013. Caption.FocusControl := ExplorerRadioButton;
  1014. Caption.OnClick := @CaptionClick;
  1015. // run checkbox
  1016. LaunchCheckbox := TCheckbox.Create(WizardForm.FinishedPage);
  1017. LaunchCheckbox.Caption := CustomMessage('Launch');
  1018. LaunchCheckbox.Checked := True;
  1019. LaunchCheckbox.Left := WizardForm.YesRadio.Left;
  1020. LaunchCheckbox.Width := WizardForm.YesRadio.Width;
  1021. ScaleFixedHeightControl(LaunchCheckbox);
  1022. LaunchCheckbox.Parent := WizardForm.FinishedPage;
  1023. OpenGettingStartedCheckbox := TCheckbox.Create(WizardForm.FinishedPage);
  1024. OpenGettingStartedCheckbox.Caption := CustomMessage('OpenGettingStarted');
  1025. OpenGettingStartedCheckbox.Checked := True;
  1026. OpenGettingStartedCheckbox.Left := WizardForm.YesRadio.Left;
  1027. OpenGettingStartedCheckbox.Width := WizardForm.YesRadio.Width;
  1028. ScaleFixedHeightControl(OpenGettingStartedCheckbox);
  1029. OpenGettingStartedCheckbox.Parent := WizardForm.FinishedPage;
  1030. #ifdef Donations
  1031. DonationPanel := TPanel.Create(WizardForm.FinishedPage);
  1032. DonationPanel.Left := WizardForm.YesRadio.Left;
  1033. DonationPanel.Width := WizardForm.YesRadio.Width;
  1034. DonationPanel.Parent := WizardForm.FinishedPage;
  1035. DonationPanel.BevelInner := bvNone;
  1036. DonationPanel.BevelOuter := bvNone;
  1037. DonationPanel.Color := WizardForm.FinishedPage.Color;
  1038. Caption := TLabel.Create(DonationPanel);
  1039. Caption.WordWrap := True;
  1040. Caption.Caption := CustomMessage('PleaseDonate');
  1041. Caption.Left := 0;
  1042. Caption.Top := 0;
  1043. Caption.Width := DonationPanel.Width;
  1044. Caption.Parent := DonationPanel;
  1045. P := GetBottom(Caption) + ScaleY(12);
  1046. #ifdef ImagesDir
  1047. P2 := P;
  1048. #endif
  1049. CreateDonateLink( 9, P);
  1050. CreateDonateLink(19, P);
  1051. CreateDonateLink(49, P);
  1052. AboutDonationCaption := TLabel.Create(DonationPanel);
  1053. AboutDonationCaption.Left := 0;
  1054. AboutDonationCaption.Top := P;
  1055. AboutDonationCaption.Parent := DonationPanel;
  1056. AboutDonationCaption.Caption := CustomMessage('AboutDonations');
  1057. AboutDonationCaption.OnClick := @AboutDonationsLinkClick;
  1058. LinkLabel(AboutDonationCaption);
  1059. #ifdef ImagesDir
  1060. Image := TBitmapImage.Create(DonationPanel);
  1061. LoadEmbededBitmap(Image, '{#PayPalCardImage}', DonationPanel.Color);
  1062. Image.AutoSize := True;
  1063. Image.Cursor := crHand;
  1064. Image.Parent := DonationPanel;
  1065. Image.Left := ScaleX(108);
  1066. Image.Top := P2 + ScaleX(8);
  1067. Image.Hint := CustomMessage('AboutDonations');
  1068. Image.ShowHint := True;
  1069. Image.OnClick := @AboutDonationsLinkClick;
  1070. #endif
  1071. DonationPanel.Height := GetBottom(AboutDonationCaption);
  1072. #endif
  1073. WizardForm.YesRadio.OnClick := @UpdatePostInstallRunCheckboxes;
  1074. WizardForm.NoRadio.OnClick := @UpdatePostInstallRunCheckboxes;
  1075. UpdatePostInstallRunCheckboxes(nil);
  1076. // 100% images are automatically loaded by
  1077. // WizardImageFile and WizardSmallImageFile
  1078. if GetScalingFactor > 100 then
  1079. begin
  1080. LoadEmbededScaledBitmap(WizardForm.WizardBitmapImage, '{#WizardImageFileBase}', 100, 0);
  1081. LoadEmbededScaledBitmap(WizardForm.WizardBitmapImage2, '{#WizardImageFileBase}', 100, 0);
  1082. LoadEmbededScaledBitmap(WizardForm.WizardSmallBitmapImage, '{#WizardSmallImageFileBase}', 100, 0);
  1083. end;
  1084. #ifdef ImagesDir
  1085. // Text does not scale as quick as with DPI,
  1086. // so the icon may overlap the labels. Shift them.
  1087. P := WizardForm.SelectDirBitmapImage.Width;
  1088. LoadEmbededScaledIcon(WizardForm.SelectDirBitmapImage, '{#SelectDirFileBase}', 32, WizardForm.SelectDirPage.Color);
  1089. P := (WizardForm.SelectDirBitmapImage.Width - P);
  1090. // Vertical change should be the same as horizontal
  1091. WizardForm.SelectDirLabel.Left := WizardForm.SelectDirLabel.Left + P;
  1092. WizardForm.SelectDirBrowseLabel.Top := WizardForm.SelectDirBrowseLabel.Top + P;
  1093. WizardForm.DirEdit.Top := WizardForm.DirEdit.Top + P;
  1094. WizardForm.DirBrowseButton.Top := WizardForm.DirBrowseButton.Top + P;
  1095. #endif
  1096. end;
  1097. procedure RegisterPreviousData(PreviousDataKey: Integer);
  1098. var
  1099. S: string;
  1100. begin
  1101. if IsTypicalInstallation then S := 'typical'
  1102. else S := 'custom';
  1103. SetPreviousData(PreviousDataKey, '{#SetupTypeData}', S);
  1104. end;
  1105. function SaveCheckListBoxState(ListBox: TNewCheckListBox): string;
  1106. var
  1107. I: Integer;
  1108. begin
  1109. for I := 0 to ListBox.Items.Count - 1 do
  1110. begin
  1111. Result := Result + IntToStr(Integer(ListBox.State[I]));
  1112. end;
  1113. end;
  1114. procedure CurPageChanged(CurPageID: Integer);
  1115. var
  1116. Delta: Integer;
  1117. LineHeight: Integer;
  1118. LaunchCheckboxTop: Integer;
  1119. S: string;
  1120. begin
  1121. if CurPageID = wpLicense then
  1122. begin
  1123. WizardForm.NextButton.Caption := CustomMessage('AcceptButton')
  1124. end;
  1125. if CurPageID = wpSelectDir then
  1126. begin
  1127. if InitDir = '' then
  1128. InitDir := WizardForm.DirEdit.Text;
  1129. end
  1130. else
  1131. if CurPageID = wpSelectComponents then
  1132. begin
  1133. if InitComponents = '' then
  1134. InitComponents := SaveCheckListBoxState(WizardForm.ComponentsList);
  1135. end
  1136. else
  1137. if CurPageID = wpSelectTasks then
  1138. begin
  1139. if InitTasks = '' then
  1140. InitTasks := SaveCheckListBoxState(WizardForm.TasksList);
  1141. end
  1142. else
  1143. if CurPageID = InterfacePage.ID then
  1144. begin
  1145. if InitInterface < 0 then
  1146. InitInterface := Integer(CommanderRadioButton.Checked);
  1147. end
  1148. else
  1149. if CurPageID = wpFinished then
  1150. begin
  1151. LineHeight := (WizardForm.NoRadio.Top - WizardForm.YesRadio.Top);
  1152. // Are we at the "Restart?" screen
  1153. // Note that it's not possible to get to the "finished" page more than once,
  1154. // so the code below does not expect re-entry
  1155. if IsRestartPage then
  1156. begin
  1157. if ShellExtNoRestart then
  1158. begin
  1159. Log('Hiding restart page as it''s not critical to replace the shell extension');
  1160. WizardForm.YesRadio.Visible := False;
  1161. WizardForm.NoRadio.Visible := False;
  1162. WizardForm.NoRadio.Checked := True;
  1163. S := SetupMessage(msgFinishedLabel);
  1164. StringChange(S, '[name]', 'WinSCP');
  1165. WizardForm.FinishedLabel.Caption :=
  1166. S + NewLine + NewLine +
  1167. // The additional new line is a padding for the "launch check box",
  1168. // as the same padding is there for the YesRadio too.
  1169. SetupMessage(msgClickFinish) + NewLine;
  1170. Log(WizardForm.FinishedLabel.Caption);
  1171. Delta := WizardForm.AdjustLabelHeight(WizardForm.FinishedLabel);
  1172. LaunchCheckboxTop := WizardForm.YesRadio.Top + Delta;
  1173. end
  1174. else
  1175. begin
  1176. WizardForm.FinishedLabel.Caption :=
  1177. CustomMessage('FinishedRestartDragExtLabel') + NewLine;
  1178. Delta := WizardForm.AdjustLabelHeight(WizardForm.FinishedLabel);
  1179. WizardForm.YesRadio.Top := WizardForm.YesRadio.Top + Delta;
  1180. WizardForm.NoRadio.Top := WizardForm.NoRadio.Top + Delta;
  1181. LaunchCheckboxTop := WizardForm.NoRadio.Top + LineHeight;
  1182. #ifdef Donations
  1183. DonationPanel.Visible := False;
  1184. #endif
  1185. end;
  1186. end
  1187. else
  1188. begin
  1189. LaunchCheckboxTop := WizardForm.RunList.Top;
  1190. end;
  1191. LaunchCheckbox.Top := LaunchCheckboxTop;
  1192. OpenGettingStartedCheckbox.Top := LaunchCheckbox.Top + LineHeight;
  1193. UpdatePostInstallRunCheckboxes(nil);
  1194. #ifdef Donations
  1195. if DonationPanel.Visible then
  1196. begin
  1197. DonationPanel.Top := GetBottom(OpenGettingStartedCheckbox) + ScaleY(12);
  1198. // Hide "about donations" if it does not fit nicely
  1199. // (happens on "long" languages, as German)
  1200. if (DonationPanel.Top + GetBottom(AboutDonationCaption)) >
  1201. (WizardForm.FinishedPage.Height - ScaleY(8)) then
  1202. begin
  1203. AboutDonationCaption.Visible := False;
  1204. end;
  1205. end;
  1206. #endif
  1207. end
  1208. else
  1209. if CurPageID = SetupTypePage.ID then
  1210. begin
  1211. Log('License accepted');
  1212. LicenseAccepted := True;
  1213. end;
  1214. end;
  1215. function AskedRestart: Boolean;
  1216. begin
  1217. Result := IsRestartPage;
  1218. end;
  1219. procedure DeinitializeSetup;
  1220. var
  1221. WinHttpReq: Variant;
  1222. ReportUrl: string;
  1223. ReportData: string;
  1224. begin
  1225. // cannot send report, unless user already accepted license
  1226. // (with privacy policy)
  1227. if LicenseAccepted then
  1228. begin
  1229. Log('Preparing installation report');
  1230. ReportData := Format(
  1231. 'installed=%d&silent=%d&ver=%s&lang=%s&prevver=%s&', [
  1232. Integer(InstallationDone), Integer(WizardSilent),
  1233. '{#VersionOnly}', ActiveLanguage,
  1234. PrevVersion]);
  1235. try
  1236. ReportUrl := '{#WebReport}?' + ReportData;
  1237. Log('Sending installation report: ' + ReportUrl);
  1238. WinHttpReq := CreateOleObject('WinHttp.WinHttpRequest.5.1');
  1239. WinHttpReq.Open('GET', ReportUrl, False);
  1240. WinHttpReq.Send('');
  1241. Log('Installation report send result: ' + IntToStr(WinHttpReq.Status) + ' ' + WinHttpReq.StatusText);
  1242. except
  1243. Log('Error sending installation report: ' + GetExceptionMessage);
  1244. end;
  1245. end;
  1246. end;
  1247. procedure ExecApp(Params: string; ShowCmd: Integer; Wait: TExecWait);
  1248. var
  1249. Path: string;
  1250. ErrorCode: Integer;
  1251. begin
  1252. Path := ExpandConstant('{app}\{#MainFileName}');
  1253. ExecAsOriginalUser(Path, Params, '', ShowCmd, Wait, ErrorCode)
  1254. end;
  1255. procedure OpenBrowserGettingStarted;
  1256. var
  1257. WebGettingStarted: string;
  1258. begin
  1259. WebGettingStarted :=
  1260. ExpandConstant('{#WebGettingStarted}') + PrevVersion +
  1261. '&automatic=' + IntToStr(Integer(AutomaticUpdate));
  1262. Log('Opening getting started page: ' + WebGettingStarted);
  1263. OpenBrowser(WebGettingStarted);
  1264. end;
  1265. procedure CurStepChanged(CurStep: TSetupStep);
  1266. var
  1267. ShowCmd: Integer;
  1268. OpenGettingStarted: Boolean;
  1269. UsageData: string;
  1270. CanPostInstallRuns: Boolean;
  1271. Installations: Cardinal;
  1272. begin
  1273. if CurStep = ssPostInstall then
  1274. begin
  1275. Log('Post install');
  1276. InstallationDone := True;
  1277. end
  1278. else
  1279. if CurStep = ssDone then
  1280. begin
  1281. Log('Done');
  1282. // bug in InnoSetup causes it using ssDone even when
  1283. // setup failed because machine was not restarted to complete previous
  1284. // installation. double check that ssPostInstall was called
  1285. if InstallationDone then
  1286. begin
  1287. CanPostInstallRuns := (not WizardSilent) and (not WillRestart);
  1288. OpenGettingStarted :=
  1289. OpenGettingStartedCheckbox.Enabled and
  1290. OpenGettingStartedCheckbox.Checked;
  1291. UsageData := '/Usage=';
  1292. // old style counter
  1293. UsageData := UsageData + Format('TypicalInstallation:%d,', [Integer(IsTypicalInstallation)]);
  1294. UsageData := UsageData + 'InstallationsUser+,';
  1295. Installations := 0; // default, if the counter does not exist
  1296. RegQueryDWordValue(HKEY_LOCAL_MACHINE, '{#RegistryKey}', 'Installations', Installations);
  1297. Inc(Installations);
  1298. if not RegWriteDWordValue(HKEY_LOCAL_MACHINE, '{#RegistryKey}', 'Installations', Installations) then
  1299. begin
  1300. Log('Cannot increment administrator installations counter, probably a non-elevated installation');
  1301. end;
  1302. // new style counters
  1303. if not Upgrade then
  1304. begin
  1305. if IsTypicalInstallation then
  1306. UsageData := UsageData + 'InstallationsFirstTypical+,'
  1307. else
  1308. UsageData := UsageData + 'InstallationsFirstCustom+,';
  1309. end
  1310. else
  1311. begin
  1312. if AutomaticUpdate then
  1313. UsageData := UsageData + 'InstallationsUpgradeAutomatic+,'
  1314. else if IsTypicalInstallation then
  1315. UsageData := UsageData + 'InstallationsUpgradeTypical+,'
  1316. else
  1317. UsageData := UsageData + 'InstallationsUpgradeCustom+,';
  1318. end;
  1319. UsageData := UsageData + Format('LastInstallationAutomaticUpgrade:%d,', [Integer(AutomaticUpdate)]);
  1320. if (InitDir <> '') and (InitDir <> WizardForm.DirEdit.Text) then
  1321. UsageData := UsageData + 'InstallationsCustomDir+,';
  1322. if (InitComponents <> '') and (InitComponents <> SaveCheckListBoxState(WizardForm.ComponentsList)) then
  1323. UsageData := UsageData + 'InstallationsCustomComponents+,';
  1324. if (InitTasks <> '') and (InitTasks <> SaveCheckListBoxState(WizardForm.TasksList)) then
  1325. UsageData := UsageData + 'InstallationsCustomTasks+,';
  1326. if (InitInterface >= 0) and (InitInterface <> Integer(CommanderRadioButton.Checked)) then
  1327. UsageData := UsageData + 'InstallationsCustomInterface+,';
  1328. if CanPostInstallRuns and OpenGettingStarted then
  1329. UsageData := UsageData + 'InstallationsGettingStarted+,';
  1330. if CanPostInstallRuns and LaunchCheckbox.Checked then
  1331. UsageData := UsageData + 'InstallationsLaunch+,';
  1332. if WizardSilent then
  1333. UsageData := UsageData + 'InstallationsSilent+,';
  1334. if AskedRestart then
  1335. UsageData := UsageData + 'InstallationsNeedRestart+,';
  1336. if WillRestart then
  1337. UsageData := UsageData + 'InstallationsRestart+,';
  1338. if Donated then
  1339. UsageData := UsageData + 'InstallationsDonate+,';
  1340. if not IsElevated then
  1341. UsageData := UsageData + 'InstallationsNonElevated+,';
  1342. // have to do this before running WinSCP GUI instance below,
  1343. // otherwise it loads the empty/previous counters and overwrites our changes,
  1344. // when it's closed
  1345. Log('Recording installer usage statistics: ' + UsageData);
  1346. // make sure we write the counters using the "normal" account
  1347. // (the account that will be used to report the counters)
  1348. ExecApp(UsageData, SW_HIDE, ewWaitUntilTerminated);
  1349. if AutomaticUpdate then
  1350. begin
  1351. Log('Launching WinSCP after automatic update');
  1352. ExecApp('', SW_SHOWNORMAL, ewNoWait);
  1353. if CmdLineParamExists('/OpenGettingStarted') then
  1354. begin
  1355. OpenBrowserGettingStarted;
  1356. end;
  1357. end
  1358. else
  1359. if CanPostInstallRuns then
  1360. begin
  1361. if OpenGettingStarted then
  1362. begin
  1363. OpenBrowserGettingStarted;
  1364. end;
  1365. if LaunchCheckbox.Checked then
  1366. begin
  1367. if OpenGettingStarted then
  1368. begin
  1369. Log('Will launch WinSCP minimized');
  1370. ShowCmd := SW_SHOWMINIMIZED
  1371. end
  1372. else
  1373. begin
  1374. ShowCmd := SW_SHOWNORMAL;
  1375. end;
  1376. Log('Launching WinSCP');
  1377. ExecApp('', ShowCmd, ewNoWait);
  1378. end;
  1379. end;
  1380. end;
  1381. end;
  1382. end;
  1383. function ShouldSkipPage(PageID: Integer): Boolean;
  1384. begin
  1385. Result :=
  1386. { Hide most pages during typical installation }
  1387. IsTypicalInstallation and
  1388. ((PageID = wpSelectDir) or (PageID = wpSelectComponents) or
  1389. (PageID = wpSelectTasks) or
  1390. { Hide Interface page for upgrades only, show for fresh installs }
  1391. ((PageID = InterfacePage.ID) and Upgrade));
  1392. end;
  1393. function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo,
  1394. MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: string): string;
  1395. var
  1396. S: string;
  1397. S2: string;
  1398. begin
  1399. S := '';
  1400. S := S + MemoDirInfo + NewLine + NewLine;
  1401. if not Upgrade then
  1402. begin
  1403. if IsTypicalInstallation then S2 := CustomMessage('TypicalType')
  1404. else S2 := CustomMessage('CustomType');
  1405. end
  1406. else
  1407. begin
  1408. if IsTypicalInstallation then S2 := CustomMessage('TypicalUpgradeType')
  1409. else S2 := CustomMessage('CustomUpgradeType');
  1410. end;
  1411. StringChange(S2, '&', '');
  1412. S := S + SetupMessage(msgReadyMemoType) + NewLine + Space + S2 + NewLine + NewLine;
  1413. S := S + MemoComponentsInfo + NewLine + NewLine;
  1414. if Length(MemoGroupInfo) > 0 then
  1415. S := S + MemoGroupInfo + NewLine + NewLine;
  1416. if Length(MemoTasksInfo) > 0 then
  1417. S := S + MemoTasksInfo + NewLine + NewLine;
  1418. S := S + CustomMessage('UserSettingsOverview') + NewLine;
  1419. S := S + Space;
  1420. if CommanderRadioButton.Checked then S2 := CustomMessage('NortonCommanderInterfaceC')
  1421. else S2 := CustomMessage('ExplorerInterfaceC');
  1422. StringChange(S2, '&', '');
  1423. S := S + S2;
  1424. S := S + NewLine;
  1425. Result := S;
  1426. end;
  1427. function InitializeUninstall: Boolean;
  1428. begin
  1429. // let application know that we are running silent uninstall,
  1430. // this turns UninstallCleanup to noop
  1431. if UninstallSilent then
  1432. CreateMutex('WinSCPSilentUninstall');
  1433. Result := True;
  1434. end;
  1435. function HasUserPrograms: Boolean;
  1436. begin
  1437. // To avoid the installer failing when the {userprograms}
  1438. // cannot be resolved (when installing via system account or SCCM)
  1439. try
  1440. ExpandConstant('{userprograms}');
  1441. Log('Have user programs');
  1442. Result := True;
  1443. except
  1444. Log('Does not have user programs');
  1445. Result := False;
  1446. end;
  1447. end;
  1448. #expr SaveToFile(AddBackslash(SourcePath) + "Preprocessed.iss")