Setup.Install.pas 127 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970
  1. unit Setup.Install;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2026 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Installation procedures
  8. }
  9. interface
  10. procedure PerformInstall(var Succeeded: Boolean; const ChangesEnvironment,
  11. ChangesAssociations: Boolean);
  12. implementation
  13. uses
  14. Windows, Messages, ShlObj, RegStr, Classes, SysUtils, Forms,
  15. ISSigFunc, PathFunc, SHA256, UnsignedFunc,
  16. Shared.CommonFunc, Shared.CommonFunc.Vcl, Shared.FileClass,
  17. Shared.SetupMessageIDs, Shared.SetupTypes, Shared.Struct, Shared.VerInfoFunc,
  18. Compression.Base, Compression.SevenZipDLLDecoder,
  19. SetupLdrAndSetup.InstFunc, SetupLdrAndSetup.Messages, Setup.PathRedir,
  20. Setup.DebugClient, Setup.DotNetFunc, Setup.DownloadFileFunc, Setup.InstFunc, Setup.InstFunc.Ole,
  21. Setup.ISSigVerifyFunc, Setup.FileExtractor, Setup.Install.HelperFunc,
  22. Setup.MainFunc, Setup.LoggingFunc, Setup.RegDLL, Setup.SecurityFunc,
  23. Setup.UninstallLog, Setup.WizardForm;
  24. type
  25. PRegisterFilesListRec = ^TRegisterFilesListRec;
  26. TRegisterFilesListRec = record
  27. Filename: String;
  28. Is64Bit, TypeLib, NoErrorMessages: Boolean;
  29. end;
  30. TUninstallExeCreated = (ueNone, ueNew, ueReplaced);
  31. procedure RecordStartInstall(const UninstLog: TUninstallLog);
  32. var
  33. AppDir: String;
  34. begin
  35. if shCreateAppDir in SetupHeader.Options then
  36. AppDir := WizardDirValue
  37. else
  38. AppDir := '';
  39. UninstLog.Add(utStartInstall, [GetComputerNameString, GetUserNameString,
  40. AppDir, GetLocalTimeAsStr], 0);
  41. end;
  42. procedure RecordCompiledCode(const UninstLog: TUninstallLog);
  43. var
  44. LeadBytesStr, ExpandedApp, ExpandedGroup, CustomMessagesStr: String;
  45. begin
  46. { Only use app if Setup creates one }
  47. if shCreateAppDir in SetupHeader.Options then
  48. ExpandedApp := ExpandConst('{app}')
  49. else
  50. ExpandedApp := '';
  51. try
  52. ExpandedGroup := ExpandConst('{group}');
  53. except
  54. { Yep, expanding "group" might fail with an exception }
  55. ExpandedGroup := '';
  56. end;
  57. if SetupHeader.CompiledCodeText <> '' then
  58. PackCustomMessagesIntoString(CustomMessagesStr);
  59. { Record [Code] even if empty to 'overwrite' old versions }
  60. UninstLog.Add(utCompiledCode, [PackCompiledCodeTextIntoString(SetupHeader.CompiledCodeText),
  61. LeadBytesStr, ExpandedApp, ExpandedGroup, WizardGroupValue,
  62. ExpandConst('{language}'), CustomMessagesStr], SetupBinVersion {$IFDEF WIN64} or $80000000 {$ENDIF});
  63. end;
  64. procedure RegisterUninstallInfo(const UninstLog: TUninstallLog; const UninstallRegKeyBaseName: String;
  65. const AfterInstallFilesSize: Int64);
  66. { Stores uninstall information in the Registry so that the program can be
  67. uninstalled through the Control Panel Add/Remove Programs applet. }
  68. const
  69. AdminInstallModeNames: array [Boolean] of String =
  70. ('non administrative', 'administrative');
  71. BitInstallModeNames: array [Boolean] of String =
  72. ('32-bit', '64-bit');
  73. var
  74. RegView, OppositeRegView: TRegView;
  75. RegViewIs64Bit, OppositeRegViewIs64Bit: Boolean;
  76. RootKey, OppositeRootKey: HKEY;
  77. RootKeyIsHKLM, OppositeRootKeyIsHKLM: Boolean;
  78. SubkeyName: String;
  79. procedure SetStringValue(const K: HKEY; const ValueName: PChar;
  80. const Data: String);
  81. begin
  82. const ErrorCode = DWORD(RegSetValueEx(K, ValueName, 0, REG_SZ, PChar(Data),
  83. (ULength(Data)+1)*SizeOf(Data[1])));
  84. if ErrorCode <> ERROR_SUCCESS then
  85. RegError(reRegSetValueEx, RootKey, SubkeyName, ErrorCode);
  86. end;
  87. procedure SetStringValueUnlessEmpty(const K: HKEY; const ValueName: PChar;
  88. const Data: String);
  89. begin
  90. if Data <> '' then
  91. SetStringValue(K, ValueName, Data);
  92. end;
  93. procedure SetDWordValue(const K: HKEY; const ValueName: PChar;
  94. const Data: DWord);
  95. begin
  96. const ErrorCode = DWORD(RegSetValueEx(K, ValueName, 0, REG_DWORD, @Data,
  97. SizeOf(Data)));
  98. if ErrorCode <> ERROR_SUCCESS then
  99. RegError(reRegSetValueEx, RootKey, SubkeyName, ErrorCode);
  100. end;
  101. function GetInstallDateString: String;
  102. var
  103. ST: TSystemTime;
  104. begin
  105. GetLocalTime(ST);
  106. Result := Format('%.4u%.2u%.2u', [ST.wYear, ST.wMonth, ST.wDay]);
  107. end;
  108. function ExtractMajorMinorVersion(Version: String; var Major, Minor: Cardinal): Boolean;
  109. var
  110. P, I: Integer;
  111. begin
  112. P := Pos('.', Version);
  113. if P <> 0 then begin
  114. Val(Copy(Version, 1, P-1), Major, I);
  115. if I = 0 then begin
  116. Delete(Version, 1, P);
  117. P := Pos('.', Version);
  118. if P <> 0 then
  119. Val(Copy(Version, 1, P-1), Minor, I)
  120. else
  121. Val(Version, Minor, I);
  122. end;
  123. end else begin
  124. Val(Version, Major, I);
  125. Minor := 0;
  126. end;
  127. Result := I = 0;
  128. end;
  129. { Also see Main.pas }
  130. function ExistingInstallationAt(const RegView: TRegView; const RootKey: HKEY): Boolean;
  131. var
  132. K: HKEY;
  133. begin
  134. if RegOpenKeyExView(RegView, RootKey, PChar(SubkeyName), 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  135. Result := True;
  136. RegCloseKey(K);
  137. end else
  138. Result := False;
  139. end;
  140. function ApplyDisplayNameMarks(const DisplayName: String;
  141. const ExistingAtOppositeAdminInstallMode, ExistingAtOpposite64BitInstallMode: Boolean): String;
  142. const
  143. UninstallDisplayNameMarksUser: array [Boolean] of TSetupMessageId =
  144. (msgUninstallDisplayNameMarkCurrentUser, msgUninstallDisplayNameMarkAllUsers);
  145. UninstallDisplayNameMarksBits: array [Boolean] of TSetupMessageId =
  146. (msgUninstallDisplayNameMark32Bit, msgUninstallDisplayNameMark64Bit);
  147. begin
  148. if ExistingAtOppositeAdminInstallMode and ExistingAtOpposite64BitInstallMode then
  149. Result := FmtSetupMessage(msgUninstallDisplayNameMarks,
  150. [DisplayName, SetupMessages[UninstallDisplayNameMarksUser[RootKeyIsHKLM]],
  151. SetupMessages[UninstallDisplayNameMarksBits[RegViewIs64Bit]]])
  152. else if ExistingAtOppositeAdminInstallMode then
  153. Result := FmtSetupMessage(msgUninstallDisplayNameMark,
  154. [DisplayName, SetupMessages[UninstallDisplayNameMarksUser[RootKeyIsHKLM]]])
  155. else
  156. Result := FmtSetupMessage(msgUninstallDisplayNameMark,
  157. [DisplayName, SetupMessages[UninstallDisplayNameMarksBits[RegViewIs64Bit]]]);
  158. end;
  159. procedure HandleDuplicateDisplayNames(var DisplayName: String);
  160. begin
  161. { Check opposite administrative install mode. }
  162. var ExistingAtOppositeAdminInstallMode := ExistingInstallationAt(RegView, OppositeRootKey);
  163. if RootKeyIsHKLM or not IsWin64 then begin
  164. { Opposite (HKCU) is shared for 32-bit and 64-bit so don't log bitness. Also don't log bitness on a 32-bit system. }
  165. LogFmt('Detected previous %s install? %s',
  166. [AdminInstallModeNames[OppositeRootKeyIsHKLM {always False}], SYesNo[ExistingAtOppositeAdminInstallMode]])
  167. end else begin
  168. { Opposite (HKLM) is not shared for 32-bit and 64-bit so log bitness. }
  169. LogFmt('Detected previous %s %s install? %s',
  170. [AdminInstallModeNames[OppositeRootKeyIsHKLM {always True}], BitInstallModeNames[RegViewIs64Bit], SYesNo[ExistingAtOppositeAdminInstallMode]]);
  171. end;
  172. var ExistingAtOpposite64BitInstallMode: Boolean;
  173. if IsWin64 then begin
  174. { Check opposite 32-bit or 64-bit install mode. }
  175. if RootKeyIsHKLM then begin
  176. { HKLM is not shared for 32-bit and 64-bit so check it for opposite 32-bit or 64-bit install mode. Not checking HKCU
  177. since HKCU is shared for 32-bit and 64-bit mode and we already checked HKCU above. }
  178. ExistingAtOpposite64BitInstallMode := ExistingInstallationAt(OppositeRegView, RootKey {always HKLM});
  179. LogFmt('Detected previous %s %s install? %s',
  180. [AdminInstallModeNames[RootKeyIsHKLM {always True}], BitInstallModeNames[OppositeRegViewIs64Bit], SYesNo[ExistingAtOpposite64BitInstallMode]]);
  181. end else begin
  182. { HKCU is shared for 32-bit and 64-bit so not checking it but we do still need to check HKLM for opposite 32-bit or
  183. 64-bit install mode since we haven't already done that. }
  184. ExistingAtOpposite64BitInstallMode := ExistingInstallationAt(OppositeRegView, OppositeRootKey {always HKLM});
  185. if ExistingAtOpposite64BitInstallMode then
  186. ExistingAtOppositeAdminInstallMode := True;
  187. LogFmt('Detected previous %s %s install? %s',
  188. [AdminInstallModeNames[OppositeRootKeyIsHKLM {always True}], BitInstallModeNames[OppositeRegViewIs64Bit], SYesNo[ExistingAtOpposite64BitInstallMode]]);
  189. end;
  190. end else
  191. ExistingAtOpposite64BitInstallMode := False;
  192. { Mark new display name if needed. Note: currently we don't attempt to mark existing display names as well. }
  193. if ExistingAtOppositeAdminInstallMode or ExistingAtOpposite64BitInstallMode then begin
  194. DisplayName := ApplyDisplayNameMarks(DisplayName, ExistingAtOppositeAdminInstallMode,
  195. ExistingAtOpposite64BitInstallMode);
  196. LogFmt('Marked uninstall display name to avoid duplicate entries. New display name: %s', [DisplayName]);
  197. end;
  198. end;
  199. var
  200. H2: HKEY;
  201. Z: String;
  202. EstimatedSize: Int64;
  203. begin
  204. RegView := InstallDefaultRegView;
  205. RegViewIs64Bit := RegView in RegViews64Bit;
  206. if RegViewIs64Bit then
  207. OppositeRegView := rv32Bit
  208. else
  209. OppositeRegView := rv64Bit;
  210. OppositeRegViewIs64Bit := not RegViewIs64Bit;
  211. RootKey := InstallModeRootKey;
  212. RootKeyIsHKLM := RootKey = HKEY_LOCAL_MACHINE;
  213. if RootKeyIsHKLM then
  214. OppositeRootKey := HKEY_CURRENT_USER
  215. else
  216. OppositeRootKey := HKEY_LOCAL_MACHINE;
  217. OppositeRootKeyIsHKLM := not RootKeyIsHKLM;
  218. SubkeyName := GetUninstallRegSubkeyName(UninstallRegKeyBaseName);
  219. if ExistingInstallationAt(RegView, RootKey) then begin
  220. if RootKeyIsHKLM then begin
  221. { HKLM is not shared for 32-bit and 64-bit so log bitness. }
  222. LogFmt('Deleting uninstall key left over from previous %s %s install.',
  223. [AdminInstallModeNames[RootKeyIsHKLM {always True}], BitInstallModeNames[RegViewIs64Bit]]);
  224. end else begin
  225. { HKCU is shared for 32-bit and 64-bit so don't log bitness. }
  226. LogFmt('Deleting uninstall key left over from previous %s install.',
  227. [AdminInstallModeNames[RootKeyIsHKLM {always False}]])
  228. end;
  229. RegDeleteKeyIncludingSubkeys(RegView, RootKey, PChar(SubkeyName));
  230. end;
  231. LogFmt('Creating new uninstall key: %s\%s', [GetRegRootKeyName(RootKey), SubkeyName]);
  232. { Create uninstall key }
  233. const ErrorCode = DWORD(RegCreateKeyExView(RegView, RootKey, PChar(SubkeyName),
  234. 0, nil, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nil, H2, nil));
  235. if ErrorCode <> ERROR_SUCCESS then
  236. RegError(reRegCreateKeyEx, RootKey, SubkeyName, ErrorCode);
  237. try
  238. Log('Writing uninstall key values.');
  239. { do not localize or change any of the following strings }
  240. SetStringValue(H2, 'Inno Setup: Setup Version', SetupVersion);
  241. if shCreateAppDir in SetupHeader.Options then
  242. Z := WizardDirValue
  243. else
  244. Z := '';
  245. SetStringValue(H2, 'Inno Setup: App Path', Z);
  246. SetStringValueUnlessEmpty(H2, 'InstallLocation', AddBackslash(Z));
  247. SetStringValue(H2, 'Inno Setup: Icon Group', WizardGroupValue);
  248. if WizardNoIcons then
  249. SetDWordValue(H2, 'Inno Setup: No Icons', 1);
  250. SetStringValue(H2, 'Inno Setup: User', GetUserNameString);
  251. if WizardSetupType <> nil then begin
  252. SetStringValue(H2, 'Inno Setup: Setup Type', WizardSetupType.Name);
  253. SetStringValue(H2, 'Inno Setup: Selected Components', StringsToCommaString(WizardComponents));
  254. SetStringValue(H2, 'Inno Setup: Deselected Components', StringsToCommaString(WizardDeselectedComponents));
  255. end;
  256. if HasTasks then begin
  257. SetStringValue(H2, 'Inno Setup: Selected Tasks', StringsToCommaString(WizardTasks));
  258. SetStringValue(H2, 'Inno Setup: Deselected Tasks', StringsToCommaString(WizardDeselectedTasks));
  259. end;
  260. if shUserInfoPage in SetupHeader.Options then begin
  261. SetStringValue(H2, 'Inno Setup: User Info: Name', WizardUserInfoName);
  262. SetStringValue(H2, 'Inno Setup: User Info: Organization', WizardUserInfoOrg);
  263. SetStringValue(H2, 'Inno Setup: User Info: Serial', WizardUserInfoSerial);
  264. end;
  265. SetStringValue(H2, 'Inno Setup: Language', PSetupLanguageEntry(Entries[seLanguage][ActiveLanguage]).Name);
  266. if SetupHeader.UninstallDisplayName <> '' then
  267. Z := ExpandConst(SetupHeader.UninstallDisplayName)
  268. else
  269. Z := ExpandedAppVerName;
  270. HandleDuplicateDisplayNames(Z);
  271. { For the entry to appear in ARP, DisplayName cannot exceed 259 characters
  272. on Windows 2000 and later. }
  273. SetStringValue(H2, 'DisplayName', Copy(Z, 1, 259));
  274. SetStringValueUnlessEmpty(H2, 'DisplayIcon', ExpandConst(SetupHeader.UninstallDisplayIcon));
  275. var ExtraUninstallString: String;
  276. if shUninstallLogging in SetupHeader.Options then
  277. ExtraUninstallString := ' /LOG'
  278. else
  279. ExtraUninstallString := '';
  280. SetStringValue(H2, 'UninstallString', '"' + UninstallExeFilename + '"' + ExtraUninstallString);
  281. SetStringValue(H2, 'QuietUninstallString', '"' + UninstallExeFilename + '" /SILENT' + ExtraUninstallString);
  282. SetStringValueUnlessEmpty(H2, 'DisplayVersion', ExpandConst(SetupHeader.AppVersion));
  283. SetStringValueUnlessEmpty(H2, 'Publisher', ExpandConst(SetupHeader.AppPublisher));
  284. SetStringValueUnlessEmpty(H2, 'URLInfoAbout', ExpandConst(SetupHeader.AppPublisherURL));
  285. SetStringValueUnlessEmpty(H2, 'HelpTelephone', ExpandConst(SetupHeader.AppSupportPhone));
  286. SetStringValueUnlessEmpty(H2, 'HelpLink', ExpandConst(SetupHeader.AppSupportURL));
  287. SetStringValueUnlessEmpty(H2, 'URLUpdateInfo', ExpandConst(SetupHeader.AppUpdatesURL));
  288. SetStringValueUnlessEmpty(H2, 'Readme', ExpandConst(SetupHeader.AppReadmeFile));
  289. SetStringValueUnlessEmpty(H2, 'Contact', ExpandConst(SetupHeader.AppContact));
  290. SetStringValueUnlessEmpty(H2, 'Comments', ExpandConst(SetupHeader.AppComments));
  291. Z := ExpandConst(SetupHeader.AppModifyPath);
  292. if Z <> '' then
  293. SetStringValue(H2, 'ModifyPath', Z)
  294. else
  295. SetDWordValue(H2, 'NoModify', 1);
  296. SetDWordValue(H2, 'NoRepair', 1);
  297. SetStringValue(H2, 'InstallDate', GetInstallDateString);
  298. var MajorVersion, MinorVersion: Cardinal;
  299. if ExtractMajorMinorVersion(ExpandConst(SetupHeader.AppVersion), MajorVersion, MinorVersion) then begin
  300. { Originally MSDN said to write to Major/MinorVersion, now it says to write to VersionMajor/Minor. So write to both. }
  301. SetDWordValue(H2, 'MajorVersion', MajorVersion);
  302. SetDWordValue(H2, 'MinorVersion', MinorVersion);
  303. SetDWordValue(H2, 'VersionMajor', MajorVersion);
  304. SetDWordValue(H2, 'VersionMinor', MinorVersion);
  305. end;
  306. { Note: Windows 7 (and later?) doesn't automatically calculate sizes so set EstimatedSize ourselves. }
  307. if SetupHeader.UninstallDisplaySize = 0 then begin
  308. { Estimate the size by taking the size of all files and adding any ExtraDiskSpaceRequired. }
  309. EstimatedSize := AfterInstallFilesSize + SetupHeader.ExtraDiskSpaceRequired;
  310. for var I := 0 to Entries[seComponent].Count-1 do begin
  311. with PSetupComponentEntry(Entries[seComponent][I])^ do begin
  312. if ShouldProcessEntry(WizardComponents, nil, Name, '', Languages, '') then
  313. Inc(EstimatedSize, ExtraDiskSpaceRequired);
  314. end;
  315. end;
  316. end else
  317. EstimatedSize := SetupHeader.UninstallDisplaySize;
  318. { ARP on Windows 7 without SP1 only pays attention to the lower 6 bytes of EstimatedSize and
  319. throws away the rest. For example putting in $4000001 (=4GB + 1KB) displays as 1 KB.
  320. So we need to check for this. }
  321. if (Hi(NTServicePackLevel) > 0) or IsWindows8 or (EstimatedSize <= High(Cardinal)) then begin
  322. EstimatedSize := EstimatedSize div 1024;
  323. SetDWordValue(H2, 'EstimatedSize', DWORD(EstimatedSize));
  324. end;
  325. { Also see SetPreviousData in ScriptFunc.pas }
  326. if CodeRunner <> nil then begin
  327. try
  328. CodeRunner.RunProcedures('RegisterPreviousData', [Integer(H2)], False);
  329. except
  330. Log('RegisterPreviousData raised an exception.');
  331. Application.HandleException(nil);
  332. end;
  333. end;
  334. finally
  335. RegCloseKey(H2);
  336. end;
  337. UninstLog.AddReg(utRegDeleteEntireKey, RegView, RootKey,
  338. [SubkeyName]);
  339. end;
  340. type
  341. TMakeDirFlags = set of (mdNoUninstall, mdAlwaysUninstall, mdDeleteAfterInstall,
  342. mdNotifyChange);
  343. function MakeDir(const UninstLog: TUninstallLog; Dir: String;
  344. const Flags: TMakeDirFlags = []): Boolean;
  345. { Returns True if a new directory was created. Also see ForceDirectories
  346. for similar code (but different return value). }
  347. var
  348. ErrorCode: DWORD;
  349. begin
  350. Result := False;
  351. Dir := RemoveBackslashUnlessRoot(PathExpand(Dir));
  352. { If we're at the root of a drive or network share, then there's nothing to
  353. do. }
  354. if PathExtractName(Dir) = '' then
  355. Exit;
  356. if DirExists(Dir) then begin
  357. if not(mdAlwaysUninstall in Flags) then
  358. Exit;
  359. end
  360. else begin
  361. MakeDir(UninstLog, PathExtractDir(Dir), Flags - [mdAlwaysUninstall]);
  362. LogFmt('Creating directory: %s', [Dir]);
  363. if not CreateDirectory(PChar(Dir), nil) then begin
  364. ErrorCode := GetLastError;
  365. raise Exception.Create(FmtSetupMessage(msgLastErrorMessage,
  366. [FmtSetupMessage1(msgErrorCreatingDir, PathConvertSuperToNormal(Dir)),
  367. IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
  368. end;
  369. Result := True;
  370. if mdNotifyChange in Flags then begin
  371. ShellChangeNotifyPath(SHCNE_MKDIR, Dir, False);
  372. ShellChangeNotifyPath(SHCNE_UPDATEDIR, PathExtractDir(Dir), True);
  373. end;
  374. end;
  375. if mdDeleteAfterInstall in Flags then
  376. DeleteDirsAfterInstallList.Add(Dir)
  377. else begin
  378. if not(mdNoUninstall in Flags) then begin
  379. var UninstFlags: TUninstallRecExtraData := utDeleteDirOrFiles_IsDir;
  380. if IsCurrentProcess64Bit then { Post-ApplyPathRedirRules we should check IsCurrentProcess64Bit and not the original InstallDefault64Bit }
  381. UninstFlags := UninstFlags or utDeleteDirOrFiles_Is64Bit;
  382. if mdNotifyChange in Flags then
  383. UninstFlags := UninstFlags or utDeleteDirOrFiles_CallChangeNotify;
  384. UninstLog.Add(utDeleteDirOrFiles, [Dir], UninstFlags);
  385. end;
  386. end;
  387. end;
  388. procedure ApplyNTFSCompression(const Filename: String; const FilenameIsDirectory, Compress: Boolean);
  389. const
  390. SSet: array [Boolean] of String = ('Setting', 'Unsetting');
  391. SFileDir: array [Boolean] of String = ('file', 'directory');
  392. begin
  393. LogFmt('%s NTFS compression on %s: %s', [SSet[Compress], SFileDir[FilenameIsDirectory], Filename]);
  394. if not SetNTFSCompression(Filename, Compress) then
  395. LogFmt('Failed to set NTFS compression state (%d).', [GetLastError]);
  396. end;
  397. procedure CreateDirs(const UninstLog: TUninstallLog);
  398. { Creates the application's directories }
  399. procedure ApplyPermissions(const Filename: String;
  400. const PermsEntry: Integer);
  401. var
  402. P: PSetupPermissionEntry;
  403. begin
  404. if PermsEntry <> -1 then begin
  405. LogFmt('Setting permissions on directory: %s', [Filename]);
  406. P := Entries[sePermission][PermsEntry];
  407. if not GrantPermissionOnFile(Filename,
  408. TGrantPermissionEntry(Pointer(P.Permissions)^),
  409. Length(P.Permissions) div SizeOf(TGrantPermissionEntry)) then
  410. LogFmt('Failed to set permissions on directory (%d).', [GetLastError]);
  411. end;
  412. end;
  413. begin
  414. { Create main application directory }
  415. MakeDir(UninstLog, ApplyPathRedirRules(InstallDefault64Bit, WizardDirValue, tpCurrent));
  416. { Create the rest of the directories, if any }
  417. for var CurDirNumber := 0 to Entries[seDir].Count-1 do
  418. with PSetupDirEntry(Entries[seDir][CurDirNumber])^ do begin
  419. if ShouldProcessEntry(WizardComponents, WizardTasks, Components, Tasks, Languages, Check) then begin
  420. DebugNotifyEntry(seDir, CurDirNumber);
  421. NotifyBeforeInstallEntry(BeforeInstall);
  422. const Path = RemoveBackslashUnlessRoot(ApplyPathRedirRules(
  423. InstallDefault64Bit, ExpandConst(DirName), tpCurrent));
  424. var Flags: TMakeDirFlags := [];
  425. if doUninsNeverUninstall in Options then Include(Flags, mdNoUninstall);
  426. if doDeleteAfterInstall in Options then Include(Flags, mdDeleteAfterInstall);
  427. if doUninsAlwaysUninstall in Options then Include(Flags, mdAlwaysUninstall);
  428. MakeDir(UninstLog, Path, Flags);
  429. AddAttributesToFile(Path, Attribs);
  430. ApplyPermissions(Path, PermissionsEntry);
  431. if (doSetNTFSCompression in Options) or (doUnsetNTFSCompression in Options) then
  432. ApplyNTFSCompression(Path, True, doSetNTFSCompression in Options);
  433. NotifyAfterInstallEntry(AfterInstall);
  434. end;
  435. end;
  436. end;
  437. procedure BindUninstallMsgDataToExe(const ExpandedAppId: String; const F: TFile);
  438. var
  439. UniqueValue: TSHA256Digest;
  440. UninstallerMsgTail: TUninstallerMsgTail;
  441. begin
  442. F.SeekToEnd;
  443. { First append the hash of AppId so that unins*.exe files from different
  444. applications won't have the same file hash. This is done to combat broken
  445. anti-spyware programs that catch all unins*.exe files with certain hash
  446. sums just because some piece of spyware was deployed with Inno Setup and
  447. had the unins*.exe file in its directory. }
  448. UniqueValue := GetSHA256OfUnicodeString(ExpandedAppId);
  449. F.WriteBuffer(UniqueValue, SizeOf(UniqueValue));
  450. UninstallerMsgTail.ID := UninstallerMsgTailID;
  451. UninstallerMsgTail.Offset := F.Position;
  452. WriteMsgData(F);
  453. F.WriteBuffer(UninstallerMsgTail, SizeOf(UninstallerMsgTail));
  454. end;
  455. procedure DoHandleFailedDeleteOrMoveFileTry(const CurFile: PSetupFileEntry;
  456. const Func, TempFile, DestFile: String;
  457. const LastError: DWORD; var RetriesLeft: Integer; var LastOperation: String;
  458. var NeedsRestart, ReplaceOnRestart: Boolean;
  459. var NextAction: TFileOperationFailingNextAction);
  460. begin
  461. { Automatically retry. Wait with replace on restart until no
  462. retries left, unless we already know we're going to restart. }
  463. if ((RetriesLeft = 0) or NeedsRestart) and
  464. (foRestartReplace in CurFile^.Options) and IsAdmin then begin
  465. LogFmt('%s: The existing file appears to be in use (%d). ' +
  466. 'Will replace on restart.', [Func, LastError]);
  467. LastOperation := SetupMessages[msgErrorRestartReplace];
  468. NeedsRestart := True;
  469. RestartReplace(TempFile, DestFile);
  470. ReplaceOnRestart := True;
  471. NextAction := naStopAndSucceed;
  472. end else if RetriesLeft > 0 then begin
  473. LogFmt('%s: The existing file appears to be in use (%d). ' +
  474. 'Retrying.', [Func, LastError]);
  475. Dec(RetriesLeft);
  476. Sleep(1000);
  477. ProcessEvents;
  478. NextAction := naRetry;
  479. end;
  480. end;
  481. type
  482. TOverwriteAll = (oaUnknown, oaOverwrite, oaKeep);
  483. procedure ProcessFileEntry(const UninstLog: TUninstallLog; const ExpandedAppId: String;
  484. const RegisterFilesList: TList; const CurFile: PSetupFileEntry;
  485. const Is64Bit: Boolean; AExternalSourceFile, ADestFile: String;
  486. const FileLocationFilenames: TStringList; const AExternalSize: Int64;
  487. var ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll: TOverwriteAll;
  488. var WarnedPerUserFonts: Boolean; const AExternalFileDate: PFileTime;
  489. var UninstallTempExeFilename: String; var UninstallExeCreated: TUninstallExeCreated);
  490. { Not external: AExternalSourceFile and ADestFile should be empty strings,
  491. FileLocationFilenames should be set, AExternalSize is unused,
  492. AExternalFileDate should not be set
  493. External : Opposite except AExternalFileDate still not set
  494. Ext. Archive: Same as external except AExternalFileDate set and
  495. AExternalSourceFile should be set to ArchiveFindHandle as a string
  496. Ext. Downl. : Same as external except
  497. AExternalSourceFile should be set to an URL }
  498. procedure InstallFont(const Filename, FontName: String;
  499. const PerUserFont, AddToFontTableNow: Boolean; var WarnedPerUserFonts: Boolean);
  500. begin
  501. const NormalFilename = PathConvertSuperToNormal(Filename);
  502. if PerUserFont and not WindowsVersionAtLeast(10, 0, 17134) then begin
  503. { Per-user fonts require Windows 10 Version 1803 (10.0.17134) or newer. }
  504. if not WarnedPerUserFonts then begin
  505. Log('Failed to set value in Fonts registry key: per-user fonts are not supported by this version of Windows.');
  506. WarnedPerUserFonts := True;
  507. end;
  508. end else begin
  509. { 64-bit Windows note: The Fonts key is evidently exempt from registry
  510. redirection. When a 32-bit app writes to the Fonts key, it's the main
  511. 64-bit key that is modified. (There is actually a Fonts key under
  512. Wow6432Node but it appears it's never used or updated.)
  513. Also: We don't bother with any FS redirection stuff here. I'm not sure
  514. it's safe to disable FS redirection when calling AddFontResource, or
  515. if it would even work. Users should be installing their fonts to the
  516. Fonts directory instead of the System directory anyway. }
  517. var RootKey: HKEY;
  518. if PerUserFont then
  519. RootKey := HKEY_CURRENT_USER
  520. else
  521. RootKey := HKEY_LOCAL_MACHINE;
  522. var K: HKEY;
  523. if RegOpenKeyExView(rvDefault, RootKey, 'Software\Microsoft\Windows NT\CurrentVersion\Fonts', 0,
  524. KEY_SET_VALUE, K) = ERROR_SUCCESS then begin
  525. if RegSetValueEx(K, PChar(FontName), 0, REG_SZ, PChar(NormalFilename),
  526. (ULength(NormalFilename)+1)*SizeOf(NormalFilename[1])) <> ERROR_SUCCESS then
  527. Log('Failed to set value in Fonts registry key.');
  528. RegCloseKey(K);
  529. end
  530. else
  531. Log('Failed to open Fonts registry key.');
  532. end;
  533. if AddToFontTableNow then begin
  534. repeat
  535. { Note: AddFontResource doesn't set the thread's last error code }
  536. if AddFontResource(PChar(NormalFilename)) <> 0 then begin
  537. SendNotifyMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
  538. Break;
  539. end;
  540. until AbortRetryIgnoreTaskDialogMsgBox(
  541. AddPeriod(FmtSetupMessage1(msgErrorFunctionFailedNoCode, 'AddFontResource')),
  542. [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgAbortRetryIgnoreIgnore], SetupMessages[msgAbortRetryIgnoreCancel]]);
  543. end;
  544. end;
  545. procedure SetFileLocationFilename(const LocationEntry: Integer;
  546. Filename: String);
  547. begin
  548. const LowercaseFilename = PathLowercase(Filename);
  549. const Hash = GetCRC32(LowercaseFilename[1], ULength(LowercaseFilename)*SizeOf(LowercaseFilename[1]));
  550. { If Filename was already associated with another LocationEntry,
  551. disassociate it. If we *don't* do this, then this script won't
  552. produce the expected result:
  553. [Files]
  554. Source: "fileA"; DestName: "file2"
  555. Source: "fileB"; DestName: "file2"
  556. Source: "fileA"; DestName: "file1"
  557. 1. It extracts fileA under the name "file2"
  558. 2. It extracts fileB under the name "file2"
  559. 3. It copies file2 to file1, thinking a copy of fileA was still
  560. stored in file2.
  561. }
  562. for var I := 0 to FileLocationFilenames.Count-1 do
  563. if (Integer(FileLocationFilenames.Objects[I]) = Hash) and
  564. (PathLowercase(FileLocationFilenames[I]) = LowercaseFilename) then begin
  565. FileLocationFilenames[I] := '';
  566. FileLocationFilenames.Objects[I] := nil;
  567. Break;
  568. end;
  569. FileLocationFilenames[LocationEntry] := Filename;
  570. FileLocationFilenames.Objects[LocationEntry] := Pointer(Hash);
  571. end;
  572. procedure ApplyPermissions(const Filename: String; const PermsEntry: Integer);
  573. var
  574. P: PSetupPermissionEntry;
  575. begin
  576. if PermsEntry <> -1 then begin
  577. LogFmt('Setting permissions on file: %s', [Filename]);
  578. const Attr = GetFileAttributes(PChar(Filename));
  579. if Attr = INVALID_FILE_ATTRIBUTES then
  580. LogWithLastError('Cannot set permissions; failed to read file attributes.')
  581. else if Attr and FILE_ATTRIBUTE_DIRECTORY <> 0 then
  582. Log('Cannot set permissions; a directory exists at that path.')
  583. else begin
  584. P := Entries[sePermission][PermsEntry];
  585. if not GrantPermissionOnFile(Filename,
  586. TGrantPermissionEntry(Pointer(P.Permissions)^),
  587. Length(P.Permissions) div SizeOf(TGrantPermissionEntry)) then
  588. LogFmt('Failed to set permissions on file (%d).', [GetLastError]);
  589. end;
  590. end;
  591. end;
  592. function AskOverwrite(const DestFile, Instruction, Caption: string; const ButtonLabels: array of String;
  593. const VerificationText: String; const Typ: TMsgBoxType; const Default, Overwrite: Integer;
  594. var OverwriteAll: TOverwriteAll): Boolean;
  595. var
  596. VerificationFlagChecked: BOOL;
  597. begin
  598. if OverwriteAll = oaKeep then
  599. Result := False { The user already said to keep (=not overwrite) all }
  600. else begin
  601. Result := LoggedTaskDialogMsgBox('', Instruction, DestFile + SNewLine2 + Caption, '',
  602. Typ, MB_YESNO, ButtonLabels, 0, True, Default, VerificationText, @VerificationFlagChecked) = Overwrite;
  603. if VerificationFlagChecked then begin
  604. if Result then
  605. OverwriteAll := oaOverwrite
  606. else
  607. OverwriteAll := oaKeep;
  608. end;
  609. end;
  610. end;
  611. var
  612. ProgressUpdated: Boolean;
  613. LastOperation: String;
  614. CurFileLocation: PSetupFileLocationEntry;
  615. SourceFile, DestFile, TempFile, FontFilename: String;
  616. DestFileExists, DestFileExistedBefore, CheckedDestFileExistedBefore,
  617. TempFileLeftOver, AllowFileToBeDuplicated, ReplaceOnRestart: Boolean;
  618. Failed: String;
  619. CurFileVersionInfoValid: Boolean;
  620. CurFileVersionInfo, ExistingVersionInfo: TFileVersionNumbers;
  621. CurFileDateValid, ExistingFileDateValid: Boolean;
  622. IsProtectedFile, AllowTimeStampComparison: Boolean;
  623. DeleteFlags: TUninstallRecExtraData;
  624. CurFileDate, ExistingFileDate: TFileTime;
  625. RegisterRec: PRegisterFilesListRec;
  626. DestF, SourceF: TFile;
  627. Flags: TMakeDirFlags;
  628. Overwrite, PerUserFont: Boolean;
  629. label Retry, Skip;
  630. begin
  631. Log('-- File entry --');
  632. CheckedDestFileExistedBefore := False;
  633. DestFileExistedBefore := False; { prevent warning }
  634. if CurFile^.LocationEntry <> -1 then
  635. CurFileLocation := PSetupFileLocationEntry(Entries[seFileLocation][CurFile^.LocationEntry])
  636. else
  637. CurFileLocation := nil;
  638. Retry:
  639. DestFile := '';
  640. TempFile := '';
  641. TempFileLeftOver := False;
  642. ProgressUpdated := False;
  643. var PreviousProgress := CurProgress;
  644. LastOperation := '';
  645. Failed := '';
  646. try
  647. try
  648. ReplaceOnRestart := False;
  649. DeleteFlags := 0;
  650. if IsCurrentProcess64Bit then { Post-ApplyPathRedirRules we should check IsCurrentProcess64Bit and not Is64Bit }
  651. DeleteFlags := DeleteFlags or utDeleteFile_Is64Bit;
  652. if foRegisterServer in CurFile^.Options then begin
  653. DeleteFlags := DeleteFlags or utDeleteFile_RegisteredServer;
  654. if IsCurrentProcess64Bit <> Is64Bit then
  655. DeleteFlags := DeleteFlags or utDeleteFile_RegisteredWithOppositeBitness;
  656. end;
  657. if foRegisterTypeLib in CurFile^.Options then
  658. DeleteFlags := DeleteFlags or utDeleteFile_RegisteredTypeLib;
  659. if foUninsRestartDelete in CurFile^.Options then
  660. DeleteFlags := DeleteFlags or utDeleteFile_RestartDelete;
  661. if foUninsRemoveReadOnly in CurFile^.Options then
  662. DeleteFlags := DeleteFlags or utDeleteFile_RemoveReadOnly;
  663. if foGacInstall in CurFile^.Options then
  664. DeleteFlags := DeleteFlags or utDeleteFile_GacInstalled;
  665. FontFilename := '';
  666. { Determine the destination filename }
  667. try
  668. case CurFile^.FileType of
  669. ftUninstExe: DestFile := UninstallExeFilename;
  670. else
  671. if ADestFile = '' then
  672. DestFile := ApplyPathRedirRules(Is64Bit, ExpandConst(CurFile^.DestName), tpCurrent)
  673. else
  674. DestFile := ADestFile;
  675. end;
  676. except
  677. { If an exception occurred, reset DestFile back to an empty string
  678. so the error message doesn't show an unexpanded name }
  679. DestFile := '';
  680. raise;
  681. end;
  682. { Update the status and filename labels }
  683. if foDownload in CurFile^.Options then
  684. SetStatusLabelText(SetupMessages[msgStatusDownloadFiles], False)
  685. else
  686. SetStatusLabelText(SetupMessages[msgStatusExtractFiles], False);
  687. SetFilenameLabelText(PathConvertSuperToNormal(DestFile), True);
  688. LogFmt('Dest filename: %s', [DestFile]);
  689. if Is64Bit <> InstallDefault64Bit then begin
  690. if Is64Bit then
  691. Log('Non-default bitness: 64-bit')
  692. else
  693. Log('Non-default bitness: 32-bit');
  694. end;
  695. { See if it's a protected system file. }
  696. if IsProtectedSystemFile(DestFile) then begin
  697. Log('Dest file is protected by Windows File Protection.');
  698. IsProtectedFile := (CurFile^.FileType = ftUserFile);
  699. end else
  700. IsProtectedFile := False;
  701. DestFileExists := NewFileExists(DestFile);
  702. if not CheckedDestFileExistedBefore then begin
  703. DestFileExistedBefore := DestFileExists;
  704. CheckedDestFileExistedBefore := True;
  705. end;
  706. if DestFileExistedBefore then
  707. DeleteFlags := DeleteFlags or utDeleteFile_ExistedBeforeInstall;
  708. var CurFileDateDidRead := True; { Set to False later if needed }
  709. if Assigned(CurFileLocation) then begin
  710. { Not an "external" file }
  711. if CurFileLocation^.TimeStamp.HasTime then begin
  712. if floTimeStampInUTC in CurFileLocation^.Flags then
  713. CurFileDate := CurFileLocation^.TimeStamp
  714. else
  715. LocalFileTimeToFileTime(CurFileLocation^.TimeStamp, CurFileDate);
  716. CurFileDateValid := True;
  717. end else begin
  718. CurFileDateValid := False;
  719. CurFileDateDidRead := False;
  720. end;
  721. end else begin
  722. { An "external" file }
  723. if Assigned(AExternalFileDate) then begin
  724. CurFileDate := AExternalFileDate^;
  725. CurFileDateValid := CurFileDate.HasTime;
  726. end else if not(foDownload in CurFile^.Options) then
  727. CurFileDateValid := GetFileDateTime(AExternalSourceFile, CurFileDate)
  728. else begin
  729. CurFileDateValid := False;
  730. CurFileDateDidRead := False;
  731. end;
  732. end;
  733. if CurFileDateValid then
  734. LogFmt('Time stamp of our file: %s', [FileTimeToStr(CurFileDate)])
  735. else if CurFileDateDidRead then
  736. Log('Time stamp of our file: (failed to read)');
  737. if DestFileExists then begin
  738. Log('Dest file exists.');
  739. if foOnlyIfDoesntExist in CurFile^.Options then begin
  740. Log('Skipping due to "onlyifdoesntexist" flag.');
  741. goto Skip;
  742. end;
  743. LastOperation := SetupMessages[msgErrorReadingExistingDest];
  744. ExistingFileDateValid := GetFileDateTime(DestFile, ExistingFileDate);
  745. if ExistingFileDateValid then
  746. LogFmt('Time stamp of existing file: %s', [FileTimeToStr(ExistingFileDate)])
  747. else
  748. Log('Time stamp of existing file: (failed to read)');
  749. { Compare version info }
  750. if not(foIgnoreVersion in CurFile^.Options) then begin
  751. AllowTimeStampComparison := False;
  752. { Read version info of file being installed }
  753. if foDownload in CurFile^.Options then
  754. InternalError('Unexpected Download flag');
  755. if foExtractArchive in CurFile^.Options then
  756. InternalError('Unexpected ExtractArchive flag');
  757. if Assigned(CurFileLocation) then begin
  758. CurFileVersionInfoValid := floVersionInfoValid in CurFileLocation^.Flags;
  759. CurFileVersionInfo.MS := CurFileLocation^.FileVersionMS;
  760. CurFileVersionInfo.LS := CurFileLocation^.FileVersionLS;
  761. end
  762. else
  763. CurFileVersionInfoValid := GetVersionNumbers(AExternalSourceFile, CurFileVersionInfo);
  764. if CurFileVersionInfoValid then
  765. LogFmt('Version of our file: %u.%u.%u.%u',
  766. [LongRec(CurFileVersionInfo.MS).Hi, LongRec(CurFileVersionInfo.MS).Lo,
  767. LongRec(CurFileVersionInfo.LS).Hi, LongRec(CurFileVersionInfo.LS).Lo])
  768. else
  769. Log('Version of our file: (none)');
  770. { Does the existing file have version info? }
  771. if GetVersionNumbers(DestFile, ExistingVersionInfo) then begin
  772. { If the file being installed has no version info, or the existing
  773. file is a newer version... }
  774. LogFmt('Version of existing file: %u.%u.%u.%u',
  775. [LongRec(ExistingVersionInfo.MS).Hi, LongRec(ExistingVersionInfo.MS).Lo,
  776. LongRec(ExistingVersionInfo.LS).Hi, LongRec(ExistingVersionInfo.LS).Lo]);
  777. if not CurFileVersionInfoValid or
  778. ((ExistingVersionInfo.MS > CurFileVersionInfo.MS) or
  779. ((ExistingVersionInfo.MS = CurFileVersionInfo.MS) and
  780. (ExistingVersionInfo.LS > CurFileVersionInfo.LS))) then begin
  781. { No version info, or existing file is newer, ask user what to do unless we shouldn't }
  782. if (foPromptIfOlder in CurFile^.Options) and not IsProtectedFile then begin
  783. if PromptIfOlderOverwriteAll <> oaOverwrite then begin
  784. Overwrite := AskOverwrite(PathConvertSuperToNormal(DestFile),
  785. SetupMessages[msgExistingFileNewerSelectAction], SetupMessages[msgExistingFileNewer2],
  786. [SetupMessages[msgExistingFileNewerKeepExisting], SetupMessages[msgExistingFileNewerOverwriteExisting]],
  787. SetupMessages[msgExistingFileNewerOverwriteOrKeepAll],
  788. mbError, IDYES, IDNO, PromptIfOlderOverwriteAll);
  789. if not Overwrite then begin
  790. Log('User opted not to overwrite the existing file. Skipping.');
  791. goto Skip;
  792. end;
  793. end;
  794. end else begin
  795. Log('Existing file is a newer version. Skipping.');
  796. goto Skip;
  797. end;
  798. end
  799. else begin
  800. { If the existing file and the file being installed are the same
  801. version... }
  802. if (ExistingVersionInfo.MS = CurFileVersionInfo.MS) and
  803. (ExistingVersionInfo.LS = CurFileVersionInfo.LS) and
  804. not(foOverwriteSameVersion in CurFile^.Options) then begin
  805. if foReplaceSameVersionIfContentsDiffer in CurFile^.Options then begin
  806. { Get the two files' SHA-256 hashes and compare them }
  807. var ExistingFileHash: TSHA256Digest;
  808. if TryToGetSHA256OfFile(DestFile, ExistingFileHash) then begin
  809. var CurFileHash: TSHA256Digest;
  810. if Assigned(CurFileLocation) then
  811. CurFileHash := CurFileLocation^.SHA256Sum
  812. else begin
  813. LastOperation := SetupMessages[msgErrorReadingSource];
  814. { This GetSHA256OfFile call could raise an exception, but
  815. it's very unlikely since we were already able to
  816. successfully read the file's version info. }
  817. CurFileHash := GetSHA256OfFile(AExternalSourceFile);
  818. LastOperation := SetupMessages[msgErrorReadingExistingDest];
  819. end;
  820. { If the two files' SHA-256 hashes are equal, skip the file }
  821. if SHA256DigestsEqual(ExistingFileHash, CurFileHash) then begin
  822. Log('Existing file''s SHA-256 hash matches our file. Skipping.');
  823. goto Skip;
  824. end;
  825. Log('Existing file''s SHA-256 hash is different from our file. Proceeding.');
  826. end
  827. else
  828. Log('Failed to read existing file''s SHA-256 hash. Proceeding.');
  829. end
  830. else begin
  831. { Skip the file or fall back to time stamp comparison }
  832. if not(foCompareTimeStamp in CurFile^.Options) then begin
  833. Log('Same version. Skipping.');
  834. goto Skip;
  835. end;
  836. AllowTimeStampComparison := True;
  837. end;
  838. end;
  839. end;
  840. end
  841. else begin
  842. Log('Version of existing file: (none)');
  843. { If neither the existing file nor our file have version info,
  844. allow time stamp comparison }
  845. if not CurFileVersionInfoValid then
  846. AllowTimeStampComparison := True;
  847. end;
  848. end
  849. else begin
  850. { When foIgnoreVersion is in Options, always allow time stamp
  851. comparison }
  852. AllowTimeStampComparison := True;
  853. end;
  854. { Fall back to comparing time stamps if needed }
  855. if AllowTimeStampComparison and
  856. (foCompareTimeStamp in CurFile^.Options) then begin
  857. if foDownload in CurFile^.Options then
  858. InternalError('Unexpected Download flag');
  859. if not CurFileDateValid or not ExistingFileDateValid then begin
  860. { If we failed to read one of the time stamps, do the safe thing
  861. and just skip the file }
  862. Log('Couldn''t read time stamp. Skipping.');
  863. goto Skip;
  864. end;
  865. if CompareFileTime(ExistingFileDate, CurFileDate) = 0 then begin
  866. { Same time stamp }
  867. Log('Same time stamp. Skipping.');
  868. goto Skip;
  869. end;
  870. if CompareFileTime(ExistingFileDate, CurFileDate) > 0 then begin
  871. { Existing file has a later time stamp, ask user what to do unless we shouldn't }
  872. if (foPromptIfOlder in CurFile^.Options) and not IsProtectedFile then begin
  873. if PromptIfOlderOverwriteAll <> oaOverwrite then begin
  874. Overwrite := AskOverwrite(PathConvertSuperToNormal(DestFile),
  875. SetupMessages[msgExistingFileNewerSelectAction], SetupMessages[msgExistingFileNewer2],
  876. [SetupMessages[msgExistingFileNewerKeepExisting], SetupMessages[msgExistingFileNewerOverwriteExisting]],
  877. SetupMessages[msgExistingFileNewerOverwriteOrKeepAll],
  878. mbError, IDYES, IDNO, PromptIfOlderOverwriteAll);
  879. if not Overwrite then begin
  880. Log('User opted not to overwrite the existing file. Skipping.');
  881. goto Skip;
  882. end;
  883. end;
  884. end else begin
  885. Log('Existing file has a later time stamp. Skipping.');
  886. goto Skip;
  887. end;
  888. end;
  889. end;
  890. LastOperation := '';
  891. { Don't attempt to replace an existing protected system file.
  892. (Do this *after* the version numbers of the new & existing files
  893. have been logged.) }
  894. if IsProtectedFile then begin
  895. Log('Existing file is protected by Windows File Protection. Skipping.');
  896. goto Skip;
  897. end;
  898. { If file already exists and foConfirmOverwrite is in Options, ask the user what to do }
  899. if foConfirmOverwrite in CurFile^.Options then begin
  900. if ConfirmOverwriteOverwriteAll <> oaOverwrite then begin
  901. Overwrite := AskOverwrite(PathConvertSuperToNormal(DestFile),
  902. SetupMessages[msgFileExistsSelectAction], SetupMessages[msgFileExists2],
  903. [SetupMessages[msgFileExistsOverwriteExisting], SetupMessages[msgFileExistsKeepExisting]],
  904. SetupMessages[msgFileExistsOverwriteOrKeepAll],
  905. mbConfirmation, IDNO, IDYES, ConfirmOverwriteOverwriteAll);
  906. if not Overwrite then begin
  907. Log('User opted not to overwrite the existing file. Skipping.');
  908. goto Skip;
  909. end;
  910. end;
  911. end;
  912. { Check if existing file is read-only }
  913. while True do begin
  914. var ExistingFileAttr := GetFileAttributes(PChar(DestFile));
  915. if (ExistingFileAttr <> INVALID_FILE_ATTRIBUTES) and
  916. (ExistingFileAttr and FILE_ATTRIBUTE_READONLY <> 0) then begin
  917. if not(foOverwriteReadOnly in CurFile^.Options) and
  918. AbortRetryIgnoreTaskDialogMsgBox(
  919. PathConvertSuperToNormal(DestFile) + SNewLine2 + SetupMessages[msgExistingFileReadOnly2],
  920. [SetupMessages[msgExistingFileReadOnlyRetry], SetupMessages[msgExistingFileReadOnlyKeepExisting], SetupMessages[msgAbortRetryIgnoreCancel]]) then begin
  921. Log('User opted not to strip the existing file''s read-only attribute. Skipping.');
  922. goto Skip;
  923. end;
  924. LastOperation := SetupMessages[msgErrorChangingAttr];
  925. if SetFileAttributes(PChar(DestFile), ExistingFileAttr and not FILE_ATTRIBUTE_READONLY) then
  926. Log('Stripped read-only attribute.')
  927. else
  928. Log('Failed to strip read-only attribute.');
  929. if foOverwriteReadOnly in CurFile^.Options then
  930. Break; { don't retry }
  931. end
  932. else
  933. Break;
  934. end;
  935. end
  936. else begin
  937. if (foOnlyIfDestFileExists in CurFile^.Options) and not DestFileExistedBefore then begin
  938. Log('Skipping due to "onlyifdestfileexists" flag.');
  939. goto Skip;
  940. end;
  941. end;
  942. Log('Installing the file.');
  943. { Locate source file }
  944. SourceFile := AExternalSourceFile; { Empty string if not external }
  945. { If the file is compressed in the setup package, has the same file
  946. already been copied somewhere else? If so, just make a duplicate of
  947. that file instead of extracting it over again. }
  948. if (SourceFile = '') and (FileLocationFilenames <> nil) and
  949. (FileLocationFilenames[CurFile^.LocationEntry] <> '') and
  950. NewFileExists(FileLocationFilenames[CurFile^.LocationEntry]) then
  951. SourceFile := FileLocationFilenames[CurFile^.LocationEntry];
  952. AllowFileToBeDuplicated := (SourceFile = '');
  953. { Download or extract or copy the file to a temporary file. Create the destination
  954. file's directory if it didn't already exist. }
  955. LastOperation := SetupMessages[msgErrorCreatingTemp];
  956. TempFile := GenerateUniqueName(PathExtractPath(DestFile), '.tmp');
  957. Flags := [];
  958. if foUninsNeverUninstall in CurFile^.Options then Include(Flags, mdNoUninstall);
  959. if foDeleteAfterInstall in CurFile^.Options then Include(Flags, mdDeleteAfterInstall);
  960. MakeDir(UninstLog, PathExtractDir(TempFile), Flags);
  961. DestF := TFile.Create(TempFile, fdCreateAlways, faReadWrite, fsNone);
  962. try
  963. TempFileLeftOver := True;
  964. try
  965. ProgressUpdated := True;
  966. LastOperation := SetupMessages[msgErrorReadingSource];
  967. if SourceFile = '' then begin
  968. { Decompress a file }
  969. FileExtractor.SeekTo(CurFileLocation^, InternalProgressProc);
  970. LastOperation := SetupMessages[msgErrorCopying];
  971. FileExtractor.DecompressFile(CurFileLocation^, DestF, InternalProgressProc,
  972. not (foDontVerifyChecksum in CurFile^.Options));
  973. end
  974. else if foExtractArchive in CurFile^.Options then begin
  975. { Extract a file from archive. Note: ISSigVerify for archive has
  976. already been handled by RecurseExternalArchiveCopyFiles. }
  977. LastOperation := SetupMessages[msgErrorExtracting];
  978. var MaxProgress := CurProgress;
  979. Inc(MaxProgress, AExternalSize);
  980. ArchiveFindExtract(TArchiveFindHandle(StrToUInt64(SourceFile)), DestF, ExternalProgressProc64, MaxProgress);
  981. end
  982. else if foDownload in CurFile^.Options then begin
  983. { Download a file with or without ISSigVerify. Note: estimate of
  984. extra .issig size has already been added to CurFile's ExternalSize. }
  985. LastOperation := SetupMessages[msgErrorDownloading];
  986. const DownloadUserName = ExpandConst(CurFile^.DownloadUserName);
  987. const DownloadPassword = ExpandConst(CurFile^.DownloadPassword);
  988. var MaxProgress := CurProgress;
  989. Inc(MaxProgress, AExternalSize);
  990. if CurFile^.Verification.Typ = fvISSig then begin
  991. const ISSigTempFile = TempFile + ISSigExt;
  992. const ISSigDestF = TFile.Create(ISSigTempFile, fdCreateAlways, faReadWrite, fsNone);
  993. try
  994. { Download the .issig file }
  995. const ISSigUrl = GetISSigUrl(SourceFile, ExpandConst(CurFile^.DownloadISSigSource));
  996. DownloadFile(ISSigUrl, DownloadUserName, DownloadPassword,
  997. ISSigDestF, NoVerification, '', JustProcessEventsProc64, 0, ProcessEvents);
  998. FreeAndNil(ISSigDestF);
  999. { Download and verify the actual file }
  1000. DownloadFile(SourceFile, DownloadUserName, DownloadPassword,
  1001. DestF, CurFile^.Verification, TempFile, ExternalProgressProc64, MaxProgress, ProcessEvents);
  1002. finally
  1003. ISSigDestF.Free;
  1004. { Delete the .issig file }
  1005. Windows.DeleteFile(PChar(ISSigTempFile));
  1006. end;
  1007. end else
  1008. DownloadFile(SourceFile, DownloadUserName, DownloadPassword,
  1009. DestF, CurFile^.Verification, '', ExternalProgressProc64, MaxProgress, ProcessEvents);
  1010. end
  1011. else begin
  1012. { Copy a duplicated non-external file, or an external file }
  1013. SourceF := TFile.Create(SourceFile, fdOpenExisting, faRead, fsRead);
  1014. try
  1015. LastOperation := SetupMessages[msgErrorCopying];
  1016. if Assigned(CurFileLocation) then
  1017. CopySourceFileToDestFile(SourceF, DestF, NoVerification,
  1018. '', CurFileLocation^.OriginalSize)
  1019. else
  1020. CopySourceFileToDestFile(SourceF, DestF, CurFile^.Verification,
  1021. SourceFile, AExternalSize);
  1022. finally
  1023. SourceF.Free;
  1024. end;
  1025. end;
  1026. except
  1027. { If an exception occurred, put progress meter back to where it was }
  1028. ProgressUpdated := False;
  1029. SetProgress(PreviousProgress);
  1030. raise;
  1031. end;
  1032. { Set time/date stamp }
  1033. if CurFileDateValid then
  1034. SetFileTime(DestF.Handle, nil, nil, @CurFileDate);
  1035. { If it's the uninstall program, bind the messages }
  1036. if CurFile^.FileType = ftUninstExe then begin
  1037. AllowFileToBeDuplicated := False;
  1038. MarkExeHeader(DestF, SetupExeModeUninstaller);
  1039. if not(shSignedUninstaller in SetupHeader.Options) and
  1040. not DetachedUninstMsgFile then
  1041. BindUninstallMsgDataToExe(ExpandedAppId, DestF);
  1042. end;
  1043. finally
  1044. DestF.Free;
  1045. end;
  1046. { If it's a font, unregister the existing one to ensure that Windows
  1047. 'notices' the file is being replaced, and to increase the chances
  1048. of the file being unlocked/closed before we replace it. }
  1049. if CurFile^.InstallFontName <> '' then begin
  1050. LastOperation := '';
  1051. FontFilename := ShortenFontFilename(DestFile);
  1052. if DestFileExistedBefore then
  1053. RemoveFontResource(PChar(FontFilename));
  1054. end;
  1055. { Delete existing version of file, if any. If it can't be deleted
  1056. because it's in use and the "restartreplace" flag was specified
  1057. on the entry, register it to be replaced when the system is
  1058. restarted. Do retry deletion before doing this. }
  1059. if DestFileExists and (CurFile^.FileType <> ftUninstExe) then begin
  1060. LastOperation := SetupMessages[msgErrorReplacingExistingFile];
  1061. PerformFileOperationWithRetries(4, False,
  1062. function(out LastError: Cardinal): Boolean
  1063. begin
  1064. Result := Windows.DeleteFile(PChar(DestFile));
  1065. if not Result then begin
  1066. LastError := GetLastError;
  1067. if LastError = ERROR_FILE_NOT_FOUND then begin
  1068. Result := True; { If the file inexplicably vanished, it's not a problem }
  1069. LastError := ERROR_SUCCESS;
  1070. end;
  1071. end;
  1072. end,
  1073. procedure(const LastError: Cardinal; var RetriesLeft: Integer; var NextAction: TFileOperationFailingNextAction)
  1074. begin
  1075. DoHandleFailedDeleteOrMoveFileTry(CurFile, 'DeleteFile', TempFile, DestFile,
  1076. LastError, RetriesLeft, LastOperation, NeedsRestart, ReplaceOnRestart, NextAction);
  1077. end,
  1078. procedure(const LastError: Cardinal; var TryOnceMore: Boolean)
  1079. begin
  1080. Win32ErrorMsg('DeleteFile'); { Throws an exception }
  1081. end);
  1082. end;
  1083. { Rename the temporary file to the new name now, unless the file is
  1084. to be replaced when the system is restarted, or if the file is the
  1085. uninstall program and an existing uninstall program already exists.
  1086. If it can't be renamed and the "restartreplace" flag was specified
  1087. on the entry, register it to be replaced when the system is
  1088. restarted. Do retry renaming before doing this. }
  1089. if not (ReplaceOnRestart or
  1090. ((CurFile^.FileType = ftUninstExe) and DestFileExistedBefore)) then begin
  1091. LastOperation := SetupMessages[msgErrorRenamingTemp];
  1092. { Since the DeleteFile above succeeded you would expect the rename to
  1093. also always succeed, but if it doesn't retry anyway. }
  1094. PerformFileOperationWithRetries(4, True,
  1095. function(out LastError: Cardinal): Boolean
  1096. begin
  1097. Result := MoveFile(PChar(TempFile), PChar(DestFile));
  1098. if not Result then
  1099. LastError := GetLastError;
  1100. end,
  1101. procedure(const LastError: Cardinal; var RetriesLeft: Integer; var NextAction: TFileOperationFailingNextAction)
  1102. begin
  1103. DoHandleFailedDeleteOrMoveFileTry(CurFile, 'MoveFile', TempFile, DestFile,
  1104. LastError, RetriesLeft, LastOperation, NeedsRestart, ReplaceOnRestart, NextAction);
  1105. end,
  1106. procedure(const LastError: Cardinal; var TryOnceMore: Boolean)
  1107. begin
  1108. Win32ErrorMsg('MoveFile'); { Throws an exception }
  1109. end);
  1110. { If ReplaceOnRestart is still False the rename succeeded so handle this.
  1111. Then set any file attributes. }
  1112. if not ReplaceOnRestart then begin
  1113. TempFileLeftOver := False;
  1114. TempFile := '';
  1115. LastOperation := '';
  1116. Log('Successfully installed the file.');
  1117. if AllowFileToBeDuplicated then
  1118. SetFileLocationFilename(CurFile^.LocationEntry, DestFile);
  1119. if foDeleteAfterInstall in CurFile^.Options then
  1120. DeleteFilesAfterInstallList.Add(DestFile);
  1121. { Set file attributes *after* renaming the file since Novell
  1122. reportedly can't rename read-only files. }
  1123. AddAttributesToFile(DestFile, CurFile^.Attribs);
  1124. end;
  1125. end;
  1126. { Leave the temporary file in place for now if the file is to be
  1127. replaced when the system is restarted, or if the file is the uninstall
  1128. program and an existing uninstall program already exists. }
  1129. if ReplaceOnRestart or
  1130. ((CurFile^.FileType = ftUninstExe) and DestFileExistedBefore) then begin
  1131. if CurFile^.FileType = ftUninstExe then
  1132. UninstallTempExeFilename := TempFile;
  1133. TempFileLeftOver := False;
  1134. LastOperation := '';
  1135. Log('Leaving temporary file in place for now.');
  1136. if AllowFileToBeDuplicated then
  1137. SetFileLocationFilename(CurFile^.LocationEntry, TempFile);
  1138. AddAttributesToFile(TempFile, CurFile^.Attribs);
  1139. end;
  1140. { If it's a font, register it }
  1141. if CurFile^.InstallFontName <> '' then begin
  1142. LastOperation := '';
  1143. LogFmt('Registering file as a font ("%s")', [CurFile^.InstallFontName]);
  1144. PerUserFont := not IsAdminInstallMode;
  1145. InstallFont(FontFilename, CurFile^.InstallFontName, PerUserFont, not ReplaceOnRestart, WarnedPerUserFonts);
  1146. DeleteFlags := DeleteFlags or utDeleteFile_IsFont;
  1147. if PerUserFont then
  1148. DeleteFlags := DeleteFlags or utDeleteFile_PerUserFont;
  1149. end;
  1150. { There were no errors so add the uninstall log entry, unless the file
  1151. is the uninstall program, or if it has the foSharedFile flag; shared
  1152. files are handled below. }
  1153. LastOperation := '';
  1154. if CurFile^.FileType <> ftUninstExe then begin
  1155. if not(foUninsNeverUninstall in CurFile^.Options) and
  1156. not(foSharedFile in CurFile^.Options) then begin
  1157. UninstLog.Add(utDeleteFile, [DestFile, TempFile,
  1158. CurFile^.InstallFontName, FontFilename,
  1159. CurFile^.StrongAssemblyName], DeleteFlags);
  1160. end;
  1161. end
  1162. else begin
  1163. if UninstallTempExeFilename = '' then
  1164. UninstallExeCreated := ueNew
  1165. else
  1166. UninstallExeCreated := ueReplaced;
  1167. end;
  1168. Skip:
  1169. { If foRegisterServer or foRegisterTypeLib is in Options, add the
  1170. file to RegisterFilesList for registering later.
  1171. Don't attempt to register if the file doesn't exist (which can
  1172. happen if the foOnlyIfDestFileExists flag is used). }
  1173. if ((foRegisterServer in CurFile^.Options) or
  1174. (foRegisterTypeLib in CurFile^.Options)) and
  1175. NewFileExists(DestFile) then begin
  1176. LastOperation := '';
  1177. if foRegisterTypeLib in CurFile^.Options then
  1178. Log('Will register the file (a type library) later.')
  1179. else
  1180. Log('Will register the file (a DLL/OCX) later.');
  1181. New(RegisterRec);
  1182. RegisterRec^.Filename := DestFile;
  1183. RegisterRec^.Is64Bit := Is64Bit;
  1184. RegisterRec^.TypeLib := foRegisterTypeLib in CurFile^.Options;
  1185. RegisterRec^.NoErrorMessages := foNoRegError in CurFile^.Options;
  1186. RegisterFilesList.Add(RegisterRec);
  1187. end;
  1188. { If foSharedFile is in Options, increment the reference count in the
  1189. registry for the file, then add the uninstall log entry (which,
  1190. unlike non-shared files, must be done on skipped files as well;
  1191. that's why there are two places where utDeleteFile entries are
  1192. added). }
  1193. if foSharedFile in CurFile^.Options then begin
  1194. LastOperation := '';
  1195. if Is64Bit then begin
  1196. Log('Incrementing shared file count (64-bit).');
  1197. IncrementSharedCount(rv64Bit, DestFile, DestFileExistedBefore);
  1198. end
  1199. else begin
  1200. Log('Incrementing shared file count (32-bit).');
  1201. IncrementSharedCount(rv32Bit, DestFile, DestFileExistedBefore);
  1202. end;
  1203. if not(foUninsNeverUninstall in CurFile^.Options) then begin
  1204. DeleteFlags := DeleteFlags or utDeleteFile_SharedFile;
  1205. if Is64Bit then
  1206. DeleteFlags := DeleteFlags or utDeleteFile_SharedFileIn64BitKey;
  1207. if foUninsNoSharedFilePrompt in CurFile^.Options then
  1208. DeleteFlags := DeleteFlags or utDeleteFile_NoSharedFilePrompt;
  1209. UninstLog.Add(utDeleteFile, [DestFile, TempFile,
  1210. CurFile^.InstallFontName, FontFilename,
  1211. CurFile^.StrongAssemblyName], DeleteFlags);
  1212. end
  1213. else begin
  1214. { See comment in Setup.UninstallLog }
  1215. const RedirDestFile = ApplyRedirForRegistrationOperation(Is64Bit, DestFile);
  1216. if Is64Bit then
  1217. UninstLog.Add(utDecrementSharedCount, [RedirDestFile],
  1218. utDecrementSharedCount_64BitKey)
  1219. else
  1220. UninstLog.Add(utDecrementSharedCount, [RedirDestFile], 0);
  1221. end;
  1222. end;
  1223. { Apply permissions (even if the file wasn't replaced) }
  1224. LastOperation := '';
  1225. if TempFile <> '' then
  1226. ApplyPermissions(TempFile, CurFile^.PermissionsEntry)
  1227. else
  1228. ApplyPermissions(DestFile, CurFile^.PermissionsEntry);
  1229. { Set NTFS compression (even if the file wasn't replaced) }
  1230. if (foSetNTFSCompression in CurFile^.Options) or (foUnsetNTFSCompression in CurFile^.Options) then begin
  1231. LastOperation := '';
  1232. if TempFile <> '' then
  1233. ApplyNTFSCompression(TempFile, False, foSetNTFSCompression in CurFile^.Options)
  1234. else
  1235. ApplyNTFSCompression(DestFile, False, foSetNTFSCompression in CurFile^.Options);
  1236. end;
  1237. { Install into GAC (even if the file wasn't replaced) }
  1238. if foGacInstall in CurFile^.Options then begin
  1239. Log('Installing into GAC');
  1240. with TAssemblyCacheInfo.Create(rvDefault) do try
  1241. { PathConvertSuperToNormal because it is not known where InstallAssembly supports
  1242. super paths. It might do now (not tested), but this might not have always been
  1243. the case. }
  1244. if TempFile <> '' then
  1245. InstallAssembly(PathConvertSuperToNormal(TempFile))
  1246. else
  1247. InstallAssembly(PathConvertSuperToNormal(DestFile));
  1248. finally
  1249. Free;
  1250. end;
  1251. end;
  1252. except
  1253. if ExceptObject is EAbort then
  1254. raise;
  1255. Failed := GetExceptMessage;
  1256. end;
  1257. finally
  1258. { If an exception occurred before TempFile was cleaned up, delete it now }
  1259. if TempFileLeftOver then
  1260. Windows.DeleteFile(PChar(TempFile));
  1261. end;
  1262. { Was there an exception? Display error message and offer to retry }
  1263. if Failed <> '' then begin
  1264. if (CurFile^.FileType = ftUninstExe) and (UninstallTempExeFilename <> '') then begin
  1265. DeleteFile(UninstallTempExeFilename);
  1266. UninstallTempExeFilename := '';
  1267. UninstallExeCreated := ueNone;
  1268. end;
  1269. if LastOperation <> '' then
  1270. LastOperation := LastOperation + SNewLine;
  1271. if not AbortRetryIgnoreTaskDialogMsgBox(
  1272. PathConvertSuperToNormal(DestFile) + SNewLine2 + LastOperation + Failed,
  1273. [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgFileAbortRetryIgnoreSkipNotRecommended], SetupMessages[msgAbortRetryIgnoreCancel]]) then begin
  1274. if ProgressUpdated then
  1275. SetProgress(PreviousProgress);
  1276. goto Retry;
  1277. end;
  1278. end;
  1279. { Increment progress meter, if not already done so }
  1280. if not ProgressUpdated then begin
  1281. if Assigned(CurFileLocation) then { not an "external" file }
  1282. IncProgress(CurFileLocation^.OriginalSize)
  1283. else
  1284. IncProgress(AExternalSize);
  1285. end;
  1286. { Process any events between copying files }
  1287. ProcessEvents;
  1288. { Clear previous filename label in case an exception or debugger break
  1289. occurs between now and when the label for the next entry is set }
  1290. SetFilenameLabelText('', False);
  1291. end;
  1292. procedure CopyFiles(const UninstLog: TUninstallLog; const ExpandedAppId: String;
  1293. const RegisterFilesList: TList; Uninstallable: Boolean;
  1294. var UninstallTempExeFilename: String; var UninstallExeCreated: TUninstallExeCreated);
  1295. { Copies all the application's files }
  1296. function RecurseExternalCopyFiles(const Is64Bit: Boolean;
  1297. const SearchBaseDir, SearchSubDir, SearchWildcard: String; const SourceIsWildcard: Boolean;
  1298. const Excludes: TStrings; const CurFile: PSetupFileEntry; var ExpectedBytesLeft: Int64;
  1299. var ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll: TOverwriteAll;
  1300. var WarnedPerUserFonts: Boolean): Boolean;
  1301. begin
  1302. { Also see RecurseExternalFiles and RecurseExternalGetSizeOfFiles in Setup.MainFunc
  1303. Also see RecurseExternalArchiveCopyFiles directly below }
  1304. Result := False;
  1305. var FindData: TWin32FindData;
  1306. var H := FindFirstFile(PChar(SearchBaseDir + SearchSubDir + SearchWildcard), FindData);
  1307. if H <> INVALID_HANDLE_VALUE then begin
  1308. try
  1309. repeat
  1310. if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
  1311. var FileName: String;
  1312. if SourceIsWildcard then begin
  1313. if FindData.dwFileAttributes and FILE_ATTRIBUTE_HIDDEN <> 0 then
  1314. Continue;
  1315. FileName := FindData.cFileName;
  1316. end
  1317. else
  1318. FileName := SearchWildcard; { use the case specified in the script }
  1319. if IsExcluded(SearchSubDir + FileName, Excludes) then
  1320. Continue;
  1321. Result := True;
  1322. var SourceFile := SearchBaseDir + SearchSubDir + FileName;
  1323. { Note: CurFile^.DestName only includes a a filename if foCustomDestName is set,
  1324. see TSetupCompiler.EnumFilesProc.ProcessFileList }
  1325. var DestFile := ExpandConst(CurFile^.DestName);
  1326. if not(foCustomDestName in CurFile^.Options) then
  1327. DestFile := DestFile + SearchSubDir + FileName
  1328. else if SearchSubDir <> '' then
  1329. DestFile := PathExtractPath(DestFile) + SearchSubDir + PathExtractName(DestFile);
  1330. DestFile := ApplyPathRedirRules(Is64Bit, DestFile, tpCurrent);
  1331. var Size := FindDataFileSizeToInt64(FindData);
  1332. if Size > ExpectedBytesLeft then begin
  1333. { Don't allow the progress bar to overflow if the size of the
  1334. files is greater than when we last checked }
  1335. Size := ExpectedBytesLeft;
  1336. end;
  1337. ProcessFileEntry(UninstLog, ExpandedAppId, RegisterFilesList,
  1338. CurFile, Is64Bit, SourceFile, DestFile, nil,
  1339. Size, ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll,
  1340. WarnedPerUserFonts, nil, UninstallTempExeFilename, UninstallExeCreated);
  1341. Dec(ExpectedBytesLeft, Size);
  1342. end;
  1343. until not FindNextFile(H, FindData);
  1344. finally
  1345. Windows.FindClose(H);
  1346. end;
  1347. end;
  1348. if foRecurseSubDirsExternal in CurFile^.Options then begin
  1349. H := FindFirstFile(PChar(SearchBaseDir + SearchSubDir + '*'), FindData);
  1350. if H <> INVALID_HANDLE_VALUE then begin
  1351. try
  1352. repeat
  1353. if IsRecurseableDirectory(FindData) then
  1354. Result := RecurseExternalCopyFiles(Is64Bit, SearchBaseDir,
  1355. SearchSubDir + FindData.cFileName + '\', SearchWildcard,
  1356. SourceIsWildcard, Excludes, CurFile, ExpectedBytesLeft,
  1357. ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll,
  1358. WarnedPerUserFonts) or Result;
  1359. until not FindNextFile(H, FindData);
  1360. finally
  1361. Windows.FindClose(H);
  1362. end;
  1363. end;
  1364. end;
  1365. if SearchSubDir <> '' then begin
  1366. { If Result is False this subdir won't be created, so create it now if
  1367. CreateAllSubDirs was set }
  1368. if not Result and (foCreateAllSubDirs in CurFile.Options) then begin
  1369. var DestName := ExpandConst(CurFile^.DestName); { See above }
  1370. if not(foCustomDestName in CurFile^.Options) then
  1371. DestName := DestName + SearchSubDir
  1372. else
  1373. DestName := PathExtractPath(DestName) + SearchSubDir;
  1374. DestName := ApplyPathRedirRules(Is64Bit, DestName, tpCurrent);
  1375. var Flags: TMakeDirFlags := [];
  1376. if foUninsNeverUninstall in CurFile^.Options then Include(Flags, mdNoUninstall);
  1377. if foDeleteAfterInstall in CurFile^.Options then Include(Flags, mdDeleteAfterInstall);
  1378. MakeDir(UninstLog, DestName, Flags);
  1379. Result := True;
  1380. end;
  1381. end;
  1382. { When recursively searching but not picking up every file, we could
  1383. be frozen for a long time when installing from a network. Calling
  1384. ProcessEvents after every directory helps. }
  1385. ProcessEvents;
  1386. end;
  1387. function RecurseExternalArchiveCopyFiles(const Is64Bit: Boolean;
  1388. const ArchiveFilename: String; const Excludes: TStrings;
  1389. const CurFile: PSetupFileEntry; var ExpectedBytesLeft: Int64;
  1390. var ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll: TOverwriteAll;
  1391. var WarnedPerUserFonts: Boolean): Boolean;
  1392. begin
  1393. { See above }
  1394. { If the archive doesn't exist then the caller should handle this with
  1395. a msgSourceDoesntExist message. All other errors we handle ourselves
  1396. with a msgErrorExtracting message, without informing the caller, unless
  1397. you count EAbort. }
  1398. Result := NewFileExists(ArchiveFilename);
  1399. if not Result then
  1400. Exit;
  1401. if foCustomDestName in CurFile^.Options then
  1402. InternalError('Unexpected custom DestName');
  1403. const DestDir = ApplyPathRedirRules(Is64Bit, ExpandConst(CurFile^.DestName), tpCurrent);
  1404. Log('-- Archive entry --');
  1405. var VerifySourceF: TFile := nil;
  1406. try
  1407. var FindData: TWin32FindData;
  1408. var H: TArchiveFindHandle := INVALID_HANDLE_VALUE;
  1409. var Failed: String;
  1410. repeat
  1411. try
  1412. if CurFile^.Verification.Typ <> fvNone then begin
  1413. if VerifySourceF = nil then
  1414. VerifySourceF := TFile.Create(ArchiveFilename, fdOpenExisting, faRead, fsRead);
  1415. var ExpectedFileHash: TSHA256Digest;
  1416. if CurFile^.Verification.Typ = fvHash then
  1417. ExpectedFileHash := CurFile^.Verification.Hash
  1418. else begin
  1419. DoISSigVerify(VerifySourceF, nil, ArchiveFilename, True, CurFile^.Verification.ISSigAllowedKeys,
  1420. ExpectedFileHash);
  1421. end;
  1422. { Can't get the SHA-256 while extracting so need to get and check it now }
  1423. const ActualFileHash = GetSHA256OfFile(VerifySourceF);
  1424. if not SHA256DigestsEqual(ActualFileHash, ExpectedFileHash) then
  1425. VerificationError(veFileHashIncorrect);
  1426. Log(VerificationSuccessfulLogMessage);
  1427. { Keep VerifySourceF open until extraction has completed to prevent TOCTOU problem }
  1428. end;
  1429. H := ArchiveFindFirstFile(ArchiveFilename, DestDir,
  1430. ExpandConst(CurFile^.ExtractArchivePassword), foRecurseSubDirsExternal in CurFile^.Options,
  1431. True, FindData);
  1432. Failed := '';
  1433. except
  1434. if ExceptObject is EAbort then
  1435. raise;
  1436. Failed := GetExceptMessage;
  1437. end;
  1438. until (Failed = '') or
  1439. AbortRetryIgnoreTaskDialogMsgBox(
  1440. ArchiveFilename + SNewLine2 + SetupMessages[msgErrorExtracting] + SNewLine + Failed,
  1441. [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgFileAbortRetryIgnoreSkipNotRecommended], SetupMessages[msgAbortRetryIgnoreCancel]]);
  1442. if H <> INVALID_HANDLE_VALUE then begin
  1443. try
  1444. repeat
  1445. if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
  1446. if IsExcluded(FindData.cFileName, Excludes) then
  1447. Continue;
  1448. var SourceFile := UIntToStr(H);
  1449. const DestFile = DestDir + FindData.cFileName;
  1450. var Size := FindDataFileSizeToInt64(FindData);
  1451. if Size > ExpectedBytesLeft then begin
  1452. { Don't allow the progress bar to overflow if the size of the
  1453. files is greater than when we last checked }
  1454. Size := ExpectedBytesLeft;
  1455. end;
  1456. ProcessFileEntry(UninstLog, ExpandedAppId, RegisterFilesList,
  1457. CurFile, Is64Bit, SourceFile, DestFile,
  1458. nil, Size, ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll,
  1459. WarnedPerUserFonts, @FindData.ftLastWriteTime,
  1460. UninstallTempExeFilename, UninstallExeCreated);
  1461. Dec(ExpectedBytesLeft, Size);
  1462. end else if foCreateAllSubDirs in CurFile.Options then begin
  1463. var Flags: TMakeDirFlags := [];
  1464. if foUninsNeverUninstall in CurFile^.Options then Include(Flags, mdNoUninstall);
  1465. if foDeleteAfterInstall in CurFile^.Options then Include(Flags, mdDeleteAfterInstall);
  1466. MakeDir(UninstLog, DestDir + FindData.cFileName, Flags);
  1467. Result := True;
  1468. end;
  1469. until not ArchiveFindNextFile(H, FindData);
  1470. finally
  1471. ArchiveFindClose(H);
  1472. end;
  1473. Log('Successfully extracted the archive.');
  1474. end else
  1475. Log('Found no files to extract.');
  1476. finally
  1477. VerifySourceF.Free;
  1478. end;
  1479. end;
  1480. var
  1481. CurFile: PSetupFileEntry;
  1482. SourceWildcard: String;
  1483. FoundFiles: Boolean;
  1484. ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll: TOverwriteAll;
  1485. WarnedPerUserFonts: Boolean;
  1486. begin
  1487. ConfirmOverwriteOverwriteAll := oaUnknown;
  1488. PromptIfOlderOverwriteAll := oaUnknown;
  1489. WarnedPerUserFonts := False;
  1490. var FileLocationFilenames: TStringList := nil;
  1491. var Excludes: TStringList := nil;
  1492. try
  1493. FileLocationFilenames := TStringList.Create;
  1494. for var I := 0 to Entries[seFileLocation].Count-1 do
  1495. FileLocationFilenames.Add('');
  1496. Excludes := TStringList.Create;
  1497. Excludes.StrictDelimiter := True;
  1498. Excludes.Delimiter := ',';
  1499. for var CurFileNumber := 0 to Entries[seFile].Count-1 do begin
  1500. CurFile := PSetupFileEntry(Entries[seFile][CurFileNumber]);
  1501. if ((CurFile^.FileType <> ftUninstExe) or Uninstallable) and
  1502. ShouldProcessFileEntry(WizardComponents, WizardTasks, CurFile, False) then begin
  1503. DebugNotifyEntry(seFile, CurFileNumber);
  1504. NotifyBeforeInstallFileEntry(CurFile);
  1505. var Is64Bit := InstallDefault64Bit;
  1506. if fo32Bit in CurFile^.Options then
  1507. Is64Bit := False;
  1508. if fo64Bit in CurFile^.Options then begin
  1509. if not IsWin64 then
  1510. InternalError('Cannot install files to 64-bit locations on this version of Windows');
  1511. Is64Bit := True;
  1512. end;
  1513. if CurFile^.LocationEntry <> -1 then begin
  1514. ProcessFileEntry(UninstLog, ExpandedAppId, RegisterFilesList,
  1515. CurFile, Is64Bit, '', '', FileLocationFilenames, 0,
  1516. ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll, WarnedPerUserFonts, nil,
  1517. UninstallTempExeFilename, UninstallExeCreated);
  1518. end
  1519. else begin
  1520. { File is an 'external' file }
  1521. if CurFile^.FileType = ftUninstExe then begin
  1522. { This is the file entry for the uninstaller program }
  1523. SourceWildcard := NewParamStr(0);
  1524. Is64Bit := IsCurrentProcess64Bit;
  1525. end else begin
  1526. SourceWildcard := ExpandConst(CurFile^.SourceFilename);
  1527. if not(foDownload in CurFile^.Options) then
  1528. SourceWildcard := ApplyPathRedirRules(Is64Bit, SourceWildcard, tpCurrent);
  1529. end;
  1530. Excludes.DelimitedText := CurFile^.Excludes;
  1531. var ProgressBefore := CurProgress;
  1532. repeat
  1533. SetProgress(ProgressBefore);
  1534. var ExpectedBytesLeft := CurFile^.ExternalSize;
  1535. if foDownload in CurFile^.Options then begin
  1536. { Archive download should have been done already by Setup.WizardForm's DownloadArchivesToExtract }
  1537. if foExtractArchive in CurFile^.Options then
  1538. InternalError('Unexpected Download flag');
  1539. if foSkipIfSourceDoesntExist in CurFile^.Options then
  1540. InternalError('Unexpected SkipIfSourceDoesntExist flag');
  1541. if not(foCustomDestName in CurFile^.Options) then
  1542. InternalError('Expected CustomDestName flag');
  1543. { CurFile^.DestName now includes a filename, see TSetupCompiler.EnumFilesProc.ProcessFileList }
  1544. ProcessFileEntry(UninstLog, ExpandedAppId, RegisterFilesList,
  1545. CurFile, Is64Bit, SourceWildcard, ApplyPathRedirRules(Is64Bit, ExpandConst(CurFile^.DestName), tpCurrent),
  1546. nil, ExpectedBytesLeft, ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll,
  1547. WarnedPerUserFonts, nil,
  1548. UninstallTempExeFilename, UninstallExeCreated);
  1549. FoundFiles := True;
  1550. end else if foExtractArchive in CurFile^.Options then
  1551. FoundFiles := RecurseExternalArchiveCopyFiles(Is64Bit,
  1552. SourceWildcard, Excludes, CurFile,
  1553. ExpectedBytesLeft, ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll,
  1554. WarnedPerUserFonts)
  1555. else
  1556. FoundFiles := RecurseExternalCopyFiles(Is64Bit,
  1557. PathExtractPath(SourceWildcard), '', PathExtractName(SourceWildcard),
  1558. IsWildcard(SourceWildcard), Excludes, CurFile,
  1559. ExpectedBytesLeft, ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll,
  1560. WarnedPerUserFonts);
  1561. until FoundFiles or
  1562. (foSkipIfSourceDoesntExist in CurFile^.Options) or
  1563. AbortRetryIgnoreTaskDialogMsgBox(
  1564. SetupMessages[msgErrorReadingSource] + SNewLine + AddPeriod(FmtSetupMessage(msgSourceDoesntExist, [SourceWildcard])),
  1565. [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgFileAbortRetryIgnoreSkipNotRecommended], SetupMessages[msgAbortRetryIgnoreCancel]]);
  1566. { In case we didn't end up copying all the expected bytes, bump
  1567. the progress bar up to the expected amount }
  1568. Inc(ProgressBefore, CurFile^.ExternalSize);
  1569. SetProgress(ProgressBefore);
  1570. end;
  1571. NotifyAfterInstallFileEntry(CurFile);
  1572. end;
  1573. end;
  1574. finally
  1575. Excludes.Free;
  1576. FileLocationFilenames.Free;
  1577. end;
  1578. end;
  1579. procedure CreateIcons(const UninstLog: TUninstallLog);
  1580. function IsPathURL(const S: String): Boolean;
  1581. { Returns True if S begins with a scheme name and colon. Should be
  1582. compliant with RFC 2396 section 3.1. }
  1583. const
  1584. SchemeAlphaChars = ['A'..'Z', 'a'..'z'];
  1585. SchemeAllChars = SchemeAlphaChars + ['0'..'9', '+', '-', '.'];
  1586. var
  1587. P, I: Integer;
  1588. begin
  1589. Result := False;
  1590. P := PathPos(':', S);
  1591. if (P > 2) and CharInSet(S[1], SchemeAlphaChars) then begin
  1592. for I := 2 to P-1 do
  1593. if not CharInSet(S[I], SchemeAllChars) then
  1594. Exit;
  1595. Result := True;
  1596. end;
  1597. end;
  1598. procedure CreateURLFile(const Filename, URL, IconFilename: String;
  1599. const IconIndex: Integer);
  1600. var
  1601. S: String;
  1602. F: TTextFileWriter;
  1603. begin
  1604. S := '[InternetShortcut]' + SNewLine + 'URL=' + URL + SNewLine;
  1605. if IconFilename <> '' then
  1606. S := S + 'IconFile=' + IconFilename + SNewLine +
  1607. 'IconIndex=' + IntToStr(IconIndex) + SNewLine;
  1608. F := TTextFileWriter.Create(Filename, fdCreateAlways, faWrite, fsNone);
  1609. try
  1610. if SameText(S, String(AnsiString(S))) then
  1611. F.WriteAnsi(AnsiString(S))
  1612. else
  1613. F.Write(S);
  1614. finally
  1615. F.Free;
  1616. end;
  1617. end;
  1618. procedure DeleteFolderShortcut(const Dir: String);
  1619. var
  1620. Attr: DWORD;
  1621. DesktopIniFilename, S: String;
  1622. begin
  1623. Attr := GetFileAttributes(PChar(Dir));
  1624. if (Attr <> INVALID_FILE_ATTRIBUTES) and (Attr and FILE_ATTRIBUTE_DIRECTORY <> 0) then begin
  1625. { To be sure this is really a folder shortcut and not a regular folder,
  1626. look for a desktop.ini file specifying CLSID_FolderShortcut }
  1627. DesktopIniFilename := PathCombine(Dir, 'desktop.ini');
  1628. S := GetIniString('.ShellClassInfo', 'CLSID2', '', DesktopIniFilename);
  1629. if CompareText(S, '{0AFACED1-E828-11D1-9187-B532F1E9575D}') = 0 then begin
  1630. DeleteFile(DesktopIniFilename);
  1631. DeleteFile(PathCombine(Dir, 'target.lnk'));
  1632. SetFileAttributes(PChar(Dir), Attr and not FILE_ATTRIBUTE_READONLY);
  1633. RemoveDirectory(PChar(Dir));
  1634. end;
  1635. end;
  1636. end;
  1637. procedure CreateAnIcon(Name: String; const Description, Path, Parameters,
  1638. WorkingDir, IconFilename: String; const IconIndex, ShowCmd: Integer;
  1639. const NeverUninstall: Boolean; const CloseOnExit: TSetupIconCloseOnExit;
  1640. const HotKey: Word; const AppUserModelID: String;
  1641. const AppUserModelToastActivatorCLSID: PGUID;
  1642. const ExcludeFromShowInNewInstall, PreventPinning: Boolean);
  1643. var
  1644. BeginsWithGroup: Boolean;
  1645. LinkFilename, PifFilename, UrlFilename, DirFilename, ProbableFilename,
  1646. ResultingFilename: String;
  1647. Flags: TMakeDirFlags;
  1648. URLShortcut: Boolean;
  1649. begin
  1650. BeginsWithGroup := Copy(Name, 1, 8) = '{group}\';
  1651. { Note: PathExpand removes trailing spaces, so it can't be called on
  1652. Name before the extensions are appended }
  1653. Name := ExpandConst(Name);
  1654. LinkFilename := PathExpand(Name + '.lnk');
  1655. PifFilename := PathExpand(Name + '.pif');
  1656. UrlFilename := PathExpand(Name + '.url');
  1657. DirFilename := PathExpand(Name);
  1658. Flags := [mdNotifyChange];
  1659. if NeverUninstall then
  1660. Include(Flags, mdNoUninstall)
  1661. else if BeginsWithGroup then
  1662. Include(Flags, mdAlwaysUninstall);
  1663. URLShortcut := IsPathURL(Path);
  1664. if URLShortcut then
  1665. ProbableFilename := UrlFilename
  1666. else
  1667. ProbableFilename := LinkFilename;
  1668. LogFmt('Dest filename: %s', [ProbableFilename]);
  1669. SetFilenameLabelText(ProbableFilename, True);
  1670. MakeDir(UninstLog, PathExtractDir(ProbableFilename), Flags);
  1671. { Delete any old files first }
  1672. DeleteFile(LinkFilename);
  1673. DeleteFile(PifFilename);
  1674. if NewFileExists(UrlFilename) then begin
  1675. { Flush out any pending writes by other apps before deleting }
  1676. WritePrivateProfileString(nil, nil, nil, PChar(UrlFilename));
  1677. end;
  1678. DeleteFile(UrlFilename);
  1679. DeleteFolderShortcut(DirFilename);
  1680. Log('Creating the icon.');
  1681. if not URLShortcut then begin
  1682. { Create the shortcut.
  1683. Note: Don't call PathExpand on any of the paths since they may contain
  1684. environment-variable strings (e.g. %SystemRoot%\...) }
  1685. ResultingFilename := CreateShellLink(LinkFilename, Description, Path,
  1686. Parameters, WorkingDir, IconFilename, IconIndex, ShowCmd, HotKey,
  1687. AppUserModelID, AppUserModelToastActivatorCLSID,
  1688. ExcludeFromShowInNewInstall, PreventPinning);
  1689. { If a .pif file was created, apply the "Close on exit" setting }
  1690. if (CloseOnExit <> icNoSetting) and
  1691. SameText(PathExtractExt(ResultingFilename), '.pif') then begin
  1692. try
  1693. ModifyPifFile(ResultingFilename, CloseOnExit = icYes);
  1694. except
  1695. { Failure isn't important here. Ignore exceptions }
  1696. end;
  1697. end;
  1698. end
  1699. else begin
  1700. { Create an Internet Shortcut (.url) file }
  1701. CreateURLFile(UrlFilename, Path, IconFilename, IconIndex);
  1702. ResultingFilename := UrlFilename;
  1703. end;
  1704. Log('Successfully created the icon.');
  1705. { Set the global flag that is checked by the Finished wizard page }
  1706. CreatedIcon := True;
  1707. { Notify shell of the change }
  1708. ShellChangeNotifyPath(SHCNE_CREATE, ResultingFilename, False);
  1709. ShellChangeNotifyPath(SHCNE_UPDATEDIR, PathExtractDir(ResultingFilename), True);
  1710. { Add uninstall log entries }
  1711. if not NeverUninstall then begin
  1712. if URLShortcut then
  1713. UninstLog.Add(utDeleteFile, [ResultingFilename], utDeleteFile_CallChangeNotify)
  1714. else begin
  1715. { Even though we only created one file, go ahead and try deleting
  1716. both a .lnk and .pif file at uninstall time, in case the user
  1717. alters the shortcut after installation }
  1718. UninstLog.Add(utDeleteFile, [LinkFilename], utDeleteFile_CallChangeNotify);
  1719. UninstLog.Add(utDeleteFile, [PifFilename], utDeleteFile_CallChangeNotify);
  1720. end;
  1721. end;
  1722. end;
  1723. function ExpandAppPath(const Filename: String): String;
  1724. var
  1725. K: HKEY;
  1726. Found: Boolean;
  1727. begin
  1728. if RegOpenKeyExView(InstallDefaultRegView, HKEY_LOCAL_MACHINE,
  1729. PChar(REGSTR_PATH_APPPATHS + '\' + Filename), 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  1730. Found := RegQueryStringValue(K, '', Result);
  1731. RegCloseKey(K);
  1732. if Found then
  1733. Exit;
  1734. end;
  1735. Result := Filename;
  1736. end;
  1737. var
  1738. CurIcon: PSetupIconEntry;
  1739. FN: String;
  1740. TACLSID: PGUID;
  1741. begin
  1742. for var CurIconNumber := 0 to Entries[seIcon].Count-1 do begin
  1743. try
  1744. CurIcon := PSetupIconEntry(Entries[seIcon][CurIconNumber]);
  1745. with CurIcon^ do begin
  1746. if ShouldProcessIconEntry(WizardComponents, WizardTasks, WizardNoIcons, CurIcon) then begin
  1747. DebugNotifyEntry(seIcon, CurIconNumber);
  1748. NotifyBeforeInstallEntry(BeforeInstall);
  1749. Log('-- Icon entry --');
  1750. FN := ExpandConst(Filename);
  1751. if ioUseAppPaths in Options then
  1752. FN := ExpandAppPath(FN);
  1753. if not(ioCreateOnlyIfFileExists in Options) or
  1754. NewFileExists(ApplyPathRedirRules(IsWin64, FN, tpCurrent)) then begin
  1755. if ioHasAppUserModelToastActivatorCLSID in Options then
  1756. TACLSID := @AppUserModelToastActivatorCLSID
  1757. else
  1758. TACLSID := nil;
  1759. CreateAnIcon(IconName, ExpandConst(Comment), FN,
  1760. ExpandConst(Parameters), ExpandConst(WorkingDir),
  1761. ExpandConst(IconFilename), IconIndex, ShowCmd,
  1762. ioUninsNeverUninstall in Options, CloseOnExit, HotKey,
  1763. ExpandConst(AppUserModelID), TACLSID,
  1764. ioExcludeFromShowInNewInstall in Options,
  1765. ioPreventPinning in Options)
  1766. end else
  1767. Log('Skipping due to "createonlyiffileexists" flag.');
  1768. { Increment progress meter }
  1769. IncProgress(1000);
  1770. NotifyAfterInstallEntry(AfterInstall);
  1771. end;
  1772. end;
  1773. except
  1774. if not(ExceptObject is EAbort) then
  1775. Application.HandleException(nil)
  1776. else
  1777. raise;
  1778. end;
  1779. ProcessEvents;
  1780. { Clear previous filename label in case an exception or debugger break
  1781. occurs between now and when the label for the next entry is set }
  1782. SetFilenameLabelText('', False);
  1783. end;
  1784. end;
  1785. procedure CreateIniEntries(const UninstLog: TUninstallLog);
  1786. var
  1787. CurIni: PSetupIniEntry;
  1788. IniSection, IniEntry, IniValue, IniFilename, IniDir: String;
  1789. Skip: Boolean;
  1790. begin
  1791. for var CurIniNumber := 0 to Entries[seIni].Count-1 do begin
  1792. CurIni := PSetupIniEntry(Entries[seIni][CurIniNumber]);
  1793. with CurIni^ do begin
  1794. if ShouldProcessEntry(WizardComponents, WizardTasks, Components, Tasks, Languages, Check) then begin
  1795. DebugNotifyEntry(seIni, CurIniNumber);
  1796. NotifyBeforeInstallEntry(BeforeInstall);
  1797. Log('-- INI entry --');
  1798. IniSection := ExpandConst(Section);
  1799. IniEntry := ExpandConst(Entry);
  1800. IniValue := ExpandConst(Value);
  1801. IniFilename := ExpandConst(Filename);
  1802. LogFmt('Dest filename: %s', [IniFilename]);
  1803. LogFmt('Section: %s', [IniSection]);
  1804. if IniEntry <> '' then
  1805. LogFmt('Entry: %s', [IniEntry]);
  1806. if ioHasValue in Options then
  1807. LogFmt('Value: %s', [IniValue]);
  1808. if (IniEntry <> '') and (ioHasValue in Options) and
  1809. (not(ioCreateKeyIfDoesntExist in Options) or
  1810. not IniKeyExists(IniSection, IniEntry, IniFilename)) then begin
  1811. Skip := False;
  1812. IniDir := PathExtractDir(IniFilename);
  1813. if IniDir <> '' then begin
  1814. while True do begin
  1815. try
  1816. MakeDir(UninstLog, IniDir);
  1817. Break;
  1818. except
  1819. if AbortRetryIgnoreTaskDialogMsgBox(
  1820. GetExceptMessage,
  1821. [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgAbortRetryIgnoreIgnore], SetupMessages[msgAbortRetryIgnoreCancel]]) then begin
  1822. Skip := True;
  1823. Break;
  1824. end;
  1825. end;
  1826. end;
  1827. end;
  1828. if not Skip then
  1829. Log('Updating the .INI file.');
  1830. repeat
  1831. if SetIniString(IniSection, IniEntry, IniValue, IniFilename) then begin
  1832. Log('Successfully updated the .INI file.');
  1833. Break;
  1834. end;
  1835. until AbortRetryIgnoreTaskDialogMsgBox(
  1836. FmtSetupMessage1(msgErrorIniEntry, IniFilename),
  1837. [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgAbortRetryIgnoreIgnore], SetupMessages[msgAbortRetryIgnoreCancel]]);
  1838. end else
  1839. Log('Skipping updating the .INI file, only updating uninstall log.');
  1840. if ioUninsDeleteEntireSection in Options then
  1841. UninstLog.Add(utIniDeleteSection, [IniFilename, IniSection], 0);
  1842. if ioUninsDeleteSectionIfEmpty in Options then
  1843. UninstLog.Add(utIniDeleteSection, [IniFilename, IniSection],
  1844. utIniDeleteSection_OnlyIfEmpty);
  1845. if (ioUninsDeleteEntry in Options) and (IniEntry <> '') then
  1846. UninstLog.Add(utIniDeleteEntry, [IniFilename, IniSection, IniEntry], 0);
  1847. { ^ add utIniDeleteEntry last since we want it done first by the
  1848. uninstaller (in case the entry's also got the
  1849. "uninsdeletesectionifempty" flag) }
  1850. NotifyAfterInstallEntry(AfterInstall);
  1851. end;
  1852. end;
  1853. end;
  1854. { Increment progress meter }
  1855. IncProgress(1000);
  1856. end;
  1857. procedure CreateRegistryEntries(const UninstLog: TUninstallLog);
  1858. function IsDeletableSubkey(const S: String): Boolean;
  1859. { A sanity check to prevent people from shooting themselves in the foot by
  1860. using
  1861. Root: HKLM; Subkey: ""; Flags: [unins]deletekey
  1862. or a 'code' constant in Subkey that returns a blank string or only
  1863. backslashes. }
  1864. var
  1865. P: PChar;
  1866. begin
  1867. Result := False;
  1868. P := PChar(S);
  1869. while P^ <> #0 do begin
  1870. if P^ <> '\' then begin
  1871. Result := True;
  1872. Break;
  1873. end;
  1874. Inc(P);
  1875. end;
  1876. end;
  1877. procedure ApplyPermissions(const RegView: TRegView; const RootKey: HKEY;
  1878. const Subkey: String; const PermsEntry: Integer);
  1879. var
  1880. P: PSetupPermissionEntry;
  1881. begin
  1882. LogFmt('Setting permissions on key: %s\%s',
  1883. [GetRegRootKeyName(RootKey), Subkey]);
  1884. P := Entries[sePermission][PermsEntry];
  1885. if not GrantPermissionOnKey(RegView, RootKey, Subkey,
  1886. TGrantPermissionEntry(Pointer(P.Permissions)^),
  1887. Length(P.Permissions) div SizeOf(TGrantPermissionEntry)) then begin
  1888. if GetLastError = ERROR_FILE_NOT_FOUND then
  1889. Log('Could not set permissions on the key because it currently does not exist.')
  1890. else
  1891. LogFmt('Failed to set permissions on the key (%d).', [GetLastError]);
  1892. end;
  1893. end;
  1894. const
  1895. REG_QWORD = 11;
  1896. var
  1897. RK, K: HKEY;
  1898. Disp: DWORD;
  1899. N, V, ExistingData: String;
  1900. ExistingType, NewType, DV: DWORD;
  1901. S: String;
  1902. RV: TRegView;
  1903. NeedToRetry, DidDeleteKey: Boolean;
  1904. I: Integer;
  1905. AnsiS: AnsiString;
  1906. begin
  1907. for var CurRegNumber := 0 to Entries[seRegistry].Count-1 do begin
  1908. with PSetupRegistryEntry(Entries[seRegistry][CurRegNumber])^ do begin
  1909. if ShouldProcessEntry(WizardComponents, WizardTasks, Components, Tasks, Languages, Check) then begin
  1910. DebugNotifyEntry(seRegistry, CurRegNumber);
  1911. NotifyBeforeInstallEntry(BeforeInstall);
  1912. Log('-- Registry entry --');
  1913. RK := HKEY(RootKey);
  1914. if RK = HKEY_AUTO then
  1915. RK := InstallModeRootKey;
  1916. S := ExpandConst(Subkey);
  1917. LogFmt('Key: %s\%s', [GetRegRootKeyName(RK), Subkey]);
  1918. N := ExpandConst(ValueName);
  1919. if N <> '' then
  1920. LogFmt('Value name: %s', [N]);
  1921. RV := InstallDefaultRegView;
  1922. if (ro32Bit in Options) and (RV <> rv32Bit) then begin
  1923. Log('Non-default bitness: 32-bit');
  1924. RV := rv32Bit;
  1925. end;
  1926. if ro64Bit in Options then begin
  1927. if not IsWin64 then
  1928. InternalError('Cannot access 64-bit registry keys on this version of Windows');
  1929. if RV <> rv64Bit then begin
  1930. Log('Non-default bitness: 64-bit');
  1931. RV := rv64Bit;
  1932. end;
  1933. end;
  1934. repeat
  1935. NeedToRetry := False;
  1936. try
  1937. DidDeleteKey := False;
  1938. if roDeleteKey in Options then begin
  1939. if IsDeletableSubkey(S) then begin
  1940. Log('Deleting the key.');
  1941. RegDeleteKeyIncludingSubkeys(RV, RK, PChar(S));
  1942. DidDeleteKey := True;
  1943. end else
  1944. Log('Key to delete is not deletable.');
  1945. end;
  1946. if (roDeleteKey in Options) and (Typ = rtNone) then begin
  1947. { We've deleted the key, and no value is to be created.
  1948. Our work is done. }
  1949. if DidDeleteKey then
  1950. Log('Successfully deleted the key.');
  1951. end else if (roDeleteValue in Options) and (Typ = rtNone) then begin
  1952. { We're going to delete a value with no intention of creating
  1953. another, so don't create the key if it didn't exist. }
  1954. if RegOpenKeyExView(RV, RK, PChar(S), 0, KEY_SET_VALUE, K) = ERROR_SUCCESS then begin
  1955. Log('Deleting the value.');
  1956. RegDeleteValue(K, PChar(N));
  1957. RegCloseKey(K);
  1958. Log('Successfully deleted the value.');
  1959. { Our work is done. }
  1960. end else
  1961. Log('Key of value to delete does not exist.');
  1962. end
  1963. else begin
  1964. { Apply any permissions *before* calling RegCreateKeyExView or
  1965. RegOpenKeyExView, since we may (in a rather unlikely scenario)
  1966. need those permissions in order for those calls to succeed }
  1967. if PermissionsEntry <> -1 then
  1968. ApplyPermissions(RV, RK, S, PermissionsEntry);
  1969. { Create or open the key }
  1970. var ErrorCode: DWORD;
  1971. if not(roDontCreateKey in Options) then begin
  1972. Log('Creating or opening the key.');
  1973. ErrorCode := DWORD(RegCreateKeyExView(RV, RK, PChar(S), 0, nil,
  1974. REG_OPTION_NON_VOLATILE, KEY_QUERY_VALUE or KEY_SET_VALUE,
  1975. nil, K, @Disp));
  1976. if ErrorCode = ERROR_SUCCESS then begin
  1977. { Apply permissions again if a new key was created }
  1978. if (Disp = REG_CREATED_NEW_KEY) and (PermissionsEntry <> -1) then begin
  1979. Log('New key created, need to set permissions again.');
  1980. ApplyPermissions(RV, RK, S, PermissionsEntry);
  1981. end;
  1982. end
  1983. else begin
  1984. if not(roNoError in Options) then
  1985. RegError(reRegCreateKeyEx, RK, S, ErrorCode);
  1986. end;
  1987. end
  1988. else begin
  1989. if Typ <> rtNone then begin
  1990. Log('Opening the key.');
  1991. ErrorCode := DWORD(RegOpenKeyExView(RV, RK, PChar(S), 0,
  1992. KEY_QUERY_VALUE or KEY_SET_VALUE, K));
  1993. if (ErrorCode <> ERROR_SUCCESS) and (ErrorCode <> ERROR_FILE_NOT_FOUND) then
  1994. if not(roNoError in Options) then
  1995. RegError(reRegOpenKeyEx, RK, S, ErrorCode);
  1996. end
  1997. else begin
  1998. { We're not creating a value, and we're not just deleting a
  1999. value (that was checked above), so there is no reason to
  2000. even open the key }
  2001. Log('Not creating the key or a value, skipping the key and only updating uninstall log.');
  2002. ErrorCode := ERROR_FILE_NOT_FOUND;
  2003. end;
  2004. end;
  2005. { If there was no error opening the key, proceed with deleting
  2006. and/or creating the value }
  2007. if ErrorCode = ERROR_SUCCESS then
  2008. try
  2009. if roDeleteValue in Options then begin
  2010. Log('Deleting the value.');
  2011. RegDeleteValue(K, PChar(N));
  2012. end;
  2013. if (Typ <> rtNone) and
  2014. (not(roCreateValueIfDoesntExist in Options) or
  2015. not RegValueExists(K, PChar(N))) then begin
  2016. Log('Creating or setting the value.');
  2017. case Typ of
  2018. rtString, rtExpandString, rtMultiString: begin
  2019. NewType := REG_SZ;
  2020. case Typ of
  2021. rtExpandString: NewType := REG_EXPAND_SZ;
  2022. rtMultiString: NewType := REG_MULTI_SZ;
  2023. end;
  2024. if Typ <> rtMultiString then begin
  2025. if (Pos('{olddata}', ValueData) <> 0) and
  2026. RegQueryStringValue(K, PChar(N), ExistingData) then
  2027. { successful }
  2028. else
  2029. ExistingData := '';
  2030. if roPreserveStringType in Options then begin
  2031. if (RegQueryValueEx(K, PChar(N), nil, @ExistingType, nil, nil) = ERROR_SUCCESS) and
  2032. ((ExistingType = REG_SZ) or (ExistingType = REG_EXPAND_SZ)) then
  2033. NewType := ExistingType;
  2034. end;
  2035. V := ExpandConstEx(ValueData, ['olddata', ExistingData])
  2036. end
  2037. else begin
  2038. if (Pos('{olddata}', ValueData) <> 0) and
  2039. RegQueryMultiStringValue(K, PChar(N), ExistingData) then
  2040. { successful }
  2041. else
  2042. ExistingData := '';
  2043. V := ExpandConstEx(ValueData, ['olddata', ExistingData,
  2044. 'break', #0]);
  2045. { Multi-string data requires two null terminators:
  2046. one after the last string, and one to mark the end.
  2047. Delphi's String type is implicitly null-terminated,
  2048. so only one null needs to be added to the end. }
  2049. if (V <> '') and (V[Length(V)] <> #0) then
  2050. V := V + #0;
  2051. end;
  2052. ErrorCode := DWORD(RegSetValueEx(K, PChar(N), 0, NewType,
  2053. PChar(V), (ULength(V)+1)*SizeOf(V[1])));
  2054. if (ErrorCode <> ERROR_SUCCESS) and
  2055. not(roNoError in Options) then
  2056. RegError(reRegSetValueEx, RK, S, ErrorCode);
  2057. end;
  2058. rtDWord: begin
  2059. DV := DWORD(StrToInt(ExpandConst(ValueData)));
  2060. ErrorCode := DWORD(RegSetValueEx(K, PChar(N), 0, REG_DWORD,
  2061. @DV, SizeOf(DV)));
  2062. if (ErrorCode <> ERROR_SUCCESS) and
  2063. not(roNoError in Options) then
  2064. RegError(reRegSetValueEx, RK, S, ErrorCode);
  2065. end;
  2066. rtQWord: begin
  2067. const QV: UInt64 = StrToUInt64(ExpandConst(ValueData));
  2068. ErrorCode := DWORD(RegSetValueEx(K, PChar(N), 0, REG_QWORD,
  2069. @QV, SizeOf(QV)));
  2070. if (ErrorCode <> ERROR_SUCCESS) and
  2071. not(roNoError in Options) then
  2072. RegError(reRegSetValueEx, RK, S, ErrorCode);
  2073. end;
  2074. rtBinary: begin
  2075. AnsiS := '';
  2076. for I := 1 to Length(ValueData) do
  2077. AnsiS := AnsiS + AnsiChar(Ord(ValueData[I]));
  2078. ErrorCode := DWORD(RegSetValueEx(K, PChar(N), 0, REG_BINARY,
  2079. PAnsiChar(AnsiS), ULength(AnsiS)));
  2080. if (ErrorCode <> ERROR_SUCCESS) and
  2081. not(roNoError in Options) then
  2082. RegError(reRegSetValueEx, RK, S, ErrorCode);
  2083. end;
  2084. end;
  2085. Log('Successfully created or set the value.');
  2086. end else if roDeleteValue in Options then
  2087. Log('Successfully deleted the value.')
  2088. else
  2089. Log('Successfully created the key.')
  2090. { Our work is done. }
  2091. finally
  2092. RegCloseKey(K);
  2093. end;
  2094. end;
  2095. except
  2096. if not AbortRetryIgnoreTaskDialogMsgBox(
  2097. GetExceptMessage,
  2098. [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgAbortRetryIgnoreIgnore], SetupMessages[msgAbortRetryIgnoreCancel]]) then begin
  2099. Log('Retrying.');
  2100. NeedToRetry := True;
  2101. end;
  2102. end;
  2103. until not NeedToRetry;
  2104. if roUninsDeleteEntireKey in Options then
  2105. if IsDeletableSubkey(S) then
  2106. UninstLog.AddReg(utRegDeleteEntireKey, RV, RK, [S]);
  2107. if roUninsDeleteEntireKeyIfEmpty in Options then
  2108. if IsDeletableSubkey(S) then
  2109. UninstLog.AddReg(utRegDeleteKeyIfEmpty, RV, RK, [S]);
  2110. if roUninsDeleteValue in Options then
  2111. UninstLog.AddReg(utRegDeleteValue, RV, RK, [S, N]);
  2112. { ^ must add roUninsDeleteValue after roUninstDeleteEntireKey*
  2113. since the entry may have both the roUninsDeleteValue and
  2114. roUninsDeleteEntireKeyIfEmpty options }
  2115. if roUninsClearValue in Options then
  2116. UninstLog.AddReg(utRegClearValue, RV, RK, [S, N]);
  2117. NotifyAfterInstallEntry(AfterInstall);
  2118. end;
  2119. end;
  2120. end;
  2121. { Increment progress meter }
  2122. IncProgress(1000);
  2123. end;
  2124. procedure RegisterFiles(const RegisterFilesList: TList);
  2125. procedure RegisterServersOnRestart;
  2126. function CreateRegSvrExe(const Dir: String): String;
  2127. var
  2128. ExeFilename: String;
  2129. SourceF, DestF: TFile;
  2130. NumRead: Cardinal;
  2131. Buf: array[0..16383] of Byte;
  2132. begin
  2133. ExeFilename := GenerateUniqueName(Dir, '.exe');
  2134. DestF := nil;
  2135. SourceF := TFile.Create(NewParamStr(0), fdOpenExisting, faRead, fsRead);
  2136. try
  2137. DestF := TFile.Create(ExeFilename, fdCreateAlways, faWrite, fsNone);
  2138. try
  2139. DestF.Seek(SourceF.Size);
  2140. DestF.Truncate;
  2141. DestF.Seek(0);
  2142. while True do begin
  2143. NumRead := SourceF.Read(Buf, SizeOf(Buf));
  2144. if NumRead = 0 then
  2145. Break;
  2146. DestF.WriteBuffer(Buf, NumRead);
  2147. end;
  2148. if not(shSignedUninstaller in SetupHeader.Options) then
  2149. MarkExeHeader(DestF, SetupExeModeRegSvr);
  2150. except
  2151. FreeAndNil(DestF);
  2152. DeleteFile(ExeFilename);
  2153. raise;
  2154. end;
  2155. finally
  2156. DestF.Free;
  2157. SourceF.Free;
  2158. end;
  2159. Result := ExeFilename;
  2160. end;
  2161. procedure CreateRegSvrMsg(const Filename: String);
  2162. var
  2163. F: TFile;
  2164. begin
  2165. F := TFile.Create(Filename, fdCreateAlways, faWrite, fsNone);
  2166. try
  2167. WriteMsgData(F);
  2168. finally
  2169. F.Free;
  2170. end;
  2171. end;
  2172. const
  2173. Chars: array[Boolean, Boolean] of Char = (('s', 't'), ('S', 'T'));
  2174. var
  2175. RegSvrExeFilename: String;
  2176. F: TTextFileWriter;
  2177. Rec: PRegisterFilesListRec;
  2178. RootKey, H: HKEY;
  2179. J: Integer;
  2180. Disp: DWORD;
  2181. ValueName, Data: String;
  2182. begin
  2183. { Create RegSvr program used to register OLE servers & type libraries on
  2184. the next reboot }
  2185. if IsAdmin then begin
  2186. try
  2187. RegSvrExeFilename := CreateRegSvrExe(WinDir);
  2188. except
  2189. { In case Windows directory is write protected, try the Temp directory.
  2190. Windows directory is our first choice since some people (ignorantly)
  2191. put things like "DELTREE C:\WINDOWS\TEMP\*.*" in their AUTOEXEC.BAT.
  2192. Also, each user has his own personal Temp directory which may not
  2193. be accessible by other users. }
  2194. RegSvrExeFilename := CreateRegSvrExe(GetTempDir);
  2195. end;
  2196. end
  2197. else begin
  2198. { Always use Temp directory when user doesn't have admin privileges }
  2199. RegSvrExeFilename := CreateRegSvrExe(GetTempDir);
  2200. end;
  2201. LogFmt('Registration executable created: %s', [RegSvrExeFilename]);
  2202. try
  2203. CreateRegSvrMsg(PathChangeExt(RegSvrExeFilename, '.msg'));
  2204. F := TTextFileWriter.Create(PathChangeExt(RegSvrExeFilename, '.lst'),
  2205. fdCreateAlways, faWrite, fsNone);
  2206. try
  2207. F.WriteLine('; This file was created by the installer for:');
  2208. F.WriteLine('; ' + ExpandedAppVerName);
  2209. F.WriteLine('; Location: ' + SetupLdrOriginalFilename);
  2210. F.WriteLine('');
  2211. F.WriteLine('; List of files to be registered on the next reboot. DO NOT EDIT!');
  2212. F.WriteLine('');
  2213. for var I := 0 to RegisterFilesList.Count-1 do begin
  2214. Rec := RegisterFilesList[I];
  2215. Data := '[..]' + Rec.Filename;
  2216. Data[2] := Chars[Rec.Is64Bit, Rec.TypeLib];
  2217. if Rec.NoErrorMessages then
  2218. Data[3] := 'q';
  2219. F.WriteLine(Data);
  2220. end;
  2221. finally
  2222. F.Free;
  2223. end;
  2224. if IsAdmin then
  2225. RootKey := HKEY_LOCAL_MACHINE
  2226. else
  2227. RootKey := HKEY_CURRENT_USER;
  2228. var ErrorCode := DWORD(RegCreateKeyExView(rvDefault, RootKey, REGSTR_PATH_RUNONCE, 0, nil,
  2229. REG_OPTION_NON_VOLATILE, KEY_SET_VALUE or KEY_QUERY_VALUE,
  2230. nil, H, @Disp));
  2231. if ErrorCode <> ERROR_SUCCESS then
  2232. RegError(reRegCreateKeyEx, RootKey, REGSTR_PATH_RUNONCE, ErrorCode);
  2233. try
  2234. J := 0;
  2235. while True do begin
  2236. Inc(J);
  2237. ValueName := Format('InnoSetupRegFile.%.10d', [J]); { don't localize }
  2238. { ^ Note: Names of values written to the "RunOnce" key cannot
  2239. exceed 31 characters! Otherwise the original Windows
  2240. Explorer 4.0 will not process them. }
  2241. if not RegValueExists(H, PChar(ValueName)) then begin
  2242. Data := '"' + RegSvrExeFilename + '" /REG';
  2243. if not IsAdmin then
  2244. Data := Data + 'U'; { /REG -> /REGU when not running as admin }
  2245. { Note: RegSvr expects /REG(U) to be the first parameter }
  2246. Data := Data + ' /REGSVRMODE';
  2247. ErrorCode := DWORD(RegSetValueEx(H, PChar(ValueName), 0, REG_SZ, PChar(Data),
  2248. (ULength(Data)+1)*SizeOf(Data[1])));
  2249. if ErrorCode <> ERROR_SUCCESS then
  2250. RegError(reRegSetValueEx, RootKey, REGSTR_PATH_RUNONCE, ErrorCode);
  2251. Break;
  2252. end;
  2253. end;
  2254. finally
  2255. RegCloseKey(H);
  2256. end;
  2257. except
  2258. DeleteFile(PathChangeExt(RegSvrExeFilename, '.lst'));
  2259. DeleteFile(PathChangeExt(RegSvrExeFilename, '.msg'));
  2260. DeleteFile(RegSvrExeFilename);
  2261. raise;
  2262. end;
  2263. end;
  2264. procedure RegisterServerWithRetries(const Is64Bit: Boolean; const Filename: String;
  2265. const NoErrorMessages: Boolean);
  2266. var
  2267. NeedToRetry: Boolean;
  2268. begin
  2269. repeat
  2270. if Is64Bit then
  2271. LogFmt('Registering 64-bit DLL/OCX: %s', [Filename])
  2272. else
  2273. LogFmt('Registering 32-bit DLL/OCX: %s', [Filename]);
  2274. NeedToRetry := False;
  2275. try
  2276. RegisterServer(False, Is64Bit, Filename, NoErrorMessages);
  2277. Log('Registration successful.');
  2278. except
  2279. Log('Registration failed:' + SNewLine + GetExceptMessage);
  2280. if not NoErrorMessages then
  2281. if not AbortRetryIgnoreTaskDialogMsgBox(
  2282. Filename + SNewLine2 + FmtSetupMessage1(msgErrorRegisterServer, GetExceptMessage),
  2283. [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgFileAbortRetryIgnoreIgnoreNotRecommended], SetupMessages[msgAbortRetryIgnoreCancel]]) then
  2284. NeedToRetry := True;
  2285. end;
  2286. until not NeedToRetry;
  2287. end;
  2288. procedure RegisterTypeLibraryWithRetries(const Is64Bit: Boolean; const Filename: String;
  2289. const NoErrorMessages: Boolean);
  2290. var
  2291. NeedToRetry: Boolean;
  2292. begin
  2293. repeat
  2294. if Is64Bit then
  2295. LogFmt('Registering 64-bit type library: %s', [Filename])
  2296. else
  2297. LogFmt('Registering 32-bit type library: %s', [Filename]);
  2298. NeedToRetry := False;
  2299. try
  2300. {$IFDEF WIN64}
  2301. if Is64Bit then
  2302. RegisterTypeLibrary(Filename)
  2303. else
  2304. InternalError('Cannot register 32-bit type libraries on this version of Setup');
  2305. {$ELSE}
  2306. if Is64Bit then
  2307. InternalError('Cannot register 64-bit type libraries on this version of Setup')
  2308. else
  2309. RegisterTypeLibrary(Filename);
  2310. {$ENDIF}
  2311. Log('Registration successful.');
  2312. except
  2313. Log('Registration failed:' + SNewLine + GetExceptMessage);
  2314. if not NoErrorMessages then
  2315. if not AbortRetryIgnoreTaskDialogMsgBox(
  2316. Filename + SNewLine2 + FmtSetupMessage1(msgErrorRegisterTypeLib, GetExceptMessage),
  2317. [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgFileAbortRetryIgnoreIgnoreNotRecommended], SetupMessages[msgAbortRetryIgnoreCancel]]) then
  2318. NeedToRetry := True;
  2319. end;
  2320. until not NeedToRetry;
  2321. end;
  2322. begin
  2323. if not NeedsRestart then
  2324. for var I := 0 to RegisterFilesList.Count-1 do begin
  2325. with PRegisterFilesListRec(RegisterFilesList[I])^ do
  2326. if not TypeLib then
  2327. RegisterServerWithRetries(Is64Bit, Filename, NoErrorMessages)
  2328. else
  2329. RegisterTypeLibraryWithRetries(Is64Bit, Filename, NoErrorMessages);
  2330. end
  2331. else begin
  2332. { When a restart is needed, all "regserver" & "regtypelib" files will get
  2333. registered on the next logon }
  2334. Log('Delaying registration of all files until the next logon since a restart is needed.');
  2335. try
  2336. RegisterServersOnRestart;
  2337. except
  2338. Application.HandleException(nil);
  2339. end;
  2340. end;
  2341. end;
  2342. procedure RecordUninstallDeleteEntries(const UninstLog: TUninstallLog);
  2343. const
  2344. DefFlags: array[TSetupDeleteType] of TUninstallRecExtraData = (
  2345. utDeleteDirOrFiles_Extra or utDeleteDirOrFiles_DeleteFiles,
  2346. utDeleteDirOrFiles_Extra or utDeleteDirOrFiles_DeleteFiles or
  2347. utDeleteDirOrFiles_DeleteSubdirsAlso,
  2348. utDeleteDirOrFiles_Extra or utDeleteDirOrFiles_IsDir);
  2349. begin
  2350. for var I := Entries[seUninstallDelete].Count-1 downto 0 do
  2351. { ^ process backwards so the uninstaller will process them in the order
  2352. they appear in the script }
  2353. with PSetupDeleteEntry(Entries[seUninstallDelete][I])^ do
  2354. if ShouldProcessEntry(WizardComponents, WizardTasks, Components, Tasks, Languages, Check) then begin
  2355. DebugNotifyEntry(seUninstallDelete, I);
  2356. NotifyBeforeInstallEntry(BeforeInstall);
  2357. const Path = ApplyPathRedirRules(InstallDefault64Bit, ExpandConst(Name), tpCurrent);
  2358. var Flags := DefFlags[DeleteType];
  2359. if IsCurrentProcess64Bit then { Post-ApplyPathRedirRules we should check IsCurrentProcess64Bit and not the original InstallDefault64Bit }
  2360. Flags := Flags or utDeleteDirOrFiles_Is64Bit;
  2361. UninstLog.Add(utDeleteDirOrFiles, [Path], Flags);
  2362. NotifyAfterInstallEntry(AfterInstall);
  2363. end;
  2364. end;
  2365. procedure RecordUninstallRunEntries(const UninstLog: TUninstallLog);
  2366. var
  2367. RunEntry: PSetupRunEntry;
  2368. begin
  2369. for var I := Entries[seUninstallRun].Count-1 downto 0 do begin
  2370. { ^ process backwards so the uninstaller will process them in the order
  2371. they appear in the script }
  2372. RunEntry := PSetupRunEntry(Entries[seUninstallRun][I]);
  2373. if ShouldProcessEntry(WizardComponents, WizardTasks, RunEntry.Components,
  2374. RunEntry.Tasks, RunEntry.Languages, RunEntry.Check) then begin
  2375. DebugNotifyEntry(seUninstallRun, I);
  2376. NotifyBeforeInstallEntry(RunEntry.BeforeInstall);
  2377. var Flags: TUninstallRecExtraData := 0;
  2378. case RunEntry.Wait of
  2379. rwNoWait: Flags := Flags or utRun_NoWait;
  2380. rwWaitUntilIdle: Flags := Flags or utRun_WaitUntilIdle;
  2381. end;
  2382. if roShellExec in RunEntry.Options then
  2383. Flags := Flags or (utRun_ShellExec or utRun_ShellExecRespectWaitFlags)
  2384. else begin
  2385. if RunEntryIs64Bit(RunEntry) then
  2386. Flags := Flags or utRun_Is64Bit;
  2387. end;
  2388. if roSkipIfDoesntExist in RunEntry.Options then
  2389. Flags := Flags or utRun_SkipIfDoesntExist;
  2390. case RunEntry.ShowCmd of
  2391. SW_SHOWMINNOACTIVE: Flags := Flags or utRun_RunMinimized;
  2392. SW_SHOWMAXIMIZED: Flags := Flags or utRun_RunMaximized;
  2393. SW_HIDE: Flags := Flags or utRun_RunHidden;
  2394. end;
  2395. if roDontLogParameters in RunEntry.Options then
  2396. Flags := Flags or utRun_DontLogParameters;
  2397. if roLogOutput in RunEntry.Options then
  2398. Flags := Flags or utRun_LogOutput;
  2399. UninstLog.Add(utRun, [ExpandConst(RunEntry.Name),
  2400. ExpandConst(RunEntry.Parameters), ExpandConst(RunEntry.WorkingDir),
  2401. ExpandConst(RunEntry.RunOnceId), ExpandConst(RunEntry.Verb)],
  2402. Flags);
  2403. NotifyAfterInstallEntry(RunEntry.AfterInstall);
  2404. end;
  2405. end;
  2406. end;
  2407. { Also sets UninstallExeFilename which is a global }
  2408. procedure GenerateUninstallInfoFilename(const UninstLog: TUninstallLog;
  2409. var UninstallDataFilename, UninstallMsgFilename: String;
  2410. var UninstallDataCreated, AppendUninstallData: Boolean);
  2411. procedure FindFiles(const BaseDir: String; var ExistingFiles: array of Boolean);
  2412. var
  2413. H: THandle;
  2414. FindData: TWin32FindData;
  2415. S: String;
  2416. begin
  2417. H := FindFirstFile(PChar(AddBackslash(BaseDir) + 'unins???.*'),
  2418. FindData);
  2419. if H <> INVALID_HANDLE_VALUE then begin
  2420. repeat
  2421. S := FindData.cFilename;
  2422. if (Length(S) >= 9) and (CompareText(Copy(S, 1, 5), 'unins') = 0) and
  2423. CharInSet(S[6], ['0'..'9']) and CharInSet(S[7], ['0'..'9']) and CharInSet(S[8], ['0'..'9']) and
  2424. (S[9] = '.') then
  2425. ExistingFiles[StrToInt(Copy(S, 6, 3))] := True;
  2426. until not FindNextFile(H, FindData);
  2427. Windows.FindClose(H);
  2428. end;
  2429. end;
  2430. procedure GenerateFilenames(const BaseDir: String; const I: Integer);
  2431. var
  2432. BaseFilename: String;
  2433. begin
  2434. BaseFilename := AddBackslash(BaseDir) + Format('unins%.3d', [I]);
  2435. UninstallExeFilename := BaseFilename + '.exe';
  2436. UninstallDataFilename := BaseFilename + '.dat';
  2437. UninstallMsgFilename := BaseFilename + '.msg';
  2438. end;
  2439. procedure ReserveDataFile;
  2440. var
  2441. H: THandle;
  2442. begin
  2443. { Create an empty .dat file to reserve the filename. }
  2444. H := CreateFile(PChar(UninstallDataFilename), GENERIC_READ or GENERIC_WRITE,
  2445. 0, nil, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
  2446. if H = INVALID_HANDLE_VALUE then
  2447. Win32ErrorMsg('CreateFile');
  2448. CloseHandle(H);
  2449. UninstallDataCreated := True;
  2450. end;
  2451. var
  2452. ExistingFiles: array[0..999] of Boolean;
  2453. begin
  2454. { For consistency with IS<=6.x, in case someone sets UninstallFilesDir to
  2455. "sys" (not recommended), we run the path through ApplyPathRedirRules to
  2456. change System32 to SysWOW64 when running on 32-bit Setup. This ensures
  2457. that 64-bit processes like Add/Remove Programs and Explorer know that the
  2458. uninstaller EXE is in the 32-bit system directory.
  2459. On 64-bit Setup, however, this leaves System32 as-is, so 32-bit processes
  2460. may be unable to access the uninstaller EXE.
  2461. Also, rfNormalPath is used because UninstallFilesDir is used as the basis
  2462. for the uninstallexe constant, which is written to the Uninstall key and
  2463. can also be used in [Icons] shortcuts. It is not known whether
  2464. Add/Remove Programs or Explorer support super paths, but because
  2465. UninstallFilesDir would usually not exceed MAX_PATH, using rfNormalPath
  2466. does not introduce a limitation in practice. }
  2467. const BaseDir = ApplyPathRedirRules(IsCurrentProcess64Bit,
  2468. ExpandConst(SetupHeader.UninstallFilesDir), tpCurrent, [rfNormalPath]);
  2469. LogFmt('Directory for uninstall files: %s', [BaseDir]);
  2470. MakeDir(UninstLog, BaseDir);
  2471. FillChar(ExistingFiles, SizeOf(ExistingFiles), 0); { set all to False }
  2472. FindFiles(BaseDir, ExistingFiles);
  2473. { Look for an existing .dat file to append to or overwrite }
  2474. if SetupHeader.UninstallLogMode <> lmNew then
  2475. for var I := 0 to 999 do
  2476. if ExistingFiles[I] then begin
  2477. GenerateFilenames(BaseDir, I);
  2478. if NewFileExists(UninstallDataFilename) and
  2479. UninstLog.CanAppend(UninstallDataFilename) then begin
  2480. if SetupHeader.UninstallLogMode = lmAppend then begin
  2481. LogFmt('Will append to existing uninstall log: %s', [UninstallDataFilename]);
  2482. AppendUninstallData := True;
  2483. end
  2484. else
  2485. LogFmt('Will overwrite existing uninstall log: %s', [UninstallDataFilename]);
  2486. Exit;
  2487. end;
  2488. end;
  2489. { None found; use a new .dat file }
  2490. for var I := 0 to 999 do
  2491. if not ExistingFiles[I] then begin
  2492. GenerateFilenames(BaseDir, I);
  2493. LogFmt('Creating new uninstall log: %s', [UninstallDataFilename]);
  2494. ReserveDataFile;
  2495. Exit;
  2496. end;
  2497. raise Exception.Create(FmtSetupMessage1(msgErrorTooManyFilesInDir,
  2498. BaseDir));
  2499. end;
  2500. procedure RenameUninstallExe(var UninstallTempExeFilename: String);
  2501. begin
  2502. { If the uninstall EXE wasn't extracted to a .tmp file because it isn't
  2503. replacing an existing uninstall EXE, exit. }
  2504. if UninstallTempExeFilename = '' then
  2505. Exit;
  2506. Log('Renaming uninstaller.');
  2507. var Timer: TOneShotTimer;
  2508. const CapturableUninstallTempExeFilename = UninstallTempExeFilename;
  2509. PerformFileOperationWithRetries(4, False,
  2510. function(out LastError: Cardinal): Boolean
  2511. begin
  2512. Timer.Start(1000);
  2513. Result := MoveFileReplace(CapturableUninstallTempExeFilename, UninstallExeFilename);
  2514. if not Result then
  2515. LastError := GetLastError;
  2516. end,
  2517. procedure(const LastError: Cardinal)
  2518. begin
  2519. LogFmt('The existing file appears to be in use (%d). ' +
  2520. 'Retrying.', [LastError]);
  2521. Timer.SleepUntilExpired;
  2522. ProcessEvents;
  2523. end,
  2524. procedure(const LastError: Cardinal; var TryOnceMore: Boolean)
  2525. begin
  2526. const LastOperation = SetupMessages[msgErrorReplacingExistingFile];
  2527. const Failed = AddPeriod(FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  2528. ['MoveFileEx', IntToStr(LastError), Win32ErrorString(LastError)]));
  2529. const Text = UninstallExeFilename + SNewLine2 + LastOperation + SNewLine + Failed;
  2530. case LoggedTaskDialogMsgBox('', SetupMessages[msgRetryCancelSelectAction], Text, '',
  2531. mbError, MB_RETRYCANCEL, [SetupMessages[msgRetryCancelRetry], SetupMessages[msgRetryCancelCancel]],
  2532. 0, True, IDCANCEL) of
  2533. IDRETRY: TryOnceMore := True;
  2534. IDCANCEL: Abort;
  2535. else
  2536. Log('LoggedTaskDialogMsgBox returned an unexpected value. Assuming Cancel.');
  2537. Abort;
  2538. end;
  2539. end);
  2540. UninstallTempExeFilename := '';
  2541. end;
  2542. function CreateUninstallMsgFile(const UninstallExeCreated: TUninstallExeCreated;
  2543. const UninstallMsgFilename: String): Boolean;
  2544. { If the uninstaller EXE has a digital signature, or if Setup was started
  2545. with /DETACHEDMSG, create the unins???.msg file }
  2546. var
  2547. F: TFile;
  2548. begin
  2549. { If this installation didn't create or replace an unins???.exe file,
  2550. do nothing }
  2551. Result := False;
  2552. if (UninstallExeCreated <> ueNone) and
  2553. ((shSignedUninstaller in SetupHeader.Options) or DetachedUninstMsgFile) then begin
  2554. LogFmt('Writing uninstaller messages: %s', [UninstallMsgFilename]);
  2555. F := TFile.Create(UninstallMsgFilename, fdCreateAlways, faWrite, fsNone);
  2556. try
  2557. if UninstallExeCreated = ueNew then
  2558. Result := True;
  2559. WriteMsgData(F);
  2560. finally
  2561. F.Free;
  2562. end;
  2563. end;
  2564. end;
  2565. procedure PerformInstall(var Succeeded: Boolean; const ChangesEnvironment,
  2566. ChangesAssociations: Boolean);
  2567. begin
  2568. Succeeded := False;
  2569. Log('Starting the installation process.');
  2570. SetCurrentDir(WinSystemDir);
  2571. var InstallFilesSize, AfterInstallFilesSize: Int64;
  2572. CalcFilesSize(InstallFilesSize, AfterInstallFilesSize);
  2573. InitProgressGauge(InstallFilesSize);
  2574. var RegisterFilesList: TList := nil;
  2575. const UninstLog = TSetupUninstallLog.Create;
  2576. try
  2577. var UninstallExeCreated := ueNone;
  2578. var UninstallDataCreated := False;
  2579. var UninstallMsgCreated := False;
  2580. var UninstLogCleared := False;
  2581. var UninstallTempExeFilename, UninstallDataFilename, UninstallMsgFilename: String; { There's also UninstallExeFilename but it's a global }
  2582. try
  2583. { Get AppId, UninstallRegKeyBaseName, and Uninstallable now so the user
  2584. can't change them while we're installing }
  2585. const ExpandedAppId = ExpandConst(SetupHeader.AppId);
  2586. if ExpandedAppId = '' then
  2587. InternalError('Failed to get a non empty installation "AppId"');
  2588. if TUninstallLog.WriteSafeHeaderString(nil, ExpandedAppId, 0) > 128 then
  2589. InternalError('"AppId" cannot exceed 128 bytes (encoded)');
  2590. const UninstallRegKeyBaseName = GetUninstallRegKeyBaseName(ExpandedAppId);
  2591. const Uninstallable = EvalDirectiveCheck(SetupHeader.Uninstallable);
  2592. { Init }
  2593. UninstLog.InstallMode64Bit := Is64BitInstallMode;
  2594. UninstLog.AppName := ExpandedAppName;
  2595. UninstLog.AppId := ExpandedAppId;
  2596. if IsAdminInstallMode then
  2597. Include(UninstLog.Flags, ufAdminInstallMode);
  2598. if IsWin64 then
  2599. Include(UninstLog.Flags, ufWin64);
  2600. if IsAdmin then { Setup or [Code] might have done administrative actions, even if IsAdminInstallMode is False }
  2601. Include(UninstLog.Flags, ufAdminInstalled)
  2602. else if IsPowerUserOrAdmin then
  2603. { Note: This flag is only set in 5.1.9 and later }
  2604. Include(UninstLog.Flags, ufPowerUserInstalled);
  2605. if shUninstallRestartComputer in SetupHeader.Options then
  2606. Include(UninstLog.Flags, ufAlwaysRestart);
  2607. if ChangesEnvironment then
  2608. Include(UninstLog.Flags, ufChangesEnvironment);
  2609. if RedirectionGuardEnabled then
  2610. Include(UninstLog.Flags, ufRedirectionGuard);
  2611. RecordStartInstall(UninstLog);
  2612. RecordCompiledCode(UninstLog);
  2613. RegisterFilesList := TList.Create;
  2614. { Process Component entries, if any }
  2615. ProcessComponentEntries;
  2616. ProcessEvents;
  2617. { Process Tasks entries, if any }
  2618. ProcessTasksEntries;
  2619. ProcessEvents;
  2620. { Shutdown applications, if any }
  2621. if RmSessionStarted and RmFoundApplications then begin
  2622. if WizardPreparingYesRadio then begin
  2623. SetStatusLabelText(SetupMessages[msgStatusClosingApplications]);
  2624. ShutdownApplications;
  2625. ProcessEvents;
  2626. end else
  2627. Log('User chose not to shutdown applications using our files.');
  2628. end;
  2629. { Process InstallDelete entries, if any }
  2630. ProcessInstallDeleteEntries;
  2631. ProcessEvents;
  2632. if ExpandedAppMutex <> '' then
  2633. UninstLog.Add(utMutexCheck, [ExpandedAppMutex], 0);
  2634. if ChangesAssociations then
  2635. UninstLog.Add(utRefreshFileAssoc, [''], 0);
  2636. { Record UninstallDelete entries, if any }
  2637. RecordUninstallDeleteEntries(UninstLog);
  2638. ProcessEvents;
  2639. { Create the application directory and extra dirs }
  2640. SetStatusLabelText(SetupMessages[msgStatusCreateDirs]);
  2641. CreateDirs(UninstLog);
  2642. ProcessEvents;
  2643. var AppendUninstallData := False;
  2644. if Uninstallable then begin
  2645. { Generate the filenam(UninstLog)es for the uninstall info in the application
  2646. directory }
  2647. SetStatusLabelText(SetupMessages[msgStatusSavingUninstall]);
  2648. GenerateUninstallInfoFilename(UninstLog, UninstallDataFilename, UninstallMsgFilename,
  2649. UninstallDataCreated, AppendUninstallData);
  2650. end;
  2651. { Copy the files }
  2652. SetStatusLabelText(SetupMessages[msgStatusExtractFiles]);
  2653. CopyFiles(UninstLog, ExpandedAppId, RegisterFilesList, Uninstallable,
  2654. UninstallTempExeFilename, UninstallExeCreated);
  2655. ProcessEvents;
  2656. { Create program icons, if any }
  2657. if HasIcons then begin
  2658. SetStatusLabelText(SetupMessages[msgStatusCreateIcons]);
  2659. CreateIcons(UninstLog);
  2660. ProcessEvents;
  2661. end;
  2662. { Create INI entries, if any }
  2663. if Entries[seIni].Count <> 0 then begin
  2664. SetStatusLabelText(SetupMessages[msgStatusCreateIniEntries]);
  2665. CreateIniEntries(UninstLog);
  2666. ProcessEvents;
  2667. end;
  2668. { Create registry entries, if any }
  2669. if Entries[seRegistry].Count <> 0 then begin
  2670. SetStatusLabelText(SetupMessages[msgStatusCreateRegistryEntries]);
  2671. CreateRegistryEntries(UninstLog);
  2672. ProcessEvents;
  2673. end;
  2674. { Call the NeedRestart event function now.
  2675. Note: This can't be done after RegisterFiles, since RegisterFiles
  2676. relies on the setting of the NeedsRestart variable. }
  2677. SetStatusLabelText('');
  2678. ProcessNeedRestartEvent;
  2679. ProcessEvents;
  2680. { Register files, if any }
  2681. if RegisterFilesList.Count <> 0 then begin
  2682. SetStatusLabelText(SetupMessages[msgStatusRegisterFiles]);
  2683. RegisterFiles(RegisterFilesList);
  2684. ProcessEvents;
  2685. end;
  2686. { Save uninstall information. After uninstall info is saved, you cannot
  2687. make any more modifications to the user's system. Any additional
  2688. modifications you want to add must be done before this is called. }
  2689. if Uninstallable then begin
  2690. SetStatusLabelText(SetupMessages[msgStatusSavingUninstall]);
  2691. Log('Saving uninstall information.');
  2692. RenameUninstallExe(UninstallTempExeFilename);
  2693. UninstallMsgCreated := CreateUninstallMsgFile(UninstallExeCreated, UninstallMsgFilename);
  2694. { Register uninstall information so the program can be uninstalled
  2695. through the Add/Remove Programs Control Panel applet. This is done
  2696. on NT 3.51 too, so that the uninstall entry for the app will appear
  2697. if the user later upgrades to NT 4.0+. }
  2698. if EvalDirectiveCheck(SetupHeader.CreateUninstallRegKey) then
  2699. RegisterUninstallInfo(UninstLog, UninstallRegKeyBaseName, AfterInstallFilesSize);
  2700. RecordUninstallRunEntries(UninstLog);
  2701. UninstLog.Add(utEndInstall, [GetLocalTimeAsStr], 0);
  2702. UninstLog.Save(UninstallDataFilename, AppendUninstallData,
  2703. shUpdateUninstallLogAppName in SetupHeader.Options);
  2704. if Debugging then
  2705. DebugNotifyUninstExe(UninstallExeFileName);
  2706. end;
  2707. SetStatusLabelText('');
  2708. UninstLogCleared := True;
  2709. UninstLog.Clear;
  2710. except
  2711. try
  2712. { Show error message, if any, and set the exit code we'll be returning }
  2713. if not(ExceptObject is EAbort) then begin
  2714. Log(Format('Fatal exception during installation process (%s):' + SNewLine,
  2715. [ExceptObject.ClassName]) + GetExceptMessage);
  2716. SetupExitCode := ecInstallationError;
  2717. Application.HandleException(nil);
  2718. LoggedMsgBox(SetupMessages[msgSetupAborted], '', mbCriticalError, MB_OK, True, IDOK);
  2719. end
  2720. else begin
  2721. Log('User canceled the installation process.');
  2722. SetupExitCode := ecInstallationCancelled;
  2723. end;
  2724. { Undo any changes it's made so far }
  2725. if not UninstLogCleared then begin
  2726. Log('Rolling back changes.');
  2727. try
  2728. SetStatusLabelText(SetupMessages[msgStatusRollback]);
  2729. WizardForm.ProgressGauge.Visible := False;
  2730. FinishProgressGauge(True);
  2731. WizardForm.CancelButton.Enabled := False;
  2732. WizardForm.Update;
  2733. except
  2734. { ignore any exceptions, just in case... }
  2735. end;
  2736. if UninstallTempExeFilename <> '' then
  2737. DeleteFile(UninstallTempExeFilename);
  2738. if UninstallExeCreated = ueNew then
  2739. DeleteFile(UninstallExeFilename);
  2740. if UninstallDataCreated then
  2741. DeleteFile(UninstallDataFilename);
  2742. if UninstallMsgCreated then
  2743. DeleteFile(UninstallMsgFilename);
  2744. UninstLog.PerformUninstall(False, nil);
  2745. { Sleep for a bit so that the user has time to read the "Rolling
  2746. back changes" message }
  2747. if WizardForm.Visible then
  2748. Sleep(1500);
  2749. end;
  2750. except
  2751. { No exception should be generated by the above code, but just in
  2752. case, handle any exception now so that Application.Terminate is
  2753. always called below.
  2754. Note that we can't just put Application.Terminate in a finally
  2755. section, because it would prevent the display of an exception
  2756. message box later (MessageBox() dislikes WM_QUIT). }
  2757. Application.HandleException(nil);
  2758. end;
  2759. Exit;
  2760. end;
  2761. finally
  2762. if Assigned(RegisterFilesList) then begin
  2763. for var I := RegisterFilesList.Count-1 downto 0 do
  2764. Dispose(PRegisterFilesListRec(RegisterFilesList[I]));
  2765. RegisterFilesList.Free;
  2766. end;
  2767. UninstLog.Free;
  2768. FinishProgressGauge(False);
  2769. end;
  2770. Log('Installation process succeeded.');
  2771. Succeeded := True;
  2772. end;
  2773. end.