| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970 |
- unit Setup.Install;
- {
- Inno Setup
- Copyright (C) 1997-2026 Jordan Russell
- Portions by Martijn Laan
- For conditions of distribution and use, see LICENSE.TXT.
- Installation procedures
- }
- interface
- procedure PerformInstall(var Succeeded: Boolean; const ChangesEnvironment,
- ChangesAssociations: Boolean);
- implementation
- uses
- Windows, Messages, ShlObj, RegStr, Classes, SysUtils, Forms,
- ISSigFunc, PathFunc, SHA256, UnsignedFunc,
- Shared.CommonFunc, Shared.CommonFunc.Vcl, Shared.FileClass,
- Shared.SetupMessageIDs, Shared.SetupTypes, Shared.Struct, Shared.VerInfoFunc,
- Compression.Base, Compression.SevenZipDLLDecoder,
- SetupLdrAndSetup.InstFunc, SetupLdrAndSetup.Messages, Setup.PathRedir,
- Setup.DebugClient, Setup.DotNetFunc, Setup.DownloadFileFunc, Setup.InstFunc, Setup.InstFunc.Ole,
- Setup.ISSigVerifyFunc, Setup.FileExtractor, Setup.Install.HelperFunc,
- Setup.MainFunc, Setup.LoggingFunc, Setup.RegDLL, Setup.SecurityFunc,
- Setup.UninstallLog, Setup.WizardForm;
- type
- PRegisterFilesListRec = ^TRegisterFilesListRec;
- TRegisterFilesListRec = record
- Filename: String;
- Is64Bit, TypeLib, NoErrorMessages: Boolean;
- end;
- TUninstallExeCreated = (ueNone, ueNew, ueReplaced);
-
- procedure RecordStartInstall(const UninstLog: TUninstallLog);
- var
- AppDir: String;
- begin
- if shCreateAppDir in SetupHeader.Options then
- AppDir := WizardDirValue
- else
- AppDir := '';
- UninstLog.Add(utStartInstall, [GetComputerNameString, GetUserNameString,
- AppDir, GetLocalTimeAsStr], 0);
- end;
- procedure RecordCompiledCode(const UninstLog: TUninstallLog);
- var
- LeadBytesStr, ExpandedApp, ExpandedGroup, CustomMessagesStr: String;
- begin
- { Only use app if Setup creates one }
- if shCreateAppDir in SetupHeader.Options then
- ExpandedApp := ExpandConst('{app}')
- else
- ExpandedApp := '';
- try
- ExpandedGroup := ExpandConst('{group}');
- except
- { Yep, expanding "group" might fail with an exception }
- ExpandedGroup := '';
- end;
- if SetupHeader.CompiledCodeText <> '' then
- PackCustomMessagesIntoString(CustomMessagesStr);
- { Record [Code] even if empty to 'overwrite' old versions }
- UninstLog.Add(utCompiledCode, [PackCompiledCodeTextIntoString(SetupHeader.CompiledCodeText),
- LeadBytesStr, ExpandedApp, ExpandedGroup, WizardGroupValue,
- ExpandConst('{language}'), CustomMessagesStr], SetupBinVersion {$IFDEF WIN64} or $80000000 {$ENDIF});
- end;
- procedure RegisterUninstallInfo(const UninstLog: TUninstallLog; const UninstallRegKeyBaseName: String;
- const AfterInstallFilesSize: Int64);
- { Stores uninstall information in the Registry so that the program can be
- uninstalled through the Control Panel Add/Remove Programs applet. }
- const
- AdminInstallModeNames: array [Boolean] of String =
- ('non administrative', 'administrative');
- BitInstallModeNames: array [Boolean] of String =
- ('32-bit', '64-bit');
- var
- RegView, OppositeRegView: TRegView;
- RegViewIs64Bit, OppositeRegViewIs64Bit: Boolean;
- RootKey, OppositeRootKey: HKEY;
- RootKeyIsHKLM, OppositeRootKeyIsHKLM: Boolean;
- SubkeyName: String;
- procedure SetStringValue(const K: HKEY; const ValueName: PChar;
- const Data: String);
- begin
- const ErrorCode = DWORD(RegSetValueEx(K, ValueName, 0, REG_SZ, PChar(Data),
- (ULength(Data)+1)*SizeOf(Data[1])));
- if ErrorCode <> ERROR_SUCCESS then
- RegError(reRegSetValueEx, RootKey, SubkeyName, ErrorCode);
- end;
- procedure SetStringValueUnlessEmpty(const K: HKEY; const ValueName: PChar;
- const Data: String);
- begin
- if Data <> '' then
- SetStringValue(K, ValueName, Data);
- end;
- procedure SetDWordValue(const K: HKEY; const ValueName: PChar;
- const Data: DWord);
- begin
- const ErrorCode = DWORD(RegSetValueEx(K, ValueName, 0, REG_DWORD, @Data,
- SizeOf(Data)));
- if ErrorCode <> ERROR_SUCCESS then
- RegError(reRegSetValueEx, RootKey, SubkeyName, ErrorCode);
- end;
- function GetInstallDateString: String;
- var
- ST: TSystemTime;
- begin
- GetLocalTime(ST);
- Result := Format('%.4u%.2u%.2u', [ST.wYear, ST.wMonth, ST.wDay]);
- end;
- function ExtractMajorMinorVersion(Version: String; var Major, Minor: Cardinal): Boolean;
- var
- P, I: Integer;
- begin
- P := Pos('.', Version);
- if P <> 0 then begin
- Val(Copy(Version, 1, P-1), Major, I);
- if I = 0 then begin
- Delete(Version, 1, P);
- P := Pos('.', Version);
- if P <> 0 then
- Val(Copy(Version, 1, P-1), Minor, I)
- else
- Val(Version, Minor, I);
- end;
- end else begin
- Val(Version, Major, I);
- Minor := 0;
- end;
- Result := I = 0;
- end;
- { Also see Main.pas }
- function ExistingInstallationAt(const RegView: TRegView; const RootKey: HKEY): Boolean;
- var
- K: HKEY;
- begin
- if RegOpenKeyExView(RegView, RootKey, PChar(SubkeyName), 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
- Result := True;
- RegCloseKey(K);
- end else
- Result := False;
- end;
- function ApplyDisplayNameMarks(const DisplayName: String;
- const ExistingAtOppositeAdminInstallMode, ExistingAtOpposite64BitInstallMode: Boolean): String;
- const
- UninstallDisplayNameMarksUser: array [Boolean] of TSetupMessageId =
- (msgUninstallDisplayNameMarkCurrentUser, msgUninstallDisplayNameMarkAllUsers);
- UninstallDisplayNameMarksBits: array [Boolean] of TSetupMessageId =
- (msgUninstallDisplayNameMark32Bit, msgUninstallDisplayNameMark64Bit);
- begin
- if ExistingAtOppositeAdminInstallMode and ExistingAtOpposite64BitInstallMode then
- Result := FmtSetupMessage(msgUninstallDisplayNameMarks,
- [DisplayName, SetupMessages[UninstallDisplayNameMarksUser[RootKeyIsHKLM]],
- SetupMessages[UninstallDisplayNameMarksBits[RegViewIs64Bit]]])
- else if ExistingAtOppositeAdminInstallMode then
- Result := FmtSetupMessage(msgUninstallDisplayNameMark,
- [DisplayName, SetupMessages[UninstallDisplayNameMarksUser[RootKeyIsHKLM]]])
- else
- Result := FmtSetupMessage(msgUninstallDisplayNameMark,
- [DisplayName, SetupMessages[UninstallDisplayNameMarksBits[RegViewIs64Bit]]]);
- end;
- procedure HandleDuplicateDisplayNames(var DisplayName: String);
- begin
- { Check opposite administrative install mode. }
- var ExistingAtOppositeAdminInstallMode := ExistingInstallationAt(RegView, OppositeRootKey);
- if RootKeyIsHKLM or not IsWin64 then begin
- { 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. }
- LogFmt('Detected previous %s install? %s',
- [AdminInstallModeNames[OppositeRootKeyIsHKLM {always False}], SYesNo[ExistingAtOppositeAdminInstallMode]])
- end else begin
- { Opposite (HKLM) is not shared for 32-bit and 64-bit so log bitness. }
- LogFmt('Detected previous %s %s install? %s',
- [AdminInstallModeNames[OppositeRootKeyIsHKLM {always True}], BitInstallModeNames[RegViewIs64Bit], SYesNo[ExistingAtOppositeAdminInstallMode]]);
- end;
- var ExistingAtOpposite64BitInstallMode: Boolean;
- if IsWin64 then begin
- { Check opposite 32-bit or 64-bit install mode. }
- if RootKeyIsHKLM then begin
- { 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
- since HKCU is shared for 32-bit and 64-bit mode and we already checked HKCU above. }
- ExistingAtOpposite64BitInstallMode := ExistingInstallationAt(OppositeRegView, RootKey {always HKLM});
- LogFmt('Detected previous %s %s install? %s',
- [AdminInstallModeNames[RootKeyIsHKLM {always True}], BitInstallModeNames[OppositeRegViewIs64Bit], SYesNo[ExistingAtOpposite64BitInstallMode]]);
- end else begin
- { 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
- 64-bit install mode since we haven't already done that. }
- ExistingAtOpposite64BitInstallMode := ExistingInstallationAt(OppositeRegView, OppositeRootKey {always HKLM});
- if ExistingAtOpposite64BitInstallMode then
- ExistingAtOppositeAdminInstallMode := True;
- LogFmt('Detected previous %s %s install? %s',
- [AdminInstallModeNames[OppositeRootKeyIsHKLM {always True}], BitInstallModeNames[OppositeRegViewIs64Bit], SYesNo[ExistingAtOpposite64BitInstallMode]]);
- end;
- end else
- ExistingAtOpposite64BitInstallMode := False;
-
- { Mark new display name if needed. Note: currently we don't attempt to mark existing display names as well. }
- if ExistingAtOppositeAdminInstallMode or ExistingAtOpposite64BitInstallMode then begin
- DisplayName := ApplyDisplayNameMarks(DisplayName, ExistingAtOppositeAdminInstallMode,
- ExistingAtOpposite64BitInstallMode);
- LogFmt('Marked uninstall display name to avoid duplicate entries. New display name: %s', [DisplayName]);
- end;
- end;
- var
- H2: HKEY;
- Z: String;
- EstimatedSize: Int64;
- begin
- RegView := InstallDefaultRegView;
- RegViewIs64Bit := RegView in RegViews64Bit;
- if RegViewIs64Bit then
- OppositeRegView := rv32Bit
- else
- OppositeRegView := rv64Bit;
- OppositeRegViewIs64Bit := not RegViewIs64Bit;
- RootKey := InstallModeRootKey;
- RootKeyIsHKLM := RootKey = HKEY_LOCAL_MACHINE;
- if RootKeyIsHKLM then
- OppositeRootKey := HKEY_CURRENT_USER
- else
- OppositeRootKey := HKEY_LOCAL_MACHINE;
- OppositeRootKeyIsHKLM := not RootKeyIsHKLM;
- SubkeyName := GetUninstallRegSubkeyName(UninstallRegKeyBaseName);
- if ExistingInstallationAt(RegView, RootKey) then begin
- if RootKeyIsHKLM then begin
- { HKLM is not shared for 32-bit and 64-bit so log bitness. }
- LogFmt('Deleting uninstall key left over from previous %s %s install.',
- [AdminInstallModeNames[RootKeyIsHKLM {always True}], BitInstallModeNames[RegViewIs64Bit]]);
- end else begin
- { HKCU is shared for 32-bit and 64-bit so don't log bitness. }
- LogFmt('Deleting uninstall key left over from previous %s install.',
- [AdminInstallModeNames[RootKeyIsHKLM {always False}]])
- end;
- RegDeleteKeyIncludingSubkeys(RegView, RootKey, PChar(SubkeyName));
- end;
- LogFmt('Creating new uninstall key: %s\%s', [GetRegRootKeyName(RootKey), SubkeyName]);
- { Create uninstall key }
- const ErrorCode = DWORD(RegCreateKeyExView(RegView, RootKey, PChar(SubkeyName),
- 0, nil, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nil, H2, nil));
- if ErrorCode <> ERROR_SUCCESS then
- RegError(reRegCreateKeyEx, RootKey, SubkeyName, ErrorCode);
-
- try
- Log('Writing uninstall key values.');
-
- { do not localize or change any of the following strings }
- SetStringValue(H2, 'Inno Setup: Setup Version', SetupVersion);
- if shCreateAppDir in SetupHeader.Options then
- Z := WizardDirValue
- else
- Z := '';
- SetStringValue(H2, 'Inno Setup: App Path', Z);
- SetStringValueUnlessEmpty(H2, 'InstallLocation', AddBackslash(Z));
- SetStringValue(H2, 'Inno Setup: Icon Group', WizardGroupValue);
- if WizardNoIcons then
- SetDWordValue(H2, 'Inno Setup: No Icons', 1);
- SetStringValue(H2, 'Inno Setup: User', GetUserNameString);
- if WizardSetupType <> nil then begin
- SetStringValue(H2, 'Inno Setup: Setup Type', WizardSetupType.Name);
- SetStringValue(H2, 'Inno Setup: Selected Components', StringsToCommaString(WizardComponents));
- SetStringValue(H2, 'Inno Setup: Deselected Components', StringsToCommaString(WizardDeselectedComponents));
- end;
- if HasTasks then begin
- SetStringValue(H2, 'Inno Setup: Selected Tasks', StringsToCommaString(WizardTasks));
- SetStringValue(H2, 'Inno Setup: Deselected Tasks', StringsToCommaString(WizardDeselectedTasks));
- end;
- if shUserInfoPage in SetupHeader.Options then begin
- SetStringValue(H2, 'Inno Setup: User Info: Name', WizardUserInfoName);
- SetStringValue(H2, 'Inno Setup: User Info: Organization', WizardUserInfoOrg);
- SetStringValue(H2, 'Inno Setup: User Info: Serial', WizardUserInfoSerial);
- end;
- SetStringValue(H2, 'Inno Setup: Language', PSetupLanguageEntry(Entries[seLanguage][ActiveLanguage]).Name);
- if SetupHeader.UninstallDisplayName <> '' then
- Z := ExpandConst(SetupHeader.UninstallDisplayName)
- else
- Z := ExpandedAppVerName;
- HandleDuplicateDisplayNames(Z);
- { For the entry to appear in ARP, DisplayName cannot exceed 259 characters
- on Windows 2000 and later. }
- SetStringValue(H2, 'DisplayName', Copy(Z, 1, 259));
- SetStringValueUnlessEmpty(H2, 'DisplayIcon', ExpandConst(SetupHeader.UninstallDisplayIcon));
- var ExtraUninstallString: String;
- if shUninstallLogging in SetupHeader.Options then
- ExtraUninstallString := ' /LOG'
- else
- ExtraUninstallString := '';
- SetStringValue(H2, 'UninstallString', '"' + UninstallExeFilename + '"' + ExtraUninstallString);
- SetStringValue(H2, 'QuietUninstallString', '"' + UninstallExeFilename + '" /SILENT' + ExtraUninstallString);
- SetStringValueUnlessEmpty(H2, 'DisplayVersion', ExpandConst(SetupHeader.AppVersion));
- SetStringValueUnlessEmpty(H2, 'Publisher', ExpandConst(SetupHeader.AppPublisher));
- SetStringValueUnlessEmpty(H2, 'URLInfoAbout', ExpandConst(SetupHeader.AppPublisherURL));
- SetStringValueUnlessEmpty(H2, 'HelpTelephone', ExpandConst(SetupHeader.AppSupportPhone));
- SetStringValueUnlessEmpty(H2, 'HelpLink', ExpandConst(SetupHeader.AppSupportURL));
- SetStringValueUnlessEmpty(H2, 'URLUpdateInfo', ExpandConst(SetupHeader.AppUpdatesURL));
- SetStringValueUnlessEmpty(H2, 'Readme', ExpandConst(SetupHeader.AppReadmeFile));
- SetStringValueUnlessEmpty(H2, 'Contact', ExpandConst(SetupHeader.AppContact));
- SetStringValueUnlessEmpty(H2, 'Comments', ExpandConst(SetupHeader.AppComments));
- Z := ExpandConst(SetupHeader.AppModifyPath);
- if Z <> '' then
- SetStringValue(H2, 'ModifyPath', Z)
- else
- SetDWordValue(H2, 'NoModify', 1);
- SetDWordValue(H2, 'NoRepair', 1);
- SetStringValue(H2, 'InstallDate', GetInstallDateString);
- var MajorVersion, MinorVersion: Cardinal;
- if ExtractMajorMinorVersion(ExpandConst(SetupHeader.AppVersion), MajorVersion, MinorVersion) then begin
- { Originally MSDN said to write to Major/MinorVersion, now it says to write to VersionMajor/Minor. So write to both. }
- SetDWordValue(H2, 'MajorVersion', MajorVersion);
- SetDWordValue(H2, 'MinorVersion', MinorVersion);
- SetDWordValue(H2, 'VersionMajor', MajorVersion);
- SetDWordValue(H2, 'VersionMinor', MinorVersion);
- end;
- { Note: Windows 7 (and later?) doesn't automatically calculate sizes so set EstimatedSize ourselves. }
- if SetupHeader.UninstallDisplaySize = 0 then begin
- { Estimate the size by taking the size of all files and adding any ExtraDiskSpaceRequired. }
- EstimatedSize := AfterInstallFilesSize + SetupHeader.ExtraDiskSpaceRequired;
- for var I := 0 to Entries[seComponent].Count-1 do begin
- with PSetupComponentEntry(Entries[seComponent][I])^ do begin
- if ShouldProcessEntry(WizardComponents, nil, Name, '', Languages, '') then
- Inc(EstimatedSize, ExtraDiskSpaceRequired);
- end;
- end;
- end else
- EstimatedSize := SetupHeader.UninstallDisplaySize;
- { ARP on Windows 7 without SP1 only pays attention to the lower 6 bytes of EstimatedSize and
- throws away the rest. For example putting in $4000001 (=4GB + 1KB) displays as 1 KB.
- So we need to check for this. }
- if (Hi(NTServicePackLevel) > 0) or IsWindows8 or (EstimatedSize <= High(Cardinal)) then begin
- EstimatedSize := EstimatedSize div 1024;
- SetDWordValue(H2, 'EstimatedSize', DWORD(EstimatedSize));
- end;
- { Also see SetPreviousData in ScriptFunc.pas }
- if CodeRunner <> nil then begin
- try
- CodeRunner.RunProcedures('RegisterPreviousData', [Integer(H2)], False);
- except
- Log('RegisterPreviousData raised an exception.');
- Application.HandleException(nil);
- end;
- end;
- finally
- RegCloseKey(H2);
- end;
- UninstLog.AddReg(utRegDeleteEntireKey, RegView, RootKey,
- [SubkeyName]);
- end;
- type
- TMakeDirFlags = set of (mdNoUninstall, mdAlwaysUninstall, mdDeleteAfterInstall,
- mdNotifyChange);
- function MakeDir(const UninstLog: TUninstallLog; Dir: String;
- const Flags: TMakeDirFlags = []): Boolean;
- { Returns True if a new directory was created. Also see ForceDirectories
- for similar code (but different return value). }
- var
- ErrorCode: DWORD;
- begin
- Result := False;
- Dir := RemoveBackslashUnlessRoot(PathExpand(Dir));
- { If we're at the root of a drive or network share, then there's nothing to
- do. }
- if PathExtractName(Dir) = '' then
- Exit;
- if DirExists(Dir) then begin
- if not(mdAlwaysUninstall in Flags) then
- Exit;
- end
- else begin
- MakeDir(UninstLog, PathExtractDir(Dir), Flags - [mdAlwaysUninstall]);
- LogFmt('Creating directory: %s', [Dir]);
- if not CreateDirectory(PChar(Dir), nil) then begin
- ErrorCode := GetLastError;
- raise Exception.Create(FmtSetupMessage(msgLastErrorMessage,
- [FmtSetupMessage1(msgErrorCreatingDir, PathConvertSuperToNormal(Dir)),
- IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
- end;
- Result := True;
- if mdNotifyChange in Flags then begin
- ShellChangeNotifyPath(SHCNE_MKDIR, Dir, False);
- ShellChangeNotifyPath(SHCNE_UPDATEDIR, PathExtractDir(Dir), True);
- end;
- end;
- if mdDeleteAfterInstall in Flags then
- DeleteDirsAfterInstallList.Add(Dir)
- else begin
- if not(mdNoUninstall in Flags) then begin
- var UninstFlags: TUninstallRecExtraData := utDeleteDirOrFiles_IsDir;
- if IsCurrentProcess64Bit then { Post-ApplyPathRedirRules we should check IsCurrentProcess64Bit and not the original InstallDefault64Bit }
- UninstFlags := UninstFlags or utDeleteDirOrFiles_Is64Bit;
- if mdNotifyChange in Flags then
- UninstFlags := UninstFlags or utDeleteDirOrFiles_CallChangeNotify;
- UninstLog.Add(utDeleteDirOrFiles, [Dir], UninstFlags);
- end;
- end;
- end;
- procedure ApplyNTFSCompression(const Filename: String; const FilenameIsDirectory, Compress: Boolean);
- const
- SSet: array [Boolean] of String = ('Setting', 'Unsetting');
- SFileDir: array [Boolean] of String = ('file', 'directory');
- begin
- LogFmt('%s NTFS compression on %s: %s', [SSet[Compress], SFileDir[FilenameIsDirectory], Filename]);
- if not SetNTFSCompression(Filename, Compress) then
- LogFmt('Failed to set NTFS compression state (%d).', [GetLastError]);
- end;
- procedure CreateDirs(const UninstLog: TUninstallLog);
- { Creates the application's directories }
- procedure ApplyPermissions(const Filename: String;
- const PermsEntry: Integer);
- var
- P: PSetupPermissionEntry;
- begin
- if PermsEntry <> -1 then begin
- LogFmt('Setting permissions on directory: %s', [Filename]);
- P := Entries[sePermission][PermsEntry];
- if not GrantPermissionOnFile(Filename,
- TGrantPermissionEntry(Pointer(P.Permissions)^),
- Length(P.Permissions) div SizeOf(TGrantPermissionEntry)) then
- LogFmt('Failed to set permissions on directory (%d).', [GetLastError]);
- end;
- end;
- begin
- { Create main application directory }
- MakeDir(UninstLog, ApplyPathRedirRules(InstallDefault64Bit, WizardDirValue, tpCurrent));
- { Create the rest of the directories, if any }
- for var CurDirNumber := 0 to Entries[seDir].Count-1 do
- with PSetupDirEntry(Entries[seDir][CurDirNumber])^ do begin
- if ShouldProcessEntry(WizardComponents, WizardTasks, Components, Tasks, Languages, Check) then begin
- DebugNotifyEntry(seDir, CurDirNumber);
- NotifyBeforeInstallEntry(BeforeInstall);
- const Path = RemoveBackslashUnlessRoot(ApplyPathRedirRules(
- InstallDefault64Bit, ExpandConst(DirName), tpCurrent));
- var Flags: TMakeDirFlags := [];
- if doUninsNeverUninstall in Options then Include(Flags, mdNoUninstall);
- if doDeleteAfterInstall in Options then Include(Flags, mdDeleteAfterInstall);
- if doUninsAlwaysUninstall in Options then Include(Flags, mdAlwaysUninstall);
- MakeDir(UninstLog, Path, Flags);
- AddAttributesToFile(Path, Attribs);
- ApplyPermissions(Path, PermissionsEntry);
- if (doSetNTFSCompression in Options) or (doUnsetNTFSCompression in Options) then
- ApplyNTFSCompression(Path, True, doSetNTFSCompression in Options);
- NotifyAfterInstallEntry(AfterInstall);
- end;
- end;
- end;
- procedure BindUninstallMsgDataToExe(const ExpandedAppId: String; const F: TFile);
- var
- UniqueValue: TSHA256Digest;
- UninstallerMsgTail: TUninstallerMsgTail;
- begin
- F.SeekToEnd;
- { First append the hash of AppId so that unins*.exe files from different
- applications won't have the same file hash. This is done to combat broken
- anti-spyware programs that catch all unins*.exe files with certain hash
- sums just because some piece of spyware was deployed with Inno Setup and
- had the unins*.exe file in its directory. }
- UniqueValue := GetSHA256OfUnicodeString(ExpandedAppId);
- F.WriteBuffer(UniqueValue, SizeOf(UniqueValue));
- UninstallerMsgTail.ID := UninstallerMsgTailID;
- UninstallerMsgTail.Offset := F.Position;
- WriteMsgData(F);
- F.WriteBuffer(UninstallerMsgTail, SizeOf(UninstallerMsgTail));
- end;
- procedure DoHandleFailedDeleteOrMoveFileTry(const CurFile: PSetupFileEntry;
- const Func, TempFile, DestFile: String;
- const LastError: DWORD; var RetriesLeft: Integer; var LastOperation: String;
- var NeedsRestart, ReplaceOnRestart: Boolean;
- var NextAction: TFileOperationFailingNextAction);
- begin
- { Automatically retry. Wait with replace on restart until no
- retries left, unless we already know we're going to restart. }
- if ((RetriesLeft = 0) or NeedsRestart) and
- (foRestartReplace in CurFile^.Options) and IsAdmin then begin
- LogFmt('%s: The existing file appears to be in use (%d). ' +
- 'Will replace on restart.', [Func, LastError]);
- LastOperation := SetupMessages[msgErrorRestartReplace];
- NeedsRestart := True;
- RestartReplace(TempFile, DestFile);
- ReplaceOnRestart := True;
- NextAction := naStopAndSucceed;
- end else if RetriesLeft > 0 then begin
- LogFmt('%s: The existing file appears to be in use (%d). ' +
- 'Retrying.', [Func, LastError]);
- Dec(RetriesLeft);
- Sleep(1000);
- ProcessEvents;
- NextAction := naRetry;
- end;
- end;
- type
- TOverwriteAll = (oaUnknown, oaOverwrite, oaKeep);
- procedure ProcessFileEntry(const UninstLog: TUninstallLog; const ExpandedAppId: String;
- const RegisterFilesList: TList; const CurFile: PSetupFileEntry;
- const Is64Bit: Boolean; AExternalSourceFile, ADestFile: String;
- const FileLocationFilenames: TStringList; const AExternalSize: Int64;
- var ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll: TOverwriteAll;
- var WarnedPerUserFonts: Boolean; const AExternalFileDate: PFileTime;
- var UninstallTempExeFilename: String; var UninstallExeCreated: TUninstallExeCreated);
- { Not external: AExternalSourceFile and ADestFile should be empty strings,
- FileLocationFilenames should be set, AExternalSize is unused,
- AExternalFileDate should not be set
- External : Opposite except AExternalFileDate still not set
- Ext. Archive: Same as external except AExternalFileDate set and
- AExternalSourceFile should be set to ArchiveFindHandle as a string
- Ext. Downl. : Same as external except
- AExternalSourceFile should be set to an URL }
- procedure InstallFont(const Filename, FontName: String;
- const PerUserFont, AddToFontTableNow: Boolean; var WarnedPerUserFonts: Boolean);
- begin
- const NormalFilename = PathConvertSuperToNormal(Filename);
- if PerUserFont and not WindowsVersionAtLeast(10, 0, 17134) then begin
- { Per-user fonts require Windows 10 Version 1803 (10.0.17134) or newer. }
- if not WarnedPerUserFonts then begin
- Log('Failed to set value in Fonts registry key: per-user fonts are not supported by this version of Windows.');
- WarnedPerUserFonts := True;
- end;
- end else begin
- { 64-bit Windows note: The Fonts key is evidently exempt from registry
- redirection. When a 32-bit app writes to the Fonts key, it's the main
- 64-bit key that is modified. (There is actually a Fonts key under
- Wow6432Node but it appears it's never used or updated.)
- Also: We don't bother with any FS redirection stuff here. I'm not sure
- it's safe to disable FS redirection when calling AddFontResource, or
- if it would even work. Users should be installing their fonts to the
- Fonts directory instead of the System directory anyway. }
- var RootKey: HKEY;
- if PerUserFont then
- RootKey := HKEY_CURRENT_USER
- else
- RootKey := HKEY_LOCAL_MACHINE;
- var K: HKEY;
- if RegOpenKeyExView(rvDefault, RootKey, 'Software\Microsoft\Windows NT\CurrentVersion\Fonts', 0,
- KEY_SET_VALUE, K) = ERROR_SUCCESS then begin
- if RegSetValueEx(K, PChar(FontName), 0, REG_SZ, PChar(NormalFilename),
- (ULength(NormalFilename)+1)*SizeOf(NormalFilename[1])) <> ERROR_SUCCESS then
- Log('Failed to set value in Fonts registry key.');
- RegCloseKey(K);
- end
- else
- Log('Failed to open Fonts registry key.');
- end;
-
- if AddToFontTableNow then begin
- repeat
- { Note: AddFontResource doesn't set the thread's last error code }
- if AddFontResource(PChar(NormalFilename)) <> 0 then begin
- SendNotifyMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
- Break;
- end;
- until AbortRetryIgnoreTaskDialogMsgBox(
- AddPeriod(FmtSetupMessage1(msgErrorFunctionFailedNoCode, 'AddFontResource')),
- [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgAbortRetryIgnoreIgnore], SetupMessages[msgAbortRetryIgnoreCancel]]);
- end;
- end;
- procedure SetFileLocationFilename(const LocationEntry: Integer;
- Filename: String);
- begin
- const LowercaseFilename = PathLowercase(Filename);
- const Hash = GetCRC32(LowercaseFilename[1], ULength(LowercaseFilename)*SizeOf(LowercaseFilename[1]));
- { If Filename was already associated with another LocationEntry,
- disassociate it. If we *don't* do this, then this script won't
- produce the expected result:
- [Files]
- Source: "fileA"; DestName: "file2"
- Source: "fileB"; DestName: "file2"
- Source: "fileA"; DestName: "file1"
- 1. It extracts fileA under the name "file2"
- 2. It extracts fileB under the name "file2"
- 3. It copies file2 to file1, thinking a copy of fileA was still
- stored in file2.
- }
- for var I := 0 to FileLocationFilenames.Count-1 do
- if (Integer(FileLocationFilenames.Objects[I]) = Hash) and
- (PathLowercase(FileLocationFilenames[I]) = LowercaseFilename) then begin
- FileLocationFilenames[I] := '';
- FileLocationFilenames.Objects[I] := nil;
- Break;
- end;
- FileLocationFilenames[LocationEntry] := Filename;
- FileLocationFilenames.Objects[LocationEntry] := Pointer(Hash);
- end;
- procedure ApplyPermissions(const Filename: String; const PermsEntry: Integer);
- var
- P: PSetupPermissionEntry;
- begin
- if PermsEntry <> -1 then begin
- LogFmt('Setting permissions on file: %s', [Filename]);
- const Attr = GetFileAttributes(PChar(Filename));
- if Attr = INVALID_FILE_ATTRIBUTES then
- LogWithLastError('Cannot set permissions; failed to read file attributes.')
- else if Attr and FILE_ATTRIBUTE_DIRECTORY <> 0 then
- Log('Cannot set permissions; a directory exists at that path.')
- else begin
- P := Entries[sePermission][PermsEntry];
- if not GrantPermissionOnFile(Filename,
- TGrantPermissionEntry(Pointer(P.Permissions)^),
- Length(P.Permissions) div SizeOf(TGrantPermissionEntry)) then
- LogFmt('Failed to set permissions on file (%d).', [GetLastError]);
- end;
- end;
- end;
- function AskOverwrite(const DestFile, Instruction, Caption: string; const ButtonLabels: array of String;
- const VerificationText: String; const Typ: TMsgBoxType; const Default, Overwrite: Integer;
- var OverwriteAll: TOverwriteAll): Boolean;
- var
- VerificationFlagChecked: BOOL;
- begin
- if OverwriteAll = oaKeep then
- Result := False { The user already said to keep (=not overwrite) all }
- else begin
- Result := LoggedTaskDialogMsgBox('', Instruction, DestFile + SNewLine2 + Caption, '',
- Typ, MB_YESNO, ButtonLabels, 0, True, Default, VerificationText, @VerificationFlagChecked) = Overwrite;
- if VerificationFlagChecked then begin
- if Result then
- OverwriteAll := oaOverwrite
- else
- OverwriteAll := oaKeep;
- end;
- end;
- end;
- var
- ProgressUpdated: Boolean;
- LastOperation: String;
- CurFileLocation: PSetupFileLocationEntry;
- SourceFile, DestFile, TempFile, FontFilename: String;
- DestFileExists, DestFileExistedBefore, CheckedDestFileExistedBefore,
- TempFileLeftOver, AllowFileToBeDuplicated, ReplaceOnRestart: Boolean;
- Failed: String;
- CurFileVersionInfoValid: Boolean;
- CurFileVersionInfo, ExistingVersionInfo: TFileVersionNumbers;
- CurFileDateValid, ExistingFileDateValid: Boolean;
- IsProtectedFile, AllowTimeStampComparison: Boolean;
- DeleteFlags: TUninstallRecExtraData;
- CurFileDate, ExistingFileDate: TFileTime;
- RegisterRec: PRegisterFilesListRec;
- DestF, SourceF: TFile;
- Flags: TMakeDirFlags;
- Overwrite, PerUserFont: Boolean;
- label Retry, Skip;
- begin
- Log('-- File entry --');
- CheckedDestFileExistedBefore := False;
- DestFileExistedBefore := False; { prevent warning }
- if CurFile^.LocationEntry <> -1 then
- CurFileLocation := PSetupFileLocationEntry(Entries[seFileLocation][CurFile^.LocationEntry])
- else
- CurFileLocation := nil;
- Retry:
- DestFile := '';
- TempFile := '';
- TempFileLeftOver := False;
- ProgressUpdated := False;
- var PreviousProgress := CurProgress;
- LastOperation := '';
- Failed := '';
- try
- try
- ReplaceOnRestart := False;
- DeleteFlags := 0;
- if IsCurrentProcess64Bit then { Post-ApplyPathRedirRules we should check IsCurrentProcess64Bit and not Is64Bit }
- DeleteFlags := DeleteFlags or utDeleteFile_Is64Bit;
- if foRegisterServer in CurFile^.Options then begin
- DeleteFlags := DeleteFlags or utDeleteFile_RegisteredServer;
- if IsCurrentProcess64Bit <> Is64Bit then
- DeleteFlags := DeleteFlags or utDeleteFile_RegisteredWithOppositeBitness;
- end;
- if foRegisterTypeLib in CurFile^.Options then
- DeleteFlags := DeleteFlags or utDeleteFile_RegisteredTypeLib;
- if foUninsRestartDelete in CurFile^.Options then
- DeleteFlags := DeleteFlags or utDeleteFile_RestartDelete;
- if foUninsRemoveReadOnly in CurFile^.Options then
- DeleteFlags := DeleteFlags or utDeleteFile_RemoveReadOnly;
- if foGacInstall in CurFile^.Options then
- DeleteFlags := DeleteFlags or utDeleteFile_GacInstalled;
- FontFilename := '';
- { Determine the destination filename }
- try
- case CurFile^.FileType of
- ftUninstExe: DestFile := UninstallExeFilename;
- else
- if ADestFile = '' then
- DestFile := ApplyPathRedirRules(Is64Bit, ExpandConst(CurFile^.DestName), tpCurrent)
- else
- DestFile := ADestFile;
- end;
- except
- { If an exception occurred, reset DestFile back to an empty string
- so the error message doesn't show an unexpanded name }
- DestFile := '';
- raise;
- end;
- { Update the status and filename labels }
- if foDownload in CurFile^.Options then
- SetStatusLabelText(SetupMessages[msgStatusDownloadFiles], False)
- else
- SetStatusLabelText(SetupMessages[msgStatusExtractFiles], False);
- SetFilenameLabelText(PathConvertSuperToNormal(DestFile), True);
- LogFmt('Dest filename: %s', [DestFile]);
- if Is64Bit <> InstallDefault64Bit then begin
- if Is64Bit then
- Log('Non-default bitness: 64-bit')
- else
- Log('Non-default bitness: 32-bit');
- end;
- { See if it's a protected system file. }
- if IsProtectedSystemFile(DestFile) then begin
- Log('Dest file is protected by Windows File Protection.');
- IsProtectedFile := (CurFile^.FileType = ftUserFile);
- end else
- IsProtectedFile := False;
- DestFileExists := NewFileExists(DestFile);
- if not CheckedDestFileExistedBefore then begin
- DestFileExistedBefore := DestFileExists;
- CheckedDestFileExistedBefore := True;
- end;
- if DestFileExistedBefore then
- DeleteFlags := DeleteFlags or utDeleteFile_ExistedBeforeInstall;
- var CurFileDateDidRead := True; { Set to False later if needed }
- if Assigned(CurFileLocation) then begin
- { Not an "external" file }
- if CurFileLocation^.TimeStamp.HasTime then begin
- if floTimeStampInUTC in CurFileLocation^.Flags then
- CurFileDate := CurFileLocation^.TimeStamp
- else
- LocalFileTimeToFileTime(CurFileLocation^.TimeStamp, CurFileDate);
- CurFileDateValid := True;
- end else begin
- CurFileDateValid := False;
- CurFileDateDidRead := False;
- end;
- end else begin
- { An "external" file }
- if Assigned(AExternalFileDate) then begin
- CurFileDate := AExternalFileDate^;
- CurFileDateValid := CurFileDate.HasTime;
- end else if not(foDownload in CurFile^.Options) then
- CurFileDateValid := GetFileDateTime(AExternalSourceFile, CurFileDate)
- else begin
- CurFileDateValid := False;
- CurFileDateDidRead := False;
- end;
- end;
- if CurFileDateValid then
- LogFmt('Time stamp of our file: %s', [FileTimeToStr(CurFileDate)])
- else if CurFileDateDidRead then
- Log('Time stamp of our file: (failed to read)');
- if DestFileExists then begin
- Log('Dest file exists.');
- if foOnlyIfDoesntExist in CurFile^.Options then begin
- Log('Skipping due to "onlyifdoesntexist" flag.');
- goto Skip;
- end;
- LastOperation := SetupMessages[msgErrorReadingExistingDest];
- ExistingFileDateValid := GetFileDateTime(DestFile, ExistingFileDate);
- if ExistingFileDateValid then
- LogFmt('Time stamp of existing file: %s', [FileTimeToStr(ExistingFileDate)])
- else
- Log('Time stamp of existing file: (failed to read)');
- { Compare version info }
- if not(foIgnoreVersion in CurFile^.Options) then begin
- AllowTimeStampComparison := False;
- { Read version info of file being installed }
- if foDownload in CurFile^.Options then
- InternalError('Unexpected Download flag');
- if foExtractArchive in CurFile^.Options then
- InternalError('Unexpected ExtractArchive flag');
- if Assigned(CurFileLocation) then begin
- CurFileVersionInfoValid := floVersionInfoValid in CurFileLocation^.Flags;
- CurFileVersionInfo.MS := CurFileLocation^.FileVersionMS;
- CurFileVersionInfo.LS := CurFileLocation^.FileVersionLS;
- end
- else
- CurFileVersionInfoValid := GetVersionNumbers(AExternalSourceFile, CurFileVersionInfo);
- if CurFileVersionInfoValid then
- LogFmt('Version of our file: %u.%u.%u.%u',
- [LongRec(CurFileVersionInfo.MS).Hi, LongRec(CurFileVersionInfo.MS).Lo,
- LongRec(CurFileVersionInfo.LS).Hi, LongRec(CurFileVersionInfo.LS).Lo])
- else
- Log('Version of our file: (none)');
- { Does the existing file have version info? }
- if GetVersionNumbers(DestFile, ExistingVersionInfo) then begin
- { If the file being installed has no version info, or the existing
- file is a newer version... }
- LogFmt('Version of existing file: %u.%u.%u.%u',
- [LongRec(ExistingVersionInfo.MS).Hi, LongRec(ExistingVersionInfo.MS).Lo,
- LongRec(ExistingVersionInfo.LS).Hi, LongRec(ExistingVersionInfo.LS).Lo]);
- if not CurFileVersionInfoValid or
- ((ExistingVersionInfo.MS > CurFileVersionInfo.MS) or
- ((ExistingVersionInfo.MS = CurFileVersionInfo.MS) and
- (ExistingVersionInfo.LS > CurFileVersionInfo.LS))) then begin
- { No version info, or existing file is newer, ask user what to do unless we shouldn't }
- if (foPromptIfOlder in CurFile^.Options) and not IsProtectedFile then begin
- if PromptIfOlderOverwriteAll <> oaOverwrite then begin
- Overwrite := AskOverwrite(PathConvertSuperToNormal(DestFile),
- SetupMessages[msgExistingFileNewerSelectAction], SetupMessages[msgExistingFileNewer2],
- [SetupMessages[msgExistingFileNewerKeepExisting], SetupMessages[msgExistingFileNewerOverwriteExisting]],
- SetupMessages[msgExistingFileNewerOverwriteOrKeepAll],
- mbError, IDYES, IDNO, PromptIfOlderOverwriteAll);
- if not Overwrite then begin
- Log('User opted not to overwrite the existing file. Skipping.');
- goto Skip;
- end;
- end;
- end else begin
- Log('Existing file is a newer version. Skipping.');
- goto Skip;
- end;
- end
- else begin
- { If the existing file and the file being installed are the same
- version... }
- if (ExistingVersionInfo.MS = CurFileVersionInfo.MS) and
- (ExistingVersionInfo.LS = CurFileVersionInfo.LS) and
- not(foOverwriteSameVersion in CurFile^.Options) then begin
- if foReplaceSameVersionIfContentsDiffer in CurFile^.Options then begin
- { Get the two files' SHA-256 hashes and compare them }
- var ExistingFileHash: TSHA256Digest;
- if TryToGetSHA256OfFile(DestFile, ExistingFileHash) then begin
- var CurFileHash: TSHA256Digest;
- if Assigned(CurFileLocation) then
- CurFileHash := CurFileLocation^.SHA256Sum
- else begin
- LastOperation := SetupMessages[msgErrorReadingSource];
- { This GetSHA256OfFile call could raise an exception, but
- it's very unlikely since we were already able to
- successfully read the file's version info. }
- CurFileHash := GetSHA256OfFile(AExternalSourceFile);
- LastOperation := SetupMessages[msgErrorReadingExistingDest];
- end;
- { If the two files' SHA-256 hashes are equal, skip the file }
- if SHA256DigestsEqual(ExistingFileHash, CurFileHash) then begin
- Log('Existing file''s SHA-256 hash matches our file. Skipping.');
- goto Skip;
- end;
- Log('Existing file''s SHA-256 hash is different from our file. Proceeding.');
- end
- else
- Log('Failed to read existing file''s SHA-256 hash. Proceeding.');
- end
- else begin
- { Skip the file or fall back to time stamp comparison }
- if not(foCompareTimeStamp in CurFile^.Options) then begin
- Log('Same version. Skipping.');
- goto Skip;
- end;
- AllowTimeStampComparison := True;
- end;
- end;
- end;
- end
- else begin
- Log('Version of existing file: (none)');
- { If neither the existing file nor our file have version info,
- allow time stamp comparison }
- if not CurFileVersionInfoValid then
- AllowTimeStampComparison := True;
- end;
- end
- else begin
- { When foIgnoreVersion is in Options, always allow time stamp
- comparison }
- AllowTimeStampComparison := True;
- end;
- { Fall back to comparing time stamps if needed }
- if AllowTimeStampComparison and
- (foCompareTimeStamp in CurFile^.Options) then begin
- if foDownload in CurFile^.Options then
- InternalError('Unexpected Download flag');
- if not CurFileDateValid or not ExistingFileDateValid then begin
- { If we failed to read one of the time stamps, do the safe thing
- and just skip the file }
- Log('Couldn''t read time stamp. Skipping.');
- goto Skip;
- end;
- if CompareFileTime(ExistingFileDate, CurFileDate) = 0 then begin
- { Same time stamp }
- Log('Same time stamp. Skipping.');
- goto Skip;
- end;
- if CompareFileTime(ExistingFileDate, CurFileDate) > 0 then begin
- { Existing file has a later time stamp, ask user what to do unless we shouldn't }
- if (foPromptIfOlder in CurFile^.Options) and not IsProtectedFile then begin
- if PromptIfOlderOverwriteAll <> oaOverwrite then begin
- Overwrite := AskOverwrite(PathConvertSuperToNormal(DestFile),
- SetupMessages[msgExistingFileNewerSelectAction], SetupMessages[msgExistingFileNewer2],
- [SetupMessages[msgExistingFileNewerKeepExisting], SetupMessages[msgExistingFileNewerOverwriteExisting]],
- SetupMessages[msgExistingFileNewerOverwriteOrKeepAll],
- mbError, IDYES, IDNO, PromptIfOlderOverwriteAll);
- if not Overwrite then begin
- Log('User opted not to overwrite the existing file. Skipping.');
- goto Skip;
- end;
- end;
- end else begin
- Log('Existing file has a later time stamp. Skipping.');
- goto Skip;
- end;
- end;
- end;
- LastOperation := '';
- { Don't attempt to replace an existing protected system file.
- (Do this *after* the version numbers of the new & existing files
- have been logged.) }
- if IsProtectedFile then begin
- Log('Existing file is protected by Windows File Protection. Skipping.');
- goto Skip;
- end;
- { If file already exists and foConfirmOverwrite is in Options, ask the user what to do }
- if foConfirmOverwrite in CurFile^.Options then begin
- if ConfirmOverwriteOverwriteAll <> oaOverwrite then begin
- Overwrite := AskOverwrite(PathConvertSuperToNormal(DestFile),
- SetupMessages[msgFileExistsSelectAction], SetupMessages[msgFileExists2],
- [SetupMessages[msgFileExistsOverwriteExisting], SetupMessages[msgFileExistsKeepExisting]],
- SetupMessages[msgFileExistsOverwriteOrKeepAll],
- mbConfirmation, IDNO, IDYES, ConfirmOverwriteOverwriteAll);
- if not Overwrite then begin
- Log('User opted not to overwrite the existing file. Skipping.');
- goto Skip;
- end;
- end;
- end;
- { Check if existing file is read-only }
- while True do begin
- var ExistingFileAttr := GetFileAttributes(PChar(DestFile));
- if (ExistingFileAttr <> INVALID_FILE_ATTRIBUTES) and
- (ExistingFileAttr and FILE_ATTRIBUTE_READONLY <> 0) then begin
- if not(foOverwriteReadOnly in CurFile^.Options) and
- AbortRetryIgnoreTaskDialogMsgBox(
- PathConvertSuperToNormal(DestFile) + SNewLine2 + SetupMessages[msgExistingFileReadOnly2],
- [SetupMessages[msgExistingFileReadOnlyRetry], SetupMessages[msgExistingFileReadOnlyKeepExisting], SetupMessages[msgAbortRetryIgnoreCancel]]) then begin
- Log('User opted not to strip the existing file''s read-only attribute. Skipping.');
- goto Skip;
- end;
- LastOperation := SetupMessages[msgErrorChangingAttr];
- if SetFileAttributes(PChar(DestFile), ExistingFileAttr and not FILE_ATTRIBUTE_READONLY) then
- Log('Stripped read-only attribute.')
- else
- Log('Failed to strip read-only attribute.');
- if foOverwriteReadOnly in CurFile^.Options then
- Break; { don't retry }
- end
- else
- Break;
- end;
- end
- else begin
- if (foOnlyIfDestFileExists in CurFile^.Options) and not DestFileExistedBefore then begin
- Log('Skipping due to "onlyifdestfileexists" flag.');
- goto Skip;
- end;
- end;
- Log('Installing the file.');
- { Locate source file }
- SourceFile := AExternalSourceFile; { Empty string if not external }
- { If the file is compressed in the setup package, has the same file
- already been copied somewhere else? If so, just make a duplicate of
- that file instead of extracting it over again. }
- if (SourceFile = '') and (FileLocationFilenames <> nil) and
- (FileLocationFilenames[CurFile^.LocationEntry] <> '') and
- NewFileExists(FileLocationFilenames[CurFile^.LocationEntry]) then
- SourceFile := FileLocationFilenames[CurFile^.LocationEntry];
- AllowFileToBeDuplicated := (SourceFile = '');
- { Download or extract or copy the file to a temporary file. Create the destination
- file's directory if it didn't already exist. }
- LastOperation := SetupMessages[msgErrorCreatingTemp];
- TempFile := GenerateUniqueName(PathExtractPath(DestFile), '.tmp');
- Flags := [];
- if foUninsNeverUninstall in CurFile^.Options then Include(Flags, mdNoUninstall);
- if foDeleteAfterInstall in CurFile^.Options then Include(Flags, mdDeleteAfterInstall);
- MakeDir(UninstLog, PathExtractDir(TempFile), Flags);
- DestF := TFile.Create(TempFile, fdCreateAlways, faReadWrite, fsNone);
- try
- TempFileLeftOver := True;
- try
- ProgressUpdated := True;
- LastOperation := SetupMessages[msgErrorReadingSource];
- if SourceFile = '' then begin
- { Decompress a file }
- FileExtractor.SeekTo(CurFileLocation^, InternalProgressProc);
- LastOperation := SetupMessages[msgErrorCopying];
- FileExtractor.DecompressFile(CurFileLocation^, DestF, InternalProgressProc,
- not (foDontVerifyChecksum in CurFile^.Options));
- end
- else if foExtractArchive in CurFile^.Options then begin
- { Extract a file from archive. Note: ISSigVerify for archive has
- already been handled by RecurseExternalArchiveCopyFiles. }
- LastOperation := SetupMessages[msgErrorExtracting];
- var MaxProgress := CurProgress;
- Inc(MaxProgress, AExternalSize);
- ArchiveFindExtract(TArchiveFindHandle(StrToUInt64(SourceFile)), DestF, ExternalProgressProc64, MaxProgress);
- end
- else if foDownload in CurFile^.Options then begin
- { Download a file with or without ISSigVerify. Note: estimate of
- extra .issig size has already been added to CurFile's ExternalSize. }
- LastOperation := SetupMessages[msgErrorDownloading];
- const DownloadUserName = ExpandConst(CurFile^.DownloadUserName);
- const DownloadPassword = ExpandConst(CurFile^.DownloadPassword);
- var MaxProgress := CurProgress;
- Inc(MaxProgress, AExternalSize);
- if CurFile^.Verification.Typ = fvISSig then begin
- const ISSigTempFile = TempFile + ISSigExt;
- const ISSigDestF = TFile.Create(ISSigTempFile, fdCreateAlways, faReadWrite, fsNone);
- try
- { Download the .issig file }
- const ISSigUrl = GetISSigUrl(SourceFile, ExpandConst(CurFile^.DownloadISSigSource));
- DownloadFile(ISSigUrl, DownloadUserName, DownloadPassword,
- ISSigDestF, NoVerification, '', JustProcessEventsProc64, 0, ProcessEvents);
- FreeAndNil(ISSigDestF);
- { Download and verify the actual file }
- DownloadFile(SourceFile, DownloadUserName, DownloadPassword,
- DestF, CurFile^.Verification, TempFile, ExternalProgressProc64, MaxProgress, ProcessEvents);
- finally
- ISSigDestF.Free;
- { Delete the .issig file }
- Windows.DeleteFile(PChar(ISSigTempFile));
- end;
- end else
- DownloadFile(SourceFile, DownloadUserName, DownloadPassword,
- DestF, CurFile^.Verification, '', ExternalProgressProc64, MaxProgress, ProcessEvents);
- end
- else begin
- { Copy a duplicated non-external file, or an external file }
- SourceF := TFile.Create(SourceFile, fdOpenExisting, faRead, fsRead);
- try
- LastOperation := SetupMessages[msgErrorCopying];
- if Assigned(CurFileLocation) then
- CopySourceFileToDestFile(SourceF, DestF, NoVerification,
- '', CurFileLocation^.OriginalSize)
- else
- CopySourceFileToDestFile(SourceF, DestF, CurFile^.Verification,
- SourceFile, AExternalSize);
- finally
- SourceF.Free;
- end;
- end;
- except
- { If an exception occurred, put progress meter back to where it was }
- ProgressUpdated := False;
- SetProgress(PreviousProgress);
- raise;
- end;
- { Set time/date stamp }
- if CurFileDateValid then
- SetFileTime(DestF.Handle, nil, nil, @CurFileDate);
- { If it's the uninstall program, bind the messages }
- if CurFile^.FileType = ftUninstExe then begin
- AllowFileToBeDuplicated := False;
- MarkExeHeader(DestF, SetupExeModeUninstaller);
- if not(shSignedUninstaller in SetupHeader.Options) and
- not DetachedUninstMsgFile then
- BindUninstallMsgDataToExe(ExpandedAppId, DestF);
- end;
- finally
- DestF.Free;
- end;
- { If it's a font, unregister the existing one to ensure that Windows
- 'notices' the file is being replaced, and to increase the chances
- of the file being unlocked/closed before we replace it. }
- if CurFile^.InstallFontName <> '' then begin
- LastOperation := '';
- FontFilename := ShortenFontFilename(DestFile);
- if DestFileExistedBefore then
- RemoveFontResource(PChar(FontFilename));
- end;
- { Delete existing version of file, if any. If it can't be deleted
- because it's in use and the "restartreplace" flag was specified
- on the entry, register it to be replaced when the system is
- restarted. Do retry deletion before doing this. }
- if DestFileExists and (CurFile^.FileType <> ftUninstExe) then begin
- LastOperation := SetupMessages[msgErrorReplacingExistingFile];
- PerformFileOperationWithRetries(4, False,
- function(out LastError: Cardinal): Boolean
- begin
- Result := Windows.DeleteFile(PChar(DestFile));
- if not Result then begin
- LastError := GetLastError;
- if LastError = ERROR_FILE_NOT_FOUND then begin
- Result := True; { If the file inexplicably vanished, it's not a problem }
- LastError := ERROR_SUCCESS;
- end;
- end;
- end,
- procedure(const LastError: Cardinal; var RetriesLeft: Integer; var NextAction: TFileOperationFailingNextAction)
- begin
- DoHandleFailedDeleteOrMoveFileTry(CurFile, 'DeleteFile', TempFile, DestFile,
- LastError, RetriesLeft, LastOperation, NeedsRestart, ReplaceOnRestart, NextAction);
- end,
- procedure(const LastError: Cardinal; var TryOnceMore: Boolean)
- begin
- Win32ErrorMsg('DeleteFile'); { Throws an exception }
- end);
- end;
- { Rename the temporary file to the new name now, unless the file is
- to be replaced when the system is restarted, or if the file is the
- uninstall program and an existing uninstall program already exists.
- If it can't be renamed and the "restartreplace" flag was specified
- on the entry, register it to be replaced when the system is
- restarted. Do retry renaming before doing this. }
- if not (ReplaceOnRestart or
- ((CurFile^.FileType = ftUninstExe) and DestFileExistedBefore)) then begin
- LastOperation := SetupMessages[msgErrorRenamingTemp];
- { Since the DeleteFile above succeeded you would expect the rename to
- also always succeed, but if it doesn't retry anyway. }
- PerformFileOperationWithRetries(4, True,
- function(out LastError: Cardinal): Boolean
- begin
- Result := MoveFile(PChar(TempFile), PChar(DestFile));
- if not Result then
- LastError := GetLastError;
- end,
- procedure(const LastError: Cardinal; var RetriesLeft: Integer; var NextAction: TFileOperationFailingNextAction)
- begin
- DoHandleFailedDeleteOrMoveFileTry(CurFile, 'MoveFile', TempFile, DestFile,
- LastError, RetriesLeft, LastOperation, NeedsRestart, ReplaceOnRestart, NextAction);
- end,
- procedure(const LastError: Cardinal; var TryOnceMore: Boolean)
- begin
- Win32ErrorMsg('MoveFile'); { Throws an exception }
- end);
- { If ReplaceOnRestart is still False the rename succeeded so handle this.
- Then set any file attributes. }
- if not ReplaceOnRestart then begin
- TempFileLeftOver := False;
- TempFile := '';
- LastOperation := '';
- Log('Successfully installed the file.');
- if AllowFileToBeDuplicated then
- SetFileLocationFilename(CurFile^.LocationEntry, DestFile);
- if foDeleteAfterInstall in CurFile^.Options then
- DeleteFilesAfterInstallList.Add(DestFile);
- { Set file attributes *after* renaming the file since Novell
- reportedly can't rename read-only files. }
- AddAttributesToFile(DestFile, CurFile^.Attribs);
- end;
- end;
- { Leave the temporary file in place for now if the file is to be
- replaced when the system is restarted, or if the file is the uninstall
- program and an existing uninstall program already exists. }
- if ReplaceOnRestart or
- ((CurFile^.FileType = ftUninstExe) and DestFileExistedBefore) then begin
- if CurFile^.FileType = ftUninstExe then
- UninstallTempExeFilename := TempFile;
- TempFileLeftOver := False;
- LastOperation := '';
- Log('Leaving temporary file in place for now.');
- if AllowFileToBeDuplicated then
- SetFileLocationFilename(CurFile^.LocationEntry, TempFile);
- AddAttributesToFile(TempFile, CurFile^.Attribs);
- end;
- { If it's a font, register it }
- if CurFile^.InstallFontName <> '' then begin
- LastOperation := '';
- LogFmt('Registering file as a font ("%s")', [CurFile^.InstallFontName]);
- PerUserFont := not IsAdminInstallMode;
- InstallFont(FontFilename, CurFile^.InstallFontName, PerUserFont, not ReplaceOnRestart, WarnedPerUserFonts);
- DeleteFlags := DeleteFlags or utDeleteFile_IsFont;
- if PerUserFont then
- DeleteFlags := DeleteFlags or utDeleteFile_PerUserFont;
- end;
- { There were no errors so add the uninstall log entry, unless the file
- is the uninstall program, or if it has the foSharedFile flag; shared
- files are handled below. }
- LastOperation := '';
- if CurFile^.FileType <> ftUninstExe then begin
- if not(foUninsNeverUninstall in CurFile^.Options) and
- not(foSharedFile in CurFile^.Options) then begin
- UninstLog.Add(utDeleteFile, [DestFile, TempFile,
- CurFile^.InstallFontName, FontFilename,
- CurFile^.StrongAssemblyName], DeleteFlags);
- end;
- end
- else begin
- if UninstallTempExeFilename = '' then
- UninstallExeCreated := ueNew
- else
- UninstallExeCreated := ueReplaced;
- end;
- Skip:
- { If foRegisterServer or foRegisterTypeLib is in Options, add the
- file to RegisterFilesList for registering later.
- Don't attempt to register if the file doesn't exist (which can
- happen if the foOnlyIfDestFileExists flag is used). }
- if ((foRegisterServer in CurFile^.Options) or
- (foRegisterTypeLib in CurFile^.Options)) and
- NewFileExists(DestFile) then begin
- LastOperation := '';
- if foRegisterTypeLib in CurFile^.Options then
- Log('Will register the file (a type library) later.')
- else
- Log('Will register the file (a DLL/OCX) later.');
- New(RegisterRec);
- RegisterRec^.Filename := DestFile;
- RegisterRec^.Is64Bit := Is64Bit;
- RegisterRec^.TypeLib := foRegisterTypeLib in CurFile^.Options;
- RegisterRec^.NoErrorMessages := foNoRegError in CurFile^.Options;
- RegisterFilesList.Add(RegisterRec);
- end;
- { If foSharedFile is in Options, increment the reference count in the
- registry for the file, then add the uninstall log entry (which,
- unlike non-shared files, must be done on skipped files as well;
- that's why there are two places where utDeleteFile entries are
- added). }
- if foSharedFile in CurFile^.Options then begin
- LastOperation := '';
- if Is64Bit then begin
- Log('Incrementing shared file count (64-bit).');
- IncrementSharedCount(rv64Bit, DestFile, DestFileExistedBefore);
- end
- else begin
- Log('Incrementing shared file count (32-bit).');
- IncrementSharedCount(rv32Bit, DestFile, DestFileExistedBefore);
- end;
- if not(foUninsNeverUninstall in CurFile^.Options) then begin
- DeleteFlags := DeleteFlags or utDeleteFile_SharedFile;
- if Is64Bit then
- DeleteFlags := DeleteFlags or utDeleteFile_SharedFileIn64BitKey;
- if foUninsNoSharedFilePrompt in CurFile^.Options then
- DeleteFlags := DeleteFlags or utDeleteFile_NoSharedFilePrompt;
- UninstLog.Add(utDeleteFile, [DestFile, TempFile,
- CurFile^.InstallFontName, FontFilename,
- CurFile^.StrongAssemblyName], DeleteFlags);
- end
- else begin
- { See comment in Setup.UninstallLog }
- const RedirDestFile = ApplyRedirForRegistrationOperation(Is64Bit, DestFile);
- if Is64Bit then
- UninstLog.Add(utDecrementSharedCount, [RedirDestFile],
- utDecrementSharedCount_64BitKey)
- else
- UninstLog.Add(utDecrementSharedCount, [RedirDestFile], 0);
- end;
- end;
- { Apply permissions (even if the file wasn't replaced) }
- LastOperation := '';
- if TempFile <> '' then
- ApplyPermissions(TempFile, CurFile^.PermissionsEntry)
- else
- ApplyPermissions(DestFile, CurFile^.PermissionsEntry);
- { Set NTFS compression (even if the file wasn't replaced) }
- if (foSetNTFSCompression in CurFile^.Options) or (foUnsetNTFSCompression in CurFile^.Options) then begin
- LastOperation := '';
- if TempFile <> '' then
- ApplyNTFSCompression(TempFile, False, foSetNTFSCompression in CurFile^.Options)
- else
- ApplyNTFSCompression(DestFile, False, foSetNTFSCompression in CurFile^.Options);
- end;
- { Install into GAC (even if the file wasn't replaced) }
- if foGacInstall in CurFile^.Options then begin
- Log('Installing into GAC');
- with TAssemblyCacheInfo.Create(rvDefault) do try
- { PathConvertSuperToNormal because it is not known where InstallAssembly supports
- super paths. It might do now (not tested), but this might not have always been
- the case. }
- if TempFile <> '' then
- InstallAssembly(PathConvertSuperToNormal(TempFile))
- else
- InstallAssembly(PathConvertSuperToNormal(DestFile));
- finally
- Free;
- end;
- end;
- except
- if ExceptObject is EAbort then
- raise;
- Failed := GetExceptMessage;
- end;
- finally
- { If an exception occurred before TempFile was cleaned up, delete it now }
- if TempFileLeftOver then
- Windows.DeleteFile(PChar(TempFile));
- end;
- { Was there an exception? Display error message and offer to retry }
- if Failed <> '' then begin
- if (CurFile^.FileType = ftUninstExe) and (UninstallTempExeFilename <> '') then begin
- DeleteFile(UninstallTempExeFilename);
- UninstallTempExeFilename := '';
- UninstallExeCreated := ueNone;
- end;
- if LastOperation <> '' then
- LastOperation := LastOperation + SNewLine;
- if not AbortRetryIgnoreTaskDialogMsgBox(
- PathConvertSuperToNormal(DestFile) + SNewLine2 + LastOperation + Failed,
- [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgFileAbortRetryIgnoreSkipNotRecommended], SetupMessages[msgAbortRetryIgnoreCancel]]) then begin
- if ProgressUpdated then
- SetProgress(PreviousProgress);
- goto Retry;
- end;
- end;
- { Increment progress meter, if not already done so }
- if not ProgressUpdated then begin
- if Assigned(CurFileLocation) then { not an "external" file }
- IncProgress(CurFileLocation^.OriginalSize)
- else
- IncProgress(AExternalSize);
- end;
- { Process any events between copying files }
- ProcessEvents;
- { Clear previous filename label in case an exception or debugger break
- occurs between now and when the label for the next entry is set }
- SetFilenameLabelText('', False);
- end;
- procedure CopyFiles(const UninstLog: TUninstallLog; const ExpandedAppId: String;
- const RegisterFilesList: TList; Uninstallable: Boolean;
- var UninstallTempExeFilename: String; var UninstallExeCreated: TUninstallExeCreated);
- { Copies all the application's files }
- function RecurseExternalCopyFiles(const Is64Bit: Boolean;
- const SearchBaseDir, SearchSubDir, SearchWildcard: String; const SourceIsWildcard: Boolean;
- const Excludes: TStrings; const CurFile: PSetupFileEntry; var ExpectedBytesLeft: Int64;
- var ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll: TOverwriteAll;
- var WarnedPerUserFonts: Boolean): Boolean;
- begin
- { Also see RecurseExternalFiles and RecurseExternalGetSizeOfFiles in Setup.MainFunc
- Also see RecurseExternalArchiveCopyFiles directly below }
- Result := False;
- var FindData: TWin32FindData;
- var H := FindFirstFile(PChar(SearchBaseDir + SearchSubDir + SearchWildcard), FindData);
- if H <> INVALID_HANDLE_VALUE then begin
- try
- repeat
- if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
- var FileName: String;
- if SourceIsWildcard then begin
- if FindData.dwFileAttributes and FILE_ATTRIBUTE_HIDDEN <> 0 then
- Continue;
- FileName := FindData.cFileName;
- end
- else
- FileName := SearchWildcard; { use the case specified in the script }
- if IsExcluded(SearchSubDir + FileName, Excludes) then
- Continue;
- Result := True;
- var SourceFile := SearchBaseDir + SearchSubDir + FileName;
- { Note: CurFile^.DestName only includes a a filename if foCustomDestName is set,
- see TSetupCompiler.EnumFilesProc.ProcessFileList }
- var DestFile := ExpandConst(CurFile^.DestName);
- if not(foCustomDestName in CurFile^.Options) then
- DestFile := DestFile + SearchSubDir + FileName
- else if SearchSubDir <> '' then
- DestFile := PathExtractPath(DestFile) + SearchSubDir + PathExtractName(DestFile);
- DestFile := ApplyPathRedirRules(Is64Bit, DestFile, tpCurrent);
- var Size := FindDataFileSizeToInt64(FindData);
- if Size > ExpectedBytesLeft then begin
- { Don't allow the progress bar to overflow if the size of the
- files is greater than when we last checked }
- Size := ExpectedBytesLeft;
- end;
- ProcessFileEntry(UninstLog, ExpandedAppId, RegisterFilesList,
- CurFile, Is64Bit, SourceFile, DestFile, nil,
- Size, ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll,
- WarnedPerUserFonts, nil, UninstallTempExeFilename, UninstallExeCreated);
- Dec(ExpectedBytesLeft, Size);
- end;
- until not FindNextFile(H, FindData);
- finally
- Windows.FindClose(H);
- end;
- end;
- if foRecurseSubDirsExternal in CurFile^.Options then begin
- H := FindFirstFile(PChar(SearchBaseDir + SearchSubDir + '*'), FindData);
- if H <> INVALID_HANDLE_VALUE then begin
- try
- repeat
- if IsRecurseableDirectory(FindData) then
- Result := RecurseExternalCopyFiles(Is64Bit, SearchBaseDir,
- SearchSubDir + FindData.cFileName + '\', SearchWildcard,
- SourceIsWildcard, Excludes, CurFile, ExpectedBytesLeft,
- ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll,
- WarnedPerUserFonts) or Result;
- until not FindNextFile(H, FindData);
- finally
- Windows.FindClose(H);
- end;
- end;
- end;
- if SearchSubDir <> '' then begin
- { If Result is False this subdir won't be created, so create it now if
- CreateAllSubDirs was set }
- if not Result and (foCreateAllSubDirs in CurFile.Options) then begin
- var DestName := ExpandConst(CurFile^.DestName); { See above }
- if not(foCustomDestName in CurFile^.Options) then
- DestName := DestName + SearchSubDir
- else
- DestName := PathExtractPath(DestName) + SearchSubDir;
- DestName := ApplyPathRedirRules(Is64Bit, DestName, tpCurrent);
- var Flags: TMakeDirFlags := [];
- if foUninsNeverUninstall in CurFile^.Options then Include(Flags, mdNoUninstall);
- if foDeleteAfterInstall in CurFile^.Options then Include(Flags, mdDeleteAfterInstall);
- MakeDir(UninstLog, DestName, Flags);
- Result := True;
- end;
- end;
- { When recursively searching but not picking up every file, we could
- be frozen for a long time when installing from a network. Calling
- ProcessEvents after every directory helps. }
- ProcessEvents;
- end;
- function RecurseExternalArchiveCopyFiles(const Is64Bit: Boolean;
- const ArchiveFilename: String; const Excludes: TStrings;
- const CurFile: PSetupFileEntry; var ExpectedBytesLeft: Int64;
- var ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll: TOverwriteAll;
- var WarnedPerUserFonts: Boolean): Boolean;
- begin
- { See above }
- { If the archive doesn't exist then the caller should handle this with
- a msgSourceDoesntExist message. All other errors we handle ourselves
- with a msgErrorExtracting message, without informing the caller, unless
- you count EAbort. }
- Result := NewFileExists(ArchiveFilename);
- if not Result then
- Exit;
- if foCustomDestName in CurFile^.Options then
- InternalError('Unexpected custom DestName');
- const DestDir = ApplyPathRedirRules(Is64Bit, ExpandConst(CurFile^.DestName), tpCurrent);
- Log('-- Archive entry --');
- var VerifySourceF: TFile := nil;
- try
- var FindData: TWin32FindData;
- var H: TArchiveFindHandle := INVALID_HANDLE_VALUE;
- var Failed: String;
- repeat
- try
- if CurFile^.Verification.Typ <> fvNone then begin
- if VerifySourceF = nil then
- VerifySourceF := TFile.Create(ArchiveFilename, fdOpenExisting, faRead, fsRead);
- var ExpectedFileHash: TSHA256Digest;
- if CurFile^.Verification.Typ = fvHash then
- ExpectedFileHash := CurFile^.Verification.Hash
- else begin
- DoISSigVerify(VerifySourceF, nil, ArchiveFilename, True, CurFile^.Verification.ISSigAllowedKeys,
- ExpectedFileHash);
- end;
- { Can't get the SHA-256 while extracting so need to get and check it now }
- const ActualFileHash = GetSHA256OfFile(VerifySourceF);
- if not SHA256DigestsEqual(ActualFileHash, ExpectedFileHash) then
- VerificationError(veFileHashIncorrect);
- Log(VerificationSuccessfulLogMessage);
- { Keep VerifySourceF open until extraction has completed to prevent TOCTOU problem }
- end;
- H := ArchiveFindFirstFile(ArchiveFilename, DestDir,
- ExpandConst(CurFile^.ExtractArchivePassword), foRecurseSubDirsExternal in CurFile^.Options,
- True, FindData);
- Failed := '';
- except
- if ExceptObject is EAbort then
- raise;
- Failed := GetExceptMessage;
- end;
- until (Failed = '') or
- AbortRetryIgnoreTaskDialogMsgBox(
- ArchiveFilename + SNewLine2 + SetupMessages[msgErrorExtracting] + SNewLine + Failed,
- [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgFileAbortRetryIgnoreSkipNotRecommended], SetupMessages[msgAbortRetryIgnoreCancel]]);
- if H <> INVALID_HANDLE_VALUE then begin
- try
- repeat
- if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
- if IsExcluded(FindData.cFileName, Excludes) then
- Continue;
- var SourceFile := UIntToStr(H);
- const DestFile = DestDir + FindData.cFileName;
- var Size := FindDataFileSizeToInt64(FindData);
- if Size > ExpectedBytesLeft then begin
- { Don't allow the progress bar to overflow if the size of the
- files is greater than when we last checked }
- Size := ExpectedBytesLeft;
- end;
- ProcessFileEntry(UninstLog, ExpandedAppId, RegisterFilesList,
- CurFile, Is64Bit, SourceFile, DestFile,
- nil, Size, ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll,
- WarnedPerUserFonts, @FindData.ftLastWriteTime,
- UninstallTempExeFilename, UninstallExeCreated);
- Dec(ExpectedBytesLeft, Size);
- end else if foCreateAllSubDirs in CurFile.Options then begin
- var Flags: TMakeDirFlags := [];
- if foUninsNeverUninstall in CurFile^.Options then Include(Flags, mdNoUninstall);
- if foDeleteAfterInstall in CurFile^.Options then Include(Flags, mdDeleteAfterInstall);
- MakeDir(UninstLog, DestDir + FindData.cFileName, Flags);
- Result := True;
- end;
- until not ArchiveFindNextFile(H, FindData);
- finally
- ArchiveFindClose(H);
- end;
- Log('Successfully extracted the archive.');
- end else
- Log('Found no files to extract.');
- finally
- VerifySourceF.Free;
- end;
- end;
- var
- CurFile: PSetupFileEntry;
- SourceWildcard: String;
- FoundFiles: Boolean;
- ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll: TOverwriteAll;
- WarnedPerUserFonts: Boolean;
- begin
- ConfirmOverwriteOverwriteAll := oaUnknown;
- PromptIfOlderOverwriteAll := oaUnknown;
- WarnedPerUserFonts := False;
- var FileLocationFilenames: TStringList := nil;
- var Excludes: TStringList := nil;
- try
- FileLocationFilenames := TStringList.Create;
- for var I := 0 to Entries[seFileLocation].Count-1 do
- FileLocationFilenames.Add('');
- Excludes := TStringList.Create;
- Excludes.StrictDelimiter := True;
- Excludes.Delimiter := ',';
- for var CurFileNumber := 0 to Entries[seFile].Count-1 do begin
- CurFile := PSetupFileEntry(Entries[seFile][CurFileNumber]);
- if ((CurFile^.FileType <> ftUninstExe) or Uninstallable) and
- ShouldProcessFileEntry(WizardComponents, WizardTasks, CurFile, False) then begin
- DebugNotifyEntry(seFile, CurFileNumber);
- NotifyBeforeInstallFileEntry(CurFile);
- var Is64Bit := InstallDefault64Bit;
- if fo32Bit in CurFile^.Options then
- Is64Bit := False;
- if fo64Bit in CurFile^.Options then begin
- if not IsWin64 then
- InternalError('Cannot install files to 64-bit locations on this version of Windows');
- Is64Bit := True;
- end;
- if CurFile^.LocationEntry <> -1 then begin
- ProcessFileEntry(UninstLog, ExpandedAppId, RegisterFilesList,
- CurFile, Is64Bit, '', '', FileLocationFilenames, 0,
- ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll, WarnedPerUserFonts, nil,
- UninstallTempExeFilename, UninstallExeCreated);
- end
- else begin
- { File is an 'external' file }
- if CurFile^.FileType = ftUninstExe then begin
- { This is the file entry for the uninstaller program }
- SourceWildcard := NewParamStr(0);
- Is64Bit := IsCurrentProcess64Bit;
- end else begin
- SourceWildcard := ExpandConst(CurFile^.SourceFilename);
- if not(foDownload in CurFile^.Options) then
- SourceWildcard := ApplyPathRedirRules(Is64Bit, SourceWildcard, tpCurrent);
- end;
- Excludes.DelimitedText := CurFile^.Excludes;
- var ProgressBefore := CurProgress;
- repeat
- SetProgress(ProgressBefore);
- var ExpectedBytesLeft := CurFile^.ExternalSize;
- if foDownload in CurFile^.Options then begin
- { Archive download should have been done already by Setup.WizardForm's DownloadArchivesToExtract }
- if foExtractArchive in CurFile^.Options then
- InternalError('Unexpected Download flag');
- if foSkipIfSourceDoesntExist in CurFile^.Options then
- InternalError('Unexpected SkipIfSourceDoesntExist flag');
- if not(foCustomDestName in CurFile^.Options) then
- InternalError('Expected CustomDestName flag');
- { CurFile^.DestName now includes a filename, see TSetupCompiler.EnumFilesProc.ProcessFileList }
- ProcessFileEntry(UninstLog, ExpandedAppId, RegisterFilesList,
- CurFile, Is64Bit, SourceWildcard, ApplyPathRedirRules(Is64Bit, ExpandConst(CurFile^.DestName), tpCurrent),
- nil, ExpectedBytesLeft, ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll,
- WarnedPerUserFonts, nil,
- UninstallTempExeFilename, UninstallExeCreated);
- FoundFiles := True;
- end else if foExtractArchive in CurFile^.Options then
- FoundFiles := RecurseExternalArchiveCopyFiles(Is64Bit,
- SourceWildcard, Excludes, CurFile,
- ExpectedBytesLeft, ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll,
- WarnedPerUserFonts)
- else
- FoundFiles := RecurseExternalCopyFiles(Is64Bit,
- PathExtractPath(SourceWildcard), '', PathExtractName(SourceWildcard),
- IsWildcard(SourceWildcard), Excludes, CurFile,
- ExpectedBytesLeft, ConfirmOverwriteOverwriteAll, PromptIfOlderOverwriteAll,
- WarnedPerUserFonts);
- until FoundFiles or
- (foSkipIfSourceDoesntExist in CurFile^.Options) or
- AbortRetryIgnoreTaskDialogMsgBox(
- SetupMessages[msgErrorReadingSource] + SNewLine + AddPeriod(FmtSetupMessage(msgSourceDoesntExist, [SourceWildcard])),
- [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgFileAbortRetryIgnoreSkipNotRecommended], SetupMessages[msgAbortRetryIgnoreCancel]]);
- { In case we didn't end up copying all the expected bytes, bump
- the progress bar up to the expected amount }
- Inc(ProgressBefore, CurFile^.ExternalSize);
- SetProgress(ProgressBefore);
- end;
- NotifyAfterInstallFileEntry(CurFile);
- end;
- end;
- finally
- Excludes.Free;
- FileLocationFilenames.Free;
- end;
- end;
- procedure CreateIcons(const UninstLog: TUninstallLog);
- function IsPathURL(const S: String): Boolean;
- { Returns True if S begins with a scheme name and colon. Should be
- compliant with RFC 2396 section 3.1. }
- const
- SchemeAlphaChars = ['A'..'Z', 'a'..'z'];
- SchemeAllChars = SchemeAlphaChars + ['0'..'9', '+', '-', '.'];
- var
- P, I: Integer;
- begin
- Result := False;
- P := PathPos(':', S);
- if (P > 2) and CharInSet(S[1], SchemeAlphaChars) then begin
- for I := 2 to P-1 do
- if not CharInSet(S[I], SchemeAllChars) then
- Exit;
- Result := True;
- end;
- end;
- procedure CreateURLFile(const Filename, URL, IconFilename: String;
- const IconIndex: Integer);
- var
- S: String;
- F: TTextFileWriter;
- begin
- S := '[InternetShortcut]' + SNewLine + 'URL=' + URL + SNewLine;
- if IconFilename <> '' then
- S := S + 'IconFile=' + IconFilename + SNewLine +
- 'IconIndex=' + IntToStr(IconIndex) + SNewLine;
- F := TTextFileWriter.Create(Filename, fdCreateAlways, faWrite, fsNone);
- try
- if SameText(S, String(AnsiString(S))) then
- F.WriteAnsi(AnsiString(S))
- else
- F.Write(S);
- finally
- F.Free;
- end;
- end;
- procedure DeleteFolderShortcut(const Dir: String);
- var
- Attr: DWORD;
- DesktopIniFilename, S: String;
- begin
- Attr := GetFileAttributes(PChar(Dir));
- if (Attr <> INVALID_FILE_ATTRIBUTES) and (Attr and FILE_ATTRIBUTE_DIRECTORY <> 0) then begin
- { To be sure this is really a folder shortcut and not a regular folder,
- look for a desktop.ini file specifying CLSID_FolderShortcut }
- DesktopIniFilename := PathCombine(Dir, 'desktop.ini');
- S := GetIniString('.ShellClassInfo', 'CLSID2', '', DesktopIniFilename);
- if CompareText(S, '{0AFACED1-E828-11D1-9187-B532F1E9575D}') = 0 then begin
- DeleteFile(DesktopIniFilename);
- DeleteFile(PathCombine(Dir, 'target.lnk'));
- SetFileAttributes(PChar(Dir), Attr and not FILE_ATTRIBUTE_READONLY);
- RemoveDirectory(PChar(Dir));
- end;
- end;
- end;
- procedure CreateAnIcon(Name: String; const Description, Path, Parameters,
- WorkingDir, IconFilename: String; const IconIndex, ShowCmd: Integer;
- const NeverUninstall: Boolean; const CloseOnExit: TSetupIconCloseOnExit;
- const HotKey: Word; const AppUserModelID: String;
- const AppUserModelToastActivatorCLSID: PGUID;
- const ExcludeFromShowInNewInstall, PreventPinning: Boolean);
- var
- BeginsWithGroup: Boolean;
- LinkFilename, PifFilename, UrlFilename, DirFilename, ProbableFilename,
- ResultingFilename: String;
- Flags: TMakeDirFlags;
- URLShortcut: Boolean;
- begin
- BeginsWithGroup := Copy(Name, 1, 8) = '{group}\';
- { Note: PathExpand removes trailing spaces, so it can't be called on
- Name before the extensions are appended }
- Name := ExpandConst(Name);
- LinkFilename := PathExpand(Name + '.lnk');
- PifFilename := PathExpand(Name + '.pif');
- UrlFilename := PathExpand(Name + '.url');
- DirFilename := PathExpand(Name);
- Flags := [mdNotifyChange];
- if NeverUninstall then
- Include(Flags, mdNoUninstall)
- else if BeginsWithGroup then
- Include(Flags, mdAlwaysUninstall);
- URLShortcut := IsPathURL(Path);
- if URLShortcut then
- ProbableFilename := UrlFilename
- else
- ProbableFilename := LinkFilename;
- LogFmt('Dest filename: %s', [ProbableFilename]);
- SetFilenameLabelText(ProbableFilename, True);
- MakeDir(UninstLog, PathExtractDir(ProbableFilename), Flags);
- { Delete any old files first }
- DeleteFile(LinkFilename);
- DeleteFile(PifFilename);
- if NewFileExists(UrlFilename) then begin
- { Flush out any pending writes by other apps before deleting }
- WritePrivateProfileString(nil, nil, nil, PChar(UrlFilename));
- end;
- DeleteFile(UrlFilename);
- DeleteFolderShortcut(DirFilename);
- Log('Creating the icon.');
- if not URLShortcut then begin
- { Create the shortcut.
- Note: Don't call PathExpand on any of the paths since they may contain
- environment-variable strings (e.g. %SystemRoot%\...) }
- ResultingFilename := CreateShellLink(LinkFilename, Description, Path,
- Parameters, WorkingDir, IconFilename, IconIndex, ShowCmd, HotKey,
- AppUserModelID, AppUserModelToastActivatorCLSID,
- ExcludeFromShowInNewInstall, PreventPinning);
- { If a .pif file was created, apply the "Close on exit" setting }
- if (CloseOnExit <> icNoSetting) and
- SameText(PathExtractExt(ResultingFilename), '.pif') then begin
- try
- ModifyPifFile(ResultingFilename, CloseOnExit = icYes);
- except
- { Failure isn't important here. Ignore exceptions }
- end;
- end;
- end
- else begin
- { Create an Internet Shortcut (.url) file }
- CreateURLFile(UrlFilename, Path, IconFilename, IconIndex);
- ResultingFilename := UrlFilename;
- end;
- Log('Successfully created the icon.');
- { Set the global flag that is checked by the Finished wizard page }
- CreatedIcon := True;
- { Notify shell of the change }
- ShellChangeNotifyPath(SHCNE_CREATE, ResultingFilename, False);
- ShellChangeNotifyPath(SHCNE_UPDATEDIR, PathExtractDir(ResultingFilename), True);
- { Add uninstall log entries }
- if not NeverUninstall then begin
- if URLShortcut then
- UninstLog.Add(utDeleteFile, [ResultingFilename], utDeleteFile_CallChangeNotify)
- else begin
- { Even though we only created one file, go ahead and try deleting
- both a .lnk and .pif file at uninstall time, in case the user
- alters the shortcut after installation }
- UninstLog.Add(utDeleteFile, [LinkFilename], utDeleteFile_CallChangeNotify);
- UninstLog.Add(utDeleteFile, [PifFilename], utDeleteFile_CallChangeNotify);
- end;
- end;
- end;
- function ExpandAppPath(const Filename: String): String;
- var
- K: HKEY;
- Found: Boolean;
- begin
- if RegOpenKeyExView(InstallDefaultRegView, HKEY_LOCAL_MACHINE,
- PChar(REGSTR_PATH_APPPATHS + '\' + Filename), 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
- Found := RegQueryStringValue(K, '', Result);
- RegCloseKey(K);
- if Found then
- Exit;
- end;
- Result := Filename;
- end;
- var
- CurIcon: PSetupIconEntry;
- FN: String;
- TACLSID: PGUID;
- begin
- for var CurIconNumber := 0 to Entries[seIcon].Count-1 do begin
- try
- CurIcon := PSetupIconEntry(Entries[seIcon][CurIconNumber]);
- with CurIcon^ do begin
- if ShouldProcessIconEntry(WizardComponents, WizardTasks, WizardNoIcons, CurIcon) then begin
- DebugNotifyEntry(seIcon, CurIconNumber);
- NotifyBeforeInstallEntry(BeforeInstall);
- Log('-- Icon entry --');
- FN := ExpandConst(Filename);
- if ioUseAppPaths in Options then
- FN := ExpandAppPath(FN);
- if not(ioCreateOnlyIfFileExists in Options) or
- NewFileExists(ApplyPathRedirRules(IsWin64, FN, tpCurrent)) then begin
- if ioHasAppUserModelToastActivatorCLSID in Options then
- TACLSID := @AppUserModelToastActivatorCLSID
- else
- TACLSID := nil;
- CreateAnIcon(IconName, ExpandConst(Comment), FN,
- ExpandConst(Parameters), ExpandConst(WorkingDir),
- ExpandConst(IconFilename), IconIndex, ShowCmd,
- ioUninsNeverUninstall in Options, CloseOnExit, HotKey,
- ExpandConst(AppUserModelID), TACLSID,
- ioExcludeFromShowInNewInstall in Options,
- ioPreventPinning in Options)
- end else
- Log('Skipping due to "createonlyiffileexists" flag.');
- { Increment progress meter }
- IncProgress(1000);
-
- NotifyAfterInstallEntry(AfterInstall);
- end;
- end;
- except
- if not(ExceptObject is EAbort) then
- Application.HandleException(nil)
- else
- raise;
- end;
- ProcessEvents;
- { Clear previous filename label in case an exception or debugger break
- occurs between now and when the label for the next entry is set }
- SetFilenameLabelText('', False);
- end;
- end;
- procedure CreateIniEntries(const UninstLog: TUninstallLog);
- var
- CurIni: PSetupIniEntry;
- IniSection, IniEntry, IniValue, IniFilename, IniDir: String;
- Skip: Boolean;
- begin
- for var CurIniNumber := 0 to Entries[seIni].Count-1 do begin
- CurIni := PSetupIniEntry(Entries[seIni][CurIniNumber]);
- with CurIni^ do begin
- if ShouldProcessEntry(WizardComponents, WizardTasks, Components, Tasks, Languages, Check) then begin
- DebugNotifyEntry(seIni, CurIniNumber);
- NotifyBeforeInstallEntry(BeforeInstall);
- Log('-- INI entry --');
- IniSection := ExpandConst(Section);
- IniEntry := ExpandConst(Entry);
- IniValue := ExpandConst(Value);
- IniFilename := ExpandConst(Filename);
- LogFmt('Dest filename: %s', [IniFilename]);
- LogFmt('Section: %s', [IniSection]);
- if IniEntry <> '' then
- LogFmt('Entry: %s', [IniEntry]);
- if ioHasValue in Options then
- LogFmt('Value: %s', [IniValue]);
- if (IniEntry <> '') and (ioHasValue in Options) and
- (not(ioCreateKeyIfDoesntExist in Options) or
- not IniKeyExists(IniSection, IniEntry, IniFilename)) then begin
- Skip := False;
- IniDir := PathExtractDir(IniFilename);
- if IniDir <> '' then begin
- while True do begin
- try
- MakeDir(UninstLog, IniDir);
- Break;
- except
- if AbortRetryIgnoreTaskDialogMsgBox(
- GetExceptMessage,
- [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgAbortRetryIgnoreIgnore], SetupMessages[msgAbortRetryIgnoreCancel]]) then begin
- Skip := True;
- Break;
- end;
- end;
- end;
- end;
- if not Skip then
- Log('Updating the .INI file.');
- repeat
- if SetIniString(IniSection, IniEntry, IniValue, IniFilename) then begin
- Log('Successfully updated the .INI file.');
- Break;
- end;
- until AbortRetryIgnoreTaskDialogMsgBox(
- FmtSetupMessage1(msgErrorIniEntry, IniFilename),
- [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgAbortRetryIgnoreIgnore], SetupMessages[msgAbortRetryIgnoreCancel]]);
- end else
- Log('Skipping updating the .INI file, only updating uninstall log.');
- if ioUninsDeleteEntireSection in Options then
- UninstLog.Add(utIniDeleteSection, [IniFilename, IniSection], 0);
- if ioUninsDeleteSectionIfEmpty in Options then
- UninstLog.Add(utIniDeleteSection, [IniFilename, IniSection],
- utIniDeleteSection_OnlyIfEmpty);
- if (ioUninsDeleteEntry in Options) and (IniEntry <> '') then
- UninstLog.Add(utIniDeleteEntry, [IniFilename, IniSection, IniEntry], 0);
- { ^ add utIniDeleteEntry last since we want it done first by the
- uninstaller (in case the entry's also got the
- "uninsdeletesectionifempty" flag) }
- NotifyAfterInstallEntry(AfterInstall);
- end;
- end;
- end;
- { Increment progress meter }
- IncProgress(1000);
- end;
- procedure CreateRegistryEntries(const UninstLog: TUninstallLog);
-
- function IsDeletableSubkey(const S: String): Boolean;
- { A sanity check to prevent people from shooting themselves in the foot by
- using
- Root: HKLM; Subkey: ""; Flags: [unins]deletekey
- or a 'code' constant in Subkey that returns a blank string or only
- backslashes. }
- var
- P: PChar;
- begin
- Result := False;
- P := PChar(S);
- while P^ <> #0 do begin
- if P^ <> '\' then begin
- Result := True;
- Break;
- end;
- Inc(P);
- end;
- end;
- procedure ApplyPermissions(const RegView: TRegView; const RootKey: HKEY;
- const Subkey: String; const PermsEntry: Integer);
- var
- P: PSetupPermissionEntry;
- begin
- LogFmt('Setting permissions on key: %s\%s',
- [GetRegRootKeyName(RootKey), Subkey]);
- P := Entries[sePermission][PermsEntry];
- if not GrantPermissionOnKey(RegView, RootKey, Subkey,
- TGrantPermissionEntry(Pointer(P.Permissions)^),
- Length(P.Permissions) div SizeOf(TGrantPermissionEntry)) then begin
- if GetLastError = ERROR_FILE_NOT_FOUND then
- Log('Could not set permissions on the key because it currently does not exist.')
- else
- LogFmt('Failed to set permissions on the key (%d).', [GetLastError]);
- end;
- end;
- const
- REG_QWORD = 11;
- var
- RK, K: HKEY;
- Disp: DWORD;
- N, V, ExistingData: String;
- ExistingType, NewType, DV: DWORD;
- S: String;
- RV: TRegView;
- NeedToRetry, DidDeleteKey: Boolean;
- I: Integer;
- AnsiS: AnsiString;
- begin
- for var CurRegNumber := 0 to Entries[seRegistry].Count-1 do begin
- with PSetupRegistryEntry(Entries[seRegistry][CurRegNumber])^ do begin
- if ShouldProcessEntry(WizardComponents, WizardTasks, Components, Tasks, Languages, Check) then begin
- DebugNotifyEntry(seRegistry, CurRegNumber);
- NotifyBeforeInstallEntry(BeforeInstall);
- Log('-- Registry entry --');
- RK := HKEY(RootKey);
- if RK = HKEY_AUTO then
- RK := InstallModeRootKey;
- S := ExpandConst(Subkey);
- LogFmt('Key: %s\%s', [GetRegRootKeyName(RK), Subkey]);
- N := ExpandConst(ValueName);
- if N <> '' then
- LogFmt('Value name: %s', [N]);
- RV := InstallDefaultRegView;
- if (ro32Bit in Options) and (RV <> rv32Bit) then begin
- Log('Non-default bitness: 32-bit');
- RV := rv32Bit;
- end;
- if ro64Bit in Options then begin
- if not IsWin64 then
- InternalError('Cannot access 64-bit registry keys on this version of Windows');
- if RV <> rv64Bit then begin
- Log('Non-default bitness: 64-bit');
- RV := rv64Bit;
- end;
- end;
- repeat
- NeedToRetry := False;
- try
- DidDeleteKey := False;
- if roDeleteKey in Options then begin
- if IsDeletableSubkey(S) then begin
- Log('Deleting the key.');
- RegDeleteKeyIncludingSubkeys(RV, RK, PChar(S));
- DidDeleteKey := True;
- end else
- Log('Key to delete is not deletable.');
- end;
- if (roDeleteKey in Options) and (Typ = rtNone) then begin
- { We've deleted the key, and no value is to be created.
- Our work is done. }
- if DidDeleteKey then
- Log('Successfully deleted the key.');
- end else if (roDeleteValue in Options) and (Typ = rtNone) then begin
- { We're going to delete a value with no intention of creating
- another, so don't create the key if it didn't exist. }
- if RegOpenKeyExView(RV, RK, PChar(S), 0, KEY_SET_VALUE, K) = ERROR_SUCCESS then begin
- Log('Deleting the value.');
- RegDeleteValue(K, PChar(N));
- RegCloseKey(K);
- Log('Successfully deleted the value.');
- { Our work is done. }
- end else
- Log('Key of value to delete does not exist.');
- end
- else begin
- { Apply any permissions *before* calling RegCreateKeyExView or
- RegOpenKeyExView, since we may (in a rather unlikely scenario)
- need those permissions in order for those calls to succeed }
- if PermissionsEntry <> -1 then
- ApplyPermissions(RV, RK, S, PermissionsEntry);
- { Create or open the key }
- var ErrorCode: DWORD;
- if not(roDontCreateKey in Options) then begin
- Log('Creating or opening the key.');
- ErrorCode := DWORD(RegCreateKeyExView(RV, RK, PChar(S), 0, nil,
- REG_OPTION_NON_VOLATILE, KEY_QUERY_VALUE or KEY_SET_VALUE,
- nil, K, @Disp));
- if ErrorCode = ERROR_SUCCESS then begin
- { Apply permissions again if a new key was created }
- if (Disp = REG_CREATED_NEW_KEY) and (PermissionsEntry <> -1) then begin
- Log('New key created, need to set permissions again.');
- ApplyPermissions(RV, RK, S, PermissionsEntry);
- end;
- end
- else begin
- if not(roNoError in Options) then
- RegError(reRegCreateKeyEx, RK, S, ErrorCode);
- end;
- end
- else begin
- if Typ <> rtNone then begin
- Log('Opening the key.');
- ErrorCode := DWORD(RegOpenKeyExView(RV, RK, PChar(S), 0,
- KEY_QUERY_VALUE or KEY_SET_VALUE, K));
- if (ErrorCode <> ERROR_SUCCESS) and (ErrorCode <> ERROR_FILE_NOT_FOUND) then
- if not(roNoError in Options) then
- RegError(reRegOpenKeyEx, RK, S, ErrorCode);
- end
- else begin
- { We're not creating a value, and we're not just deleting a
- value (that was checked above), so there is no reason to
- even open the key }
- Log('Not creating the key or a value, skipping the key and only updating uninstall log.');
- ErrorCode := ERROR_FILE_NOT_FOUND;
- end;
- end;
- { If there was no error opening the key, proceed with deleting
- and/or creating the value }
- if ErrorCode = ERROR_SUCCESS then
- try
- if roDeleteValue in Options then begin
- Log('Deleting the value.');
- RegDeleteValue(K, PChar(N));
- end;
- if (Typ <> rtNone) and
- (not(roCreateValueIfDoesntExist in Options) or
- not RegValueExists(K, PChar(N))) then begin
- Log('Creating or setting the value.');
- case Typ of
- rtString, rtExpandString, rtMultiString: begin
- NewType := REG_SZ;
- case Typ of
- rtExpandString: NewType := REG_EXPAND_SZ;
- rtMultiString: NewType := REG_MULTI_SZ;
- end;
- if Typ <> rtMultiString then begin
- if (Pos('{olddata}', ValueData) <> 0) and
- RegQueryStringValue(K, PChar(N), ExistingData) then
- { successful }
- else
- ExistingData := '';
- if roPreserveStringType in Options then begin
- if (RegQueryValueEx(K, PChar(N), nil, @ExistingType, nil, nil) = ERROR_SUCCESS) and
- ((ExistingType = REG_SZ) or (ExistingType = REG_EXPAND_SZ)) then
- NewType := ExistingType;
- end;
- V := ExpandConstEx(ValueData, ['olddata', ExistingData])
- end
- else begin
- if (Pos('{olddata}', ValueData) <> 0) and
- RegQueryMultiStringValue(K, PChar(N), ExistingData) then
- { successful }
- else
- ExistingData := '';
- V := ExpandConstEx(ValueData, ['olddata', ExistingData,
- 'break', #0]);
- { Multi-string data requires two null terminators:
- one after the last string, and one to mark the end.
- Delphi's String type is implicitly null-terminated,
- so only one null needs to be added to the end. }
- if (V <> '') and (V[Length(V)] <> #0) then
- V := V + #0;
- end;
- ErrorCode := DWORD(RegSetValueEx(K, PChar(N), 0, NewType,
- PChar(V), (ULength(V)+1)*SizeOf(V[1])));
- if (ErrorCode <> ERROR_SUCCESS) and
- not(roNoError in Options) then
- RegError(reRegSetValueEx, RK, S, ErrorCode);
- end;
- rtDWord: begin
- DV := DWORD(StrToInt(ExpandConst(ValueData)));
- ErrorCode := DWORD(RegSetValueEx(K, PChar(N), 0, REG_DWORD,
- @DV, SizeOf(DV)));
- if (ErrorCode <> ERROR_SUCCESS) and
- not(roNoError in Options) then
- RegError(reRegSetValueEx, RK, S, ErrorCode);
- end;
- rtQWord: begin
- const QV: UInt64 = StrToUInt64(ExpandConst(ValueData));
- ErrorCode := DWORD(RegSetValueEx(K, PChar(N), 0, REG_QWORD,
- @QV, SizeOf(QV)));
- if (ErrorCode <> ERROR_SUCCESS) and
- not(roNoError in Options) then
- RegError(reRegSetValueEx, RK, S, ErrorCode);
- end;
- rtBinary: begin
- AnsiS := '';
- for I := 1 to Length(ValueData) do
- AnsiS := AnsiS + AnsiChar(Ord(ValueData[I]));
- ErrorCode := DWORD(RegSetValueEx(K, PChar(N), 0, REG_BINARY,
- PAnsiChar(AnsiS), ULength(AnsiS)));
- if (ErrorCode <> ERROR_SUCCESS) and
- not(roNoError in Options) then
- RegError(reRegSetValueEx, RK, S, ErrorCode);
- end;
- end;
- Log('Successfully created or set the value.');
- end else if roDeleteValue in Options then
- Log('Successfully deleted the value.')
- else
- Log('Successfully created the key.')
- { Our work is done. }
- finally
- RegCloseKey(K);
- end;
- end;
- except
- if not AbortRetryIgnoreTaskDialogMsgBox(
- GetExceptMessage,
- [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgAbortRetryIgnoreIgnore], SetupMessages[msgAbortRetryIgnoreCancel]]) then begin
- Log('Retrying.');
- NeedToRetry := True;
- end;
- end;
- until not NeedToRetry;
-
- if roUninsDeleteEntireKey in Options then
- if IsDeletableSubkey(S) then
- UninstLog.AddReg(utRegDeleteEntireKey, RV, RK, [S]);
- if roUninsDeleteEntireKeyIfEmpty in Options then
- if IsDeletableSubkey(S) then
- UninstLog.AddReg(utRegDeleteKeyIfEmpty, RV, RK, [S]);
- if roUninsDeleteValue in Options then
- UninstLog.AddReg(utRegDeleteValue, RV, RK, [S, N]);
- { ^ must add roUninsDeleteValue after roUninstDeleteEntireKey*
- since the entry may have both the roUninsDeleteValue and
- roUninsDeleteEntireKeyIfEmpty options }
- if roUninsClearValue in Options then
- UninstLog.AddReg(utRegClearValue, RV, RK, [S, N]);
- NotifyAfterInstallEntry(AfterInstall);
- end;
- end;
- end;
- { Increment progress meter }
- IncProgress(1000);
- end;
- procedure RegisterFiles(const RegisterFilesList: TList);
- procedure RegisterServersOnRestart;
- function CreateRegSvrExe(const Dir: String): String;
- var
- ExeFilename: String;
- SourceF, DestF: TFile;
- NumRead: Cardinal;
- Buf: array[0..16383] of Byte;
- begin
- ExeFilename := GenerateUniqueName(Dir, '.exe');
- DestF := nil;
- SourceF := TFile.Create(NewParamStr(0), fdOpenExisting, faRead, fsRead);
- try
- DestF := TFile.Create(ExeFilename, fdCreateAlways, faWrite, fsNone);
- try
- DestF.Seek(SourceF.Size);
- DestF.Truncate;
- DestF.Seek(0);
- while True do begin
- NumRead := SourceF.Read(Buf, SizeOf(Buf));
- if NumRead = 0 then
- Break;
- DestF.WriteBuffer(Buf, NumRead);
- end;
- if not(shSignedUninstaller in SetupHeader.Options) then
- MarkExeHeader(DestF, SetupExeModeRegSvr);
- except
- FreeAndNil(DestF);
- DeleteFile(ExeFilename);
- raise;
- end;
- finally
- DestF.Free;
- SourceF.Free;
- end;
- Result := ExeFilename;
- end;
- procedure CreateRegSvrMsg(const Filename: String);
- var
- F: TFile;
- begin
- F := TFile.Create(Filename, fdCreateAlways, faWrite, fsNone);
- try
- WriteMsgData(F);
- finally
- F.Free;
- end;
- end;
- const
- Chars: array[Boolean, Boolean] of Char = (('s', 't'), ('S', 'T'));
- var
- RegSvrExeFilename: String;
- F: TTextFileWriter;
- Rec: PRegisterFilesListRec;
- RootKey, H: HKEY;
- J: Integer;
- Disp: DWORD;
- ValueName, Data: String;
- begin
- { Create RegSvr program used to register OLE servers & type libraries on
- the next reboot }
- if IsAdmin then begin
- try
- RegSvrExeFilename := CreateRegSvrExe(WinDir);
- except
- { In case Windows directory is write protected, try the Temp directory.
- Windows directory is our first choice since some people (ignorantly)
- put things like "DELTREE C:\WINDOWS\TEMP\*.*" in their AUTOEXEC.BAT.
- Also, each user has his own personal Temp directory which may not
- be accessible by other users. }
- RegSvrExeFilename := CreateRegSvrExe(GetTempDir);
- end;
- end
- else begin
- { Always use Temp directory when user doesn't have admin privileges }
- RegSvrExeFilename := CreateRegSvrExe(GetTempDir);
- end;
- LogFmt('Registration executable created: %s', [RegSvrExeFilename]);
- try
- CreateRegSvrMsg(PathChangeExt(RegSvrExeFilename, '.msg'));
- F := TTextFileWriter.Create(PathChangeExt(RegSvrExeFilename, '.lst'),
- fdCreateAlways, faWrite, fsNone);
- try
- F.WriteLine('; This file was created by the installer for:');
- F.WriteLine('; ' + ExpandedAppVerName);
- F.WriteLine('; Location: ' + SetupLdrOriginalFilename);
- F.WriteLine('');
- F.WriteLine('; List of files to be registered on the next reboot. DO NOT EDIT!');
- F.WriteLine('');
- for var I := 0 to RegisterFilesList.Count-1 do begin
- Rec := RegisterFilesList[I];
- Data := '[..]' + Rec.Filename;
- Data[2] := Chars[Rec.Is64Bit, Rec.TypeLib];
- if Rec.NoErrorMessages then
- Data[3] := 'q';
- F.WriteLine(Data);
- end;
- finally
- F.Free;
- end;
- if IsAdmin then
- RootKey := HKEY_LOCAL_MACHINE
- else
- RootKey := HKEY_CURRENT_USER;
- var ErrorCode := DWORD(RegCreateKeyExView(rvDefault, RootKey, REGSTR_PATH_RUNONCE, 0, nil,
- REG_OPTION_NON_VOLATILE, KEY_SET_VALUE or KEY_QUERY_VALUE,
- nil, H, @Disp));
- if ErrorCode <> ERROR_SUCCESS then
- RegError(reRegCreateKeyEx, RootKey, REGSTR_PATH_RUNONCE, ErrorCode);
- try
- J := 0;
- while True do begin
- Inc(J);
- ValueName := Format('InnoSetupRegFile.%.10d', [J]); { don't localize }
- { ^ Note: Names of values written to the "RunOnce" key cannot
- exceed 31 characters! Otherwise the original Windows
- Explorer 4.0 will not process them. }
- if not RegValueExists(H, PChar(ValueName)) then begin
- Data := '"' + RegSvrExeFilename + '" /REG';
- if not IsAdmin then
- Data := Data + 'U'; { /REG -> /REGU when not running as admin }
- { Note: RegSvr expects /REG(U) to be the first parameter }
- Data := Data + ' /REGSVRMODE';
- ErrorCode := DWORD(RegSetValueEx(H, PChar(ValueName), 0, REG_SZ, PChar(Data),
- (ULength(Data)+1)*SizeOf(Data[1])));
- if ErrorCode <> ERROR_SUCCESS then
- RegError(reRegSetValueEx, RootKey, REGSTR_PATH_RUNONCE, ErrorCode);
- Break;
- end;
- end;
- finally
- RegCloseKey(H);
- end;
- except
- DeleteFile(PathChangeExt(RegSvrExeFilename, '.lst'));
- DeleteFile(PathChangeExt(RegSvrExeFilename, '.msg'));
- DeleteFile(RegSvrExeFilename);
- raise;
- end;
- end;
- procedure RegisterServerWithRetries(const Is64Bit: Boolean; const Filename: String;
- const NoErrorMessages: Boolean);
- var
- NeedToRetry: Boolean;
- begin
- repeat
- if Is64Bit then
- LogFmt('Registering 64-bit DLL/OCX: %s', [Filename])
- else
- LogFmt('Registering 32-bit DLL/OCX: %s', [Filename]);
- NeedToRetry := False;
- try
- RegisterServer(False, Is64Bit, Filename, NoErrorMessages);
- Log('Registration successful.');
- except
- Log('Registration failed:' + SNewLine + GetExceptMessage);
- if not NoErrorMessages then
- if not AbortRetryIgnoreTaskDialogMsgBox(
- Filename + SNewLine2 + FmtSetupMessage1(msgErrorRegisterServer, GetExceptMessage),
- [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgFileAbortRetryIgnoreIgnoreNotRecommended], SetupMessages[msgAbortRetryIgnoreCancel]]) then
- NeedToRetry := True;
- end;
- until not NeedToRetry;
- end;
- procedure RegisterTypeLibraryWithRetries(const Is64Bit: Boolean; const Filename: String;
- const NoErrorMessages: Boolean);
- var
- NeedToRetry: Boolean;
- begin
- repeat
- if Is64Bit then
- LogFmt('Registering 64-bit type library: %s', [Filename])
- else
- LogFmt('Registering 32-bit type library: %s', [Filename]);
- NeedToRetry := False;
- try
- {$IFDEF WIN64}
- if Is64Bit then
- RegisterTypeLibrary(Filename)
- else
- InternalError('Cannot register 32-bit type libraries on this version of Setup');
- {$ELSE}
- if Is64Bit then
- InternalError('Cannot register 64-bit type libraries on this version of Setup')
- else
- RegisterTypeLibrary(Filename);
- {$ENDIF}
- Log('Registration successful.');
- except
- Log('Registration failed:' + SNewLine + GetExceptMessage);
- if not NoErrorMessages then
- if not AbortRetryIgnoreTaskDialogMsgBox(
- Filename + SNewLine2 + FmtSetupMessage1(msgErrorRegisterTypeLib, GetExceptMessage),
- [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgFileAbortRetryIgnoreIgnoreNotRecommended], SetupMessages[msgAbortRetryIgnoreCancel]]) then
- NeedToRetry := True;
- end;
- until not NeedToRetry;
- end;
- begin
- if not NeedsRestart then
- for var I := 0 to RegisterFilesList.Count-1 do begin
- with PRegisterFilesListRec(RegisterFilesList[I])^ do
- if not TypeLib then
- RegisterServerWithRetries(Is64Bit, Filename, NoErrorMessages)
- else
- RegisterTypeLibraryWithRetries(Is64Bit, Filename, NoErrorMessages);
- end
- else begin
- { When a restart is needed, all "regserver" & "regtypelib" files will get
- registered on the next logon }
- Log('Delaying registration of all files until the next logon since a restart is needed.');
- try
- RegisterServersOnRestart;
- except
- Application.HandleException(nil);
- end;
- end;
- end;
- procedure RecordUninstallDeleteEntries(const UninstLog: TUninstallLog);
- const
- DefFlags: array[TSetupDeleteType] of TUninstallRecExtraData = (
- utDeleteDirOrFiles_Extra or utDeleteDirOrFiles_DeleteFiles,
- utDeleteDirOrFiles_Extra or utDeleteDirOrFiles_DeleteFiles or
- utDeleteDirOrFiles_DeleteSubdirsAlso,
- utDeleteDirOrFiles_Extra or utDeleteDirOrFiles_IsDir);
- begin
- for var I := Entries[seUninstallDelete].Count-1 downto 0 do
- { ^ process backwards so the uninstaller will process them in the order
- they appear in the script }
- with PSetupDeleteEntry(Entries[seUninstallDelete][I])^ do
- if ShouldProcessEntry(WizardComponents, WizardTasks, Components, Tasks, Languages, Check) then begin
- DebugNotifyEntry(seUninstallDelete, I);
- NotifyBeforeInstallEntry(BeforeInstall);
- const Path = ApplyPathRedirRules(InstallDefault64Bit, ExpandConst(Name), tpCurrent);
- var Flags := DefFlags[DeleteType];
- if IsCurrentProcess64Bit then { Post-ApplyPathRedirRules we should check IsCurrentProcess64Bit and not the original InstallDefault64Bit }
- Flags := Flags or utDeleteDirOrFiles_Is64Bit;
- UninstLog.Add(utDeleteDirOrFiles, [Path], Flags);
- NotifyAfterInstallEntry(AfterInstall);
- end;
- end;
- procedure RecordUninstallRunEntries(const UninstLog: TUninstallLog);
- var
- RunEntry: PSetupRunEntry;
- begin
- for var I := Entries[seUninstallRun].Count-1 downto 0 do begin
- { ^ process backwards so the uninstaller will process them in the order
- they appear in the script }
- RunEntry := PSetupRunEntry(Entries[seUninstallRun][I]);
- if ShouldProcessEntry(WizardComponents, WizardTasks, RunEntry.Components,
- RunEntry.Tasks, RunEntry.Languages, RunEntry.Check) then begin
- DebugNotifyEntry(seUninstallRun, I);
- NotifyBeforeInstallEntry(RunEntry.BeforeInstall);
- var Flags: TUninstallRecExtraData := 0;
- case RunEntry.Wait of
- rwNoWait: Flags := Flags or utRun_NoWait;
- rwWaitUntilIdle: Flags := Flags or utRun_WaitUntilIdle;
- end;
- if roShellExec in RunEntry.Options then
- Flags := Flags or (utRun_ShellExec or utRun_ShellExecRespectWaitFlags)
- else begin
- if RunEntryIs64Bit(RunEntry) then
- Flags := Flags or utRun_Is64Bit;
- end;
- if roSkipIfDoesntExist in RunEntry.Options then
- Flags := Flags or utRun_SkipIfDoesntExist;
- case RunEntry.ShowCmd of
- SW_SHOWMINNOACTIVE: Flags := Flags or utRun_RunMinimized;
- SW_SHOWMAXIMIZED: Flags := Flags or utRun_RunMaximized;
- SW_HIDE: Flags := Flags or utRun_RunHidden;
- end;
- if roDontLogParameters in RunEntry.Options then
- Flags := Flags or utRun_DontLogParameters;
- if roLogOutput in RunEntry.Options then
- Flags := Flags or utRun_LogOutput;
- UninstLog.Add(utRun, [ExpandConst(RunEntry.Name),
- ExpandConst(RunEntry.Parameters), ExpandConst(RunEntry.WorkingDir),
- ExpandConst(RunEntry.RunOnceId), ExpandConst(RunEntry.Verb)],
- Flags);
- NotifyAfterInstallEntry(RunEntry.AfterInstall);
- end;
- end;
- end;
- { Also sets UninstallExeFilename which is a global }
- procedure GenerateUninstallInfoFilename(const UninstLog: TUninstallLog;
- var UninstallDataFilename, UninstallMsgFilename: String;
- var UninstallDataCreated, AppendUninstallData: Boolean);
- procedure FindFiles(const BaseDir: String; var ExistingFiles: array of Boolean);
- var
- H: THandle;
- FindData: TWin32FindData;
- S: String;
- begin
- H := FindFirstFile(PChar(AddBackslash(BaseDir) + 'unins???.*'),
- FindData);
- if H <> INVALID_HANDLE_VALUE then begin
- repeat
- S := FindData.cFilename;
- if (Length(S) >= 9) and (CompareText(Copy(S, 1, 5), 'unins') = 0) and
- CharInSet(S[6], ['0'..'9']) and CharInSet(S[7], ['0'..'9']) and CharInSet(S[8], ['0'..'9']) and
- (S[9] = '.') then
- ExistingFiles[StrToInt(Copy(S, 6, 3))] := True;
- until not FindNextFile(H, FindData);
- Windows.FindClose(H);
- end;
- end;
- procedure GenerateFilenames(const BaseDir: String; const I: Integer);
- var
- BaseFilename: String;
- begin
- BaseFilename := AddBackslash(BaseDir) + Format('unins%.3d', [I]);
- UninstallExeFilename := BaseFilename + '.exe';
- UninstallDataFilename := BaseFilename + '.dat';
- UninstallMsgFilename := BaseFilename + '.msg';
- end;
- procedure ReserveDataFile;
- var
- H: THandle;
- begin
- { Create an empty .dat file to reserve the filename. }
- H := CreateFile(PChar(UninstallDataFilename), GENERIC_READ or GENERIC_WRITE,
- 0, nil, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
- if H = INVALID_HANDLE_VALUE then
- Win32ErrorMsg('CreateFile');
- CloseHandle(H);
- UninstallDataCreated := True;
- end;
- var
- ExistingFiles: array[0..999] of Boolean;
- begin
- { For consistency with IS<=6.x, in case someone sets UninstallFilesDir to
- "sys" (not recommended), we run the path through ApplyPathRedirRules to
- change System32 to SysWOW64 when running on 32-bit Setup. This ensures
- that 64-bit processes like Add/Remove Programs and Explorer know that the
- uninstaller EXE is in the 32-bit system directory.
- On 64-bit Setup, however, this leaves System32 as-is, so 32-bit processes
- may be unable to access the uninstaller EXE.
- Also, rfNormalPath is used because UninstallFilesDir is used as the basis
- for the uninstallexe constant, which is written to the Uninstall key and
- can also be used in [Icons] shortcuts. It is not known whether
- Add/Remove Programs or Explorer support super paths, but because
- UninstallFilesDir would usually not exceed MAX_PATH, using rfNormalPath
- does not introduce a limitation in practice. }
- const BaseDir = ApplyPathRedirRules(IsCurrentProcess64Bit,
- ExpandConst(SetupHeader.UninstallFilesDir), tpCurrent, [rfNormalPath]);
- LogFmt('Directory for uninstall files: %s', [BaseDir]);
- MakeDir(UninstLog, BaseDir);
- FillChar(ExistingFiles, SizeOf(ExistingFiles), 0); { set all to False }
- FindFiles(BaseDir, ExistingFiles);
- { Look for an existing .dat file to append to or overwrite }
- if SetupHeader.UninstallLogMode <> lmNew then
- for var I := 0 to 999 do
- if ExistingFiles[I] then begin
- GenerateFilenames(BaseDir, I);
- if NewFileExists(UninstallDataFilename) and
- UninstLog.CanAppend(UninstallDataFilename) then begin
- if SetupHeader.UninstallLogMode = lmAppend then begin
- LogFmt('Will append to existing uninstall log: %s', [UninstallDataFilename]);
- AppendUninstallData := True;
- end
- else
- LogFmt('Will overwrite existing uninstall log: %s', [UninstallDataFilename]);
- Exit;
- end;
- end;
- { None found; use a new .dat file }
- for var I := 0 to 999 do
- if not ExistingFiles[I] then begin
- GenerateFilenames(BaseDir, I);
- LogFmt('Creating new uninstall log: %s', [UninstallDataFilename]);
- ReserveDataFile;
- Exit;
- end;
- raise Exception.Create(FmtSetupMessage1(msgErrorTooManyFilesInDir,
- BaseDir));
- end;
- procedure RenameUninstallExe(var UninstallTempExeFilename: String);
- begin
- { If the uninstall EXE wasn't extracted to a .tmp file because it isn't
- replacing an existing uninstall EXE, exit. }
- if UninstallTempExeFilename = '' then
- Exit;
- Log('Renaming uninstaller.');
- var Timer: TOneShotTimer;
- const CapturableUninstallTempExeFilename = UninstallTempExeFilename;
- PerformFileOperationWithRetries(4, False,
- function(out LastError: Cardinal): Boolean
- begin
- Timer.Start(1000);
- Result := MoveFileReplace(CapturableUninstallTempExeFilename, UninstallExeFilename);
- if not Result then
- LastError := GetLastError;
- end,
- procedure(const LastError: Cardinal)
- begin
- LogFmt('The existing file appears to be in use (%d). ' +
- 'Retrying.', [LastError]);
- Timer.SleepUntilExpired;
- ProcessEvents;
- end,
- procedure(const LastError: Cardinal; var TryOnceMore: Boolean)
- begin
- const LastOperation = SetupMessages[msgErrorReplacingExistingFile];
- const Failed = AddPeriod(FmtSetupMessage(msgErrorFunctionFailedWithMessage,
- ['MoveFileEx', IntToStr(LastError), Win32ErrorString(LastError)]));
- const Text = UninstallExeFilename + SNewLine2 + LastOperation + SNewLine + Failed;
- case LoggedTaskDialogMsgBox('', SetupMessages[msgRetryCancelSelectAction], Text, '',
- mbError, MB_RETRYCANCEL, [SetupMessages[msgRetryCancelRetry], SetupMessages[msgRetryCancelCancel]],
- 0, True, IDCANCEL) of
- IDRETRY: TryOnceMore := True;
- IDCANCEL: Abort;
- else
- Log('LoggedTaskDialogMsgBox returned an unexpected value. Assuming Cancel.');
- Abort;
- end;
- end);
- UninstallTempExeFilename := '';
- end;
- function CreateUninstallMsgFile(const UninstallExeCreated: TUninstallExeCreated;
- const UninstallMsgFilename: String): Boolean;
- { If the uninstaller EXE has a digital signature, or if Setup was started
- with /DETACHEDMSG, create the unins???.msg file }
- var
- F: TFile;
- begin
- { If this installation didn't create or replace an unins???.exe file,
- do nothing }
- Result := False;
- if (UninstallExeCreated <> ueNone) and
- ((shSignedUninstaller in SetupHeader.Options) or DetachedUninstMsgFile) then begin
- LogFmt('Writing uninstaller messages: %s', [UninstallMsgFilename]);
- F := TFile.Create(UninstallMsgFilename, fdCreateAlways, faWrite, fsNone);
- try
- if UninstallExeCreated = ueNew then
- Result := True;
- WriteMsgData(F);
- finally
- F.Free;
- end;
- end;
- end;
- procedure PerformInstall(var Succeeded: Boolean; const ChangesEnvironment,
- ChangesAssociations: Boolean);
- begin
- Succeeded := False;
- Log('Starting the installation process.');
- SetCurrentDir(WinSystemDir);
- var InstallFilesSize, AfterInstallFilesSize: Int64;
- CalcFilesSize(InstallFilesSize, AfterInstallFilesSize);
- InitProgressGauge(InstallFilesSize);
- var RegisterFilesList: TList := nil;
- const UninstLog = TSetupUninstallLog.Create;
- try
- var UninstallExeCreated := ueNone;
- var UninstallDataCreated := False;
- var UninstallMsgCreated := False;
- var UninstLogCleared := False;
- var UninstallTempExeFilename, UninstallDataFilename, UninstallMsgFilename: String; { There's also UninstallExeFilename but it's a global }
- try
- { Get AppId, UninstallRegKeyBaseName, and Uninstallable now so the user
- can't change them while we're installing }
- const ExpandedAppId = ExpandConst(SetupHeader.AppId);
- if ExpandedAppId = '' then
- InternalError('Failed to get a non empty installation "AppId"');
- if TUninstallLog.WriteSafeHeaderString(nil, ExpandedAppId, 0) > 128 then
- InternalError('"AppId" cannot exceed 128 bytes (encoded)');
- const UninstallRegKeyBaseName = GetUninstallRegKeyBaseName(ExpandedAppId);
- const Uninstallable = EvalDirectiveCheck(SetupHeader.Uninstallable);
- { Init }
- UninstLog.InstallMode64Bit := Is64BitInstallMode;
- UninstLog.AppName := ExpandedAppName;
- UninstLog.AppId := ExpandedAppId;
- if IsAdminInstallMode then
- Include(UninstLog.Flags, ufAdminInstallMode);
- if IsWin64 then
- Include(UninstLog.Flags, ufWin64);
- if IsAdmin then { Setup or [Code] might have done administrative actions, even if IsAdminInstallMode is False }
- Include(UninstLog.Flags, ufAdminInstalled)
- else if IsPowerUserOrAdmin then
- { Note: This flag is only set in 5.1.9 and later }
- Include(UninstLog.Flags, ufPowerUserInstalled);
- if shUninstallRestartComputer in SetupHeader.Options then
- Include(UninstLog.Flags, ufAlwaysRestart);
- if ChangesEnvironment then
- Include(UninstLog.Flags, ufChangesEnvironment);
- if RedirectionGuardEnabled then
- Include(UninstLog.Flags, ufRedirectionGuard);
- RecordStartInstall(UninstLog);
- RecordCompiledCode(UninstLog);
- RegisterFilesList := TList.Create;
- { Process Component entries, if any }
- ProcessComponentEntries;
- ProcessEvents;
- { Process Tasks entries, if any }
- ProcessTasksEntries;
- ProcessEvents;
- { Shutdown applications, if any }
- if RmSessionStarted and RmFoundApplications then begin
- if WizardPreparingYesRadio then begin
- SetStatusLabelText(SetupMessages[msgStatusClosingApplications]);
- ShutdownApplications;
- ProcessEvents;
- end else
- Log('User chose not to shutdown applications using our files.');
- end;
- { Process InstallDelete entries, if any }
- ProcessInstallDeleteEntries;
- ProcessEvents;
- if ExpandedAppMutex <> '' then
- UninstLog.Add(utMutexCheck, [ExpandedAppMutex], 0);
- if ChangesAssociations then
- UninstLog.Add(utRefreshFileAssoc, [''], 0);
- { Record UninstallDelete entries, if any }
- RecordUninstallDeleteEntries(UninstLog);
- ProcessEvents;
- { Create the application directory and extra dirs }
- SetStatusLabelText(SetupMessages[msgStatusCreateDirs]);
- CreateDirs(UninstLog);
- ProcessEvents;
- var AppendUninstallData := False;
- if Uninstallable then begin
- { Generate the filenam(UninstLog)es for the uninstall info in the application
- directory }
- SetStatusLabelText(SetupMessages[msgStatusSavingUninstall]);
- GenerateUninstallInfoFilename(UninstLog, UninstallDataFilename, UninstallMsgFilename,
- UninstallDataCreated, AppendUninstallData);
- end;
- { Copy the files }
- SetStatusLabelText(SetupMessages[msgStatusExtractFiles]);
- CopyFiles(UninstLog, ExpandedAppId, RegisterFilesList, Uninstallable,
- UninstallTempExeFilename, UninstallExeCreated);
- ProcessEvents;
- { Create program icons, if any }
- if HasIcons then begin
- SetStatusLabelText(SetupMessages[msgStatusCreateIcons]);
- CreateIcons(UninstLog);
- ProcessEvents;
- end;
- { Create INI entries, if any }
- if Entries[seIni].Count <> 0 then begin
- SetStatusLabelText(SetupMessages[msgStatusCreateIniEntries]);
- CreateIniEntries(UninstLog);
- ProcessEvents;
- end;
- { Create registry entries, if any }
- if Entries[seRegistry].Count <> 0 then begin
- SetStatusLabelText(SetupMessages[msgStatusCreateRegistryEntries]);
- CreateRegistryEntries(UninstLog);
- ProcessEvents;
- end;
- { Call the NeedRestart event function now.
- Note: This can't be done after RegisterFiles, since RegisterFiles
- relies on the setting of the NeedsRestart variable. }
- SetStatusLabelText('');
- ProcessNeedRestartEvent;
- ProcessEvents;
- { Register files, if any }
- if RegisterFilesList.Count <> 0 then begin
- SetStatusLabelText(SetupMessages[msgStatusRegisterFiles]);
- RegisterFiles(RegisterFilesList);
- ProcessEvents;
- end;
- { Save uninstall information. After uninstall info is saved, you cannot
- make any more modifications to the user's system. Any additional
- modifications you want to add must be done before this is called. }
- if Uninstallable then begin
- SetStatusLabelText(SetupMessages[msgStatusSavingUninstall]);
- Log('Saving uninstall information.');
- RenameUninstallExe(UninstallTempExeFilename);
- UninstallMsgCreated := CreateUninstallMsgFile(UninstallExeCreated, UninstallMsgFilename);
- { Register uninstall information so the program can be uninstalled
- through the Add/Remove Programs Control Panel applet. This is done
- on NT 3.51 too, so that the uninstall entry for the app will appear
- if the user later upgrades to NT 4.0+. }
- if EvalDirectiveCheck(SetupHeader.CreateUninstallRegKey) then
- RegisterUninstallInfo(UninstLog, UninstallRegKeyBaseName, AfterInstallFilesSize);
- RecordUninstallRunEntries(UninstLog);
- UninstLog.Add(utEndInstall, [GetLocalTimeAsStr], 0);
- UninstLog.Save(UninstallDataFilename, AppendUninstallData,
- shUpdateUninstallLogAppName in SetupHeader.Options);
- if Debugging then
- DebugNotifyUninstExe(UninstallExeFileName);
- end;
- SetStatusLabelText('');
- UninstLogCleared := True;
- UninstLog.Clear;
- except
- try
- { Show error message, if any, and set the exit code we'll be returning }
- if not(ExceptObject is EAbort) then begin
- Log(Format('Fatal exception during installation process (%s):' + SNewLine,
- [ExceptObject.ClassName]) + GetExceptMessage);
- SetupExitCode := ecInstallationError;
- Application.HandleException(nil);
- LoggedMsgBox(SetupMessages[msgSetupAborted], '', mbCriticalError, MB_OK, True, IDOK);
- end
- else begin
- Log('User canceled the installation process.');
- SetupExitCode := ecInstallationCancelled;
- end;
- { Undo any changes it's made so far }
- if not UninstLogCleared then begin
- Log('Rolling back changes.');
- try
- SetStatusLabelText(SetupMessages[msgStatusRollback]);
- WizardForm.ProgressGauge.Visible := False;
- FinishProgressGauge(True);
- WizardForm.CancelButton.Enabled := False;
- WizardForm.Update;
- except
- { ignore any exceptions, just in case... }
- end;
- if UninstallTempExeFilename <> '' then
- DeleteFile(UninstallTempExeFilename);
- if UninstallExeCreated = ueNew then
- DeleteFile(UninstallExeFilename);
- if UninstallDataCreated then
- DeleteFile(UninstallDataFilename);
- if UninstallMsgCreated then
- DeleteFile(UninstallMsgFilename);
- UninstLog.PerformUninstall(False, nil);
- { Sleep for a bit so that the user has time to read the "Rolling
- back changes" message }
- if WizardForm.Visible then
- Sleep(1500);
- end;
- except
- { No exception should be generated by the above code, but just in
- case, handle any exception now so that Application.Terminate is
- always called below.
- Note that we can't just put Application.Terminate in a finally
- section, because it would prevent the display of an exception
- message box later (MessageBox() dislikes WM_QUIT). }
- Application.HandleException(nil);
- end;
- Exit;
- end;
- finally
- if Assigned(RegisterFilesList) then begin
- for var I := RegisterFilesList.Count-1 downto 0 do
- Dispose(PRegisterFilesListRec(RegisterFilesList[I]));
- RegisterFilesList.Free;
- end;
- UninstLog.Free;
- FinishProgressGauge(False);
- end;
- Log('Installation process succeeded.');
- Succeeded := True;
- end;
- end.
|