| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568 |
- unit Setup.Install.HelperFunc;
- {
- Inno Setup
- Copyright (C) 1997-2025 Jordan Russell
- Portions by Martijn Laan
- For conditions of distribution and use, see LICENSE.TXT.
- Installation helper functions which don't need install state such as UninstLog and RegisterFileList
- Only to be called by Setup.Install: if you want to reuse any of these functione from another unit
- you should move the function so somewhere else, like Setup.InstFunc
- }
- interface
- uses
- Windows, SHA256, Shared.FileClass, Shared.Struct, Setup.UninstallLog;
- type
- TSetupUninstallLog = class(TUninstallLog)
- protected
- procedure HandleException; override;
- end;
- TRegErrorFunc = (reRegSetValueEx, reRegCreateKeyEx, reRegOpenKeyEx);
- procedure SetFilenameLabelText(const S: String; const CallUpdate: Boolean);
- procedure SetStatusLabelText(const S: String;
- const ClearFilenameLabelText: Boolean = True);
- procedure InstallMessageBoxCallback(const Flags: Cardinal; const After: Boolean;
- const Param: LongInt);
- procedure CalcFilesSize(var InstallFilesSize, AfterInstallFilesSize: Int64);
- procedure InitProgressGauge(const InstallFilesSize: Int64);
- procedure UpdateProgressGauge;
- procedure FinishProgressGauge(const HideGauge: Boolean);
- procedure SetProgress(const AProgress: Int64);
- procedure IncProgress(const N: Int64);
- function CurProgress: Int64;
- procedure ProcessEvents;
- procedure InternalProgressProc(const Bytes: Cardinal);
- procedure ExternalProgressProc64(const Bytes, MaxProgress: Int64);
- procedure JustProcessEventsProc64(const Bytes, Param: Int64);
- function AbortRetryIgnoreTaskDialogMsgBox(const Text: String;
- const RetryIgnoreAbortButtonLabels: array of String): Boolean;
- function FileTimeToStr(const AFileTime: TFileTime): String;
- function TryToGetSHA256OfFile(const DisableFsRedir: Boolean; const Filename: String;
- var Sum: TSHA256Digest): Boolean;
- procedure CopySourceFileToDestFile(const SourceF, DestF: TFile;
- [ref] const Verification: TSetupFileVerification; const ISSigSourceFilename: String;
- const AExpectedSize: Int64);
- function ShortenOrExpandFontFilename(const Filename: String): String;
- function GetLocalTimeAsStr: String;
- procedure PackCustomMessagesIntoString(var S: String);
- function PackCompiledCodeTextIntoString(const CompiledCodeText: AnsiString): String;
- procedure RegError(const Func: TRegErrorFunc; const RootKey: HKEY;
- const KeyName: String; const ErrorCode: DWORD);
- procedure WriteMsgData(const F: TFile);
- procedure MarkExeHeader(const F: TFile; const ModeID: Longint);
- procedure ProcessInstallDeleteEntries;
- procedure ProcessNeedRestartEvent;
- procedure ProcessComponentEntries;
- procedure ProcessTasksEntries;
- procedure ShutdownApplications;
- implementation
- uses
- Classes, SysUtils, Forms,
- NewProgressBar, PathFunc, RestartManager, TaskbarProgressFunc, UnsignedFunc,
- Shared.CommonFunc, Shared.CommonFunc.Vcl, Shared.SetupMessageIDs, Shared.SetupTypes,
- SetupLdrAndSetup.Messages,
- Setup.InstFunc, Setup.ISSigVerifyFunc, Setup.LoggingFunc, Setup.MainFunc, Setup.ScriptRunner,
- Setup.WizardForm;
- procedure TSetupUninstallLog.HandleException;
- begin
- Application.HandleException(Self);
- end;
- procedure SetFilenameLabelText(const S: String; const CallUpdate: Boolean);
- begin
- WizardForm.FilenameLabel.Caption := MinimizePathName(S, WizardForm.FilenameLabel.Font, WizardForm.FileNameLabel.Width);
- if CallUpdate then
- WizardForm.FilenameLabel.Update;
- end;
- procedure SetStatusLabelText(const S: String;
- const ClearFilenameLabelText: Boolean = True);
- begin
- if WizardForm.StatusLabel.Caption <> S then begin
- WizardForm.StatusLabel.Caption := S;
- WizardForm.StatusLabel.Update;
- end;
- if ClearFilenameLabelText then
- SetFilenameLabelText('', True);
- end;
- procedure InstallMessageBoxCallback(const Flags: Cardinal; const After: Boolean;
- const Param: LongInt);
- const
- States: array [TNewProgressBarState] of TTaskbarProgressState =
- (tpsNormal, tpsError, tpsPaused);
- var
- NewState: TNewProgressBarState;
- begin
- if After then
- NewState := npbsNormal
- else if (Flags and MB_ICONSTOP) <> 0 then
- NewState := npbsError
- else
- NewState := npbsPaused;
- with WizardForm.ProgressGauge do begin
- State := NewState;
- Invalidate;
- end;
- SetAppTaskbarProgressState(States[NewState]);
- end;
- procedure CalcFilesSize(var InstallFilesSize, AfterInstallFilesSize: Int64);
- var
- CurFile: PSetupFileEntry;
- begin
- InstallFilesSize := 0;
- AfterInstallFilesSize := InstallFilesSize;
- for var N := 0 to Entries[seFile].Count-1 do begin
- CurFile := PSetupFileEntry(Entries[seFile][N]);
- if ShouldProcessFileEntry(WizardComponents, WizardTasks, CurFile, False) then begin
- with CurFile^ do begin
- var FileSize: Int64;
- if LocationEntry <> -1 then { not an "external" file }
- FileSize := PSetupFileLocationEntry(Entries[seFileLocation][
- LocationEntry])^.OriginalSize
- else
- FileSize := ExternalSize;
- Inc(InstallFilesSize, FileSize);
- if not (foDeleteAfterInstall in Options) then
- Inc(AfterInstallFilesSize, FileSize);
- end;
- end;
- end;
- end;
- var
- CurProgressValue: Int64;
- ProgressShiftCount: Cardinal;
- procedure InitProgressGauge(const InstallFilesSize: Int64);
- begin
- { Calculate the MaxValue for the progress meter }
- var NewMaxValue: Int64 := 1000 * Entries[seIcon].Count;
- if Entries[seIni].Count <> 0 then Inc(NewMaxValue, 1000);
- if Entries[seRegistry].Count <> 0 then Inc(NewMaxValue, 1000);
- Inc(NewMaxValue, InstallFilesSize);
- { To avoid progress updates that are too small to result in any visible
- change, divide the Max value by 2 until it's under 1500 }
- ProgressShiftCount := 0;
- while NewMaxValue >= 1500 do begin
- NewMaxValue := NewMaxValue shr 1;
- Inc(ProgressShiftCount);
- end;
- WizardForm.ProgressGauge.Max := Integer(NewMaxValue);
- SetMessageBoxCallbackFunc(InstallMessageBoxCallback, 0);
- end;
- procedure UpdateProgressGauge;
- begin
- var NewPosition := Integer(CurProgressValue shr ProgressShiftCount);
- if WizardForm.ProgressGauge.Position <> NewPosition then begin
- WizardForm.ProgressGauge.Position := NewPosition;
- WizardForm.ProgressGauge.Update;
- end;
- SetAppTaskbarProgressValue(UInt64(NewPosition), UInt64(WizardForm.ProgressGauge.Max));
- if (CodeRunner <> nil) and CodeRunner.FunctionExists('CurInstallProgressChanged', True) then begin
- try
- CodeRunner.RunProcedures('CurInstallProgressChanged', [NewPosition,
- WizardForm.ProgressGauge.Max], False);
- except
- Log('CurInstallProgressChanged raised an exception.');
- Application.HandleException(nil);
- end;
- end;
- end;
- procedure FinishProgressGauge(const HideGauge: Boolean);
- begin
- SetMessageBoxCallbackFunc(nil, 0);
- if HideGauge then
- WizardForm.ProgressGauge.Visible := False;
- SetAppTaskbarProgressState(tpsNoProgress);
- end;
- procedure SetProgress(const AProgress: Int64);
- begin
- CurProgressValue := AProgress;
- UpdateProgressGauge;
- end;
- procedure IncProgress(const N: Int64);
- begin
- Inc(CurProgressValue, N);
- UpdateProgressGauge;
- end;
- function CurProgress: Int64;
- begin
- Result := CurProgressValue;
- end;
- procedure ProcessEvents;
- { Processes any waiting events. Must call this this periodically or else
- events like clicking the Cancel button won't be processed.
- Calls Abort if NeedToAbortInstall is True, which is usually the result of
- the user clicking Cancel and the form closing. }
- begin
- if NeedToAbortInstall then Abort;
- Application.ProcessMessages;
- if NeedToAbortInstall then Abort;
- end;
- procedure InternalProgressProc(const Bytes: Cardinal);
- begin
- IncProgress(Bytes);
- ProcessEvents;
- end;
- procedure ExternalProgressProc64(const Bytes, MaxProgress: Int64);
- begin
- var NewProgress := CurProgress;
- Inc(NewProgress, Bytes);
- { In case the source file was larger than we thought it was, stop the
- progress bar at the maximum amount. Also see CopySourceFileToDestFile. }
- if NewProgress > MaxProgress then
- NewProgress := MaxProgress;
- SetProgress(NewProgress);
-
- ProcessEvents;
- end;
- procedure JustProcessEventsProc64(const Bytes, Param: Int64);
- begin
- ProcessEvents;
- end;
- function AbortRetryIgnoreTaskDialogMsgBox(const Text: String;
- const RetryIgnoreAbortButtonLabels: array of String): Boolean;
- { Returns True if Ignore was selected, False if Retry was selected, or
- calls Abort if Abort was selected. }
- begin
- Result := False;
- case LoggedTaskDialogMsgBox('', SetupMessages[msgAbortRetryIgnoreSelectAction], Text, '',
- mbError, MB_ABORTRETRYIGNORE, RetryIgnoreAbortButtonLabels, 0, True, IDABORT) of
- IDABORT: Abort;
- IDRETRY: ;
- IDIGNORE: Result := True;
- else
- Log('LoggedTaskDialogMsgBox returned an unexpected value. Assuming Abort.');
- Abort;
- end;
- end;
- function FileTimeToStr(const AFileTime: TFileTime): String;
- { Converts a TFileTime into a string for log purposes. }
- var
- FT: TFileTime;
- ST: TSystemTime;
- begin
- FileTimeToLocalFileTime(AFileTime, FT);
- if FileTimeToSystemTime(FT, ST) then
- Result := Format('%.4u-%.2u-%.2u %.2u:%.2u:%.2u.%.3u',
- [ST.wYear, ST.wMonth, ST.wDay, ST.wHour, ST.wMinute, ST.wSecond,
- ST.wMilliseconds])
- else
- Result := '(invalid)';
- end;
- function TryToGetSHA256OfFile(const DisableFsRedir: Boolean; const Filename: String;
- var Sum: TSHA256Digest): Boolean;
- { Like GetSHA256OfFile but traps exceptions locally. Returns True if successful. }
- begin
- try
- Sum := GetSHA256OfFile(DisableFsRedir, Filename);
- Result := True;
- except
- Result := False;
- end;
- end;
- procedure CopySourceFileToDestFile(const SourceF, DestF: TFile;
- [ref] const Verification: TSetupFileVerification; const ISSigSourceFilename: String;
- const AExpectedSize: Int64);
- { Copies all bytes from SourceF to DestF, incrementing process meter as it
- goes. Assumes file pointers of both are 0. }
- var
- Buf: array[0..16383] of Byte;
- Context: TSHA256Context;
- begin
- var ExpectedFileHash: TSHA256Digest;
- if Verification.Typ <> fvNone then begin
- if Verification.Typ = fvHash then
- ExpectedFileHash := Verification.Hash
- else
- DoISSigVerify(SourceF, nil, ISSigSourceFilename, True, Verification.ISSigAllowedKeys, ExpectedFileHash);
- { ExpectedFileHash checked below after copy }
- SHA256Init(Context);
- end;
- var MaxProgress := CurProgress;
- Inc(MaxProgress, AExpectedSize);
- var BytesLeft := SourceF.Size;
- { To avoid file system fragmentation, preallocate all of the bytes in the
- destination file }
- DestF.Seek(BytesLeft);
- DestF.Truncate;
- DestF.Seek(0);
- while BytesLeft > 0 do begin
- var BufSize: Cardinal := SizeOf(Buf);
- if BytesLeft < BufSize then
- BufSize := Cardinal(BytesLeft);
- SourceF.ReadBuffer(Buf, BufSize);
- DestF.WriteBuffer(Buf, BufSize);
- Dec(BytesLeft, BufSize);
- if Verification.Typ <> fvNone then
- SHA256Update(Context, Buf, BufSize);
- ExternalProgressProc64(BufSize, MaxProgress);
- end;
- if Verification.Typ <> fvNone then begin
- if not SHA256DigestsEqual(SHA256Final(Context), ExpectedFileHash) then
- VerificationError(veFileHashIncorrect);
- Log(VerificationSuccessfulLogMessage);
- end;
- { In case the source file was shorter than we thought it was, bump the
- progress bar to the maximum amount }
- SetProgress(MaxProgress);
- end;
- function ShortenOrExpandFontFilename(const Filename: String): String;
- { Expands Filename, except if it's in the Fonts directory, in which case it
- removes the path }
- var
- FontDir: String;
- begin
- Result := PathExpand(Filename);
- FontDir := GetShellFolder(False, sfFonts);
- if FontDir <> '' then
- if PathCompare(PathExtractDir(Result), FontDir) = 0 then
- Result := PathExtractName(Result);
- end;
- function GetLocalTimeAsStr: String;
- var
- SysTime: TSystemTime;
- begin
- GetLocalTime(SysTime);
- SetString(Result, PChar(@SysTime), SizeOf(SysTime) div SizeOf(Char));
- end;
- procedure PackCustomMessagesIntoString(var S: String);
- var
- M: TMemoryStream;
- Count, N: Integer;
- begin
- M := TMemoryStream.Create;
- try
- Count := 0;
- M.WriteBuffer(Count, SizeOf(Count)); { overwritten later }
- for var I := 0 to Entries[seCustomMessage].Count-1 do begin
- with PSetupCustomMessageEntry(Entries[seCustomMessage][I])^ do begin
- if (LangIndex = -1) or (LangIndex = ActiveLanguage) then begin
- N := Length(Name);
- M.WriteBuffer(N, SizeOf(N));
- M.WriteBuffer(Name[1], N*SizeOf(Name[1]));
- N := Length(Value);
- M.WriteBuffer(N, SizeOf(N));
- M.WriteBuffer(Value[1], N*SizeOf(Value[1]));
- Inc(Count);
- end;
- end;
- end;
- M.Seek(0, soFromBeginning);
- M.WriteBuffer(Count, SizeOf(Count));
- SetString(S, PChar(M.Memory), M.Size div SizeOf(Char));
- finally
- M.Free;
- end;
- end;
- function PackCompiledCodeTextIntoString(const CompiledCodeText: AnsiString): String;
- var
- N: Integer;
- begin
- N := Length(CompiledCodeText);
- if N mod 2 = 1 then
- Inc(N); { This will lead to 1 extra byte being moved but that's ok since it is the #0 }
- N := N div 2;
- SetString(Result, PChar(Pointer(CompiledCodeText)), N);
- end;
- procedure RegError(const Func: TRegErrorFunc; const RootKey: HKEY;
- const KeyName: String; const ErrorCode: DWORD);
- const
- ErrorMsgs: array[TRegErrorFunc] of TSetupMessageID =
- (msgErrorRegWriteKey, msgErrorRegCreateKey, msgErrorRegOpenKey);
- FuncNames: array[TRegErrorFunc] of String =
- ('RegSetValueEx', 'RegCreateKeyEx', 'RegOpenKeyEx');
- begin
- raise Exception.Create(FmtSetupMessage(ErrorMsgs[Func],
- [GetRegRootKeyName(RootKey), KeyName]) + SNewLine2 +
- FmtSetupMessage(msgErrorFunctionFailedWithMessage,
- [FuncNames[Func], IntToStr(ErrorCode), Win32ErrorString(DWORD(ErrorCode))]));
- end;
- procedure WriteMsgData(const F: TFile);
- var
- MsgLangOpts: TMessagesLangOptions;
- LangEntry: PSetupLanguageEntry;
- begin
- FillChar(MsgLangOpts, SizeOf(MsgLangOpts), 0);
- MsgLangOpts.ID := MessagesLangOptionsID;
- { TMessagesLangOptions fields and flags from LangOptions - together these are a simplified
- version of TSetupLanguageEntry }
- StrPLCopy(MsgLangOpts.DialogFontName, LangOptions.DialogFontName,
- (SizeOf(MsgLangOpts.DialogFontName) div SizeOf(MsgLangOpts.DialogFontName[0])) - 1);
- MsgLangOpts.DialogFontSize := LangOptions.DialogFontSize;
- MsgLangOpts.DialogFontBaseScaleWidth := LangOptions.DialogFontBaseScaleWidth;
- MsgLangOpts.DialogFontBaseScaleHeight := LangOptions.DialogFontBaseScaleHeight;
- if LangOptions.RightToLeft then
- Include(MsgLangOpts.Flags, lfRightToLeft);
- { Other TMessagesLangOptions fields and flags - all appearance only }
- MsgLangOpts.WizardSizePercentX := SetupHeader.WizardSizePercentX;
- MsgLangOpts.WizardSizePercentY := SetupHeader.WizardSizePercentY;
- MsgLangOpts.WizardBackColor := OrigSetupHeaderWizardBackColor; { See Setup.MainFunc }
- MsgLangOpts.WizardBackColorDynamicDark := SetupHeader.WizardBackColorDynamicDark;
- MsgLangOpts.WizardLightControlStyling := SetupHeader.WizardLightControlStyling;
- if shWizardModern in SetupHeader.Options then
- Include(MsgLangOpts.Flags, lfWizardModern);
- if shWizardBorderStyled in SetupHeader.Options then
- Include(MsgLangOpts.Flags, lfWizardBorderStyled);
- if shWizardKeepAspectRatio in SetupHeader.Options then
- Include(MsgLangOpts.Flags, lfWizardKeepAspectRatio);
- if shWizardBevelsHidden in SetupHeader.Options then
- Include(MsgLangOpts.Flags, lfWizardBevelsHidden);
- if SetupHeader.WizardDarkStyle = wdsDark then
- Include(MsgLangOpts.Flags, lfWizardDarkStyleDark)
- else if SetupHeader.WizardDarkStyle = wdsDynamic then
- Include(MsgLangOpts.Flags, lfWizardDarkStyleDynamic);
- LangEntry := Entries[seLanguage][ActiveLanguage];
- F.WriteBuffer(LangEntry.Data[1], ULength(LangEntry.Data));
- F.WriteBuffer(MsgLangOpts, SizeOf(MsgLangOpts));
- end;
- procedure MarkExeHeader(const F: TFile; const ModeID: Longint);
- begin
- F.Seek(SetupExeModeOffset);
- F.WriteBuffer(ModeID, SizeOf(ModeID));
- end;
- procedure ProcessInstallDeleteEntries;
- begin
- for var I := 0 to Entries[seInstallDelete].Count-1 do
- with PSetupDeleteEntry(Entries[seInstallDelete][I])^ do
- if ShouldProcessEntry(WizardComponents, WizardTasks, Components, Tasks, Languages, Check) then begin
- DebugNotifyEntry(seInstallDelete, I);
- NotifyBeforeInstallEntry(BeforeInstall);
- case DeleteType of
- dfFiles, dfFilesAndOrSubdirs:
- DelTree(InstallDefaultDisableFsRedir, ExpandConst(Name), False, True, DeleteType = dfFilesAndOrSubdirs, False,
- nil, nil, nil);
- dfDirIfEmpty:
- DelTree(InstallDefaultDisableFsRedir, ExpandConst(Name), True, False, False, False, nil, nil, nil);
- end;
- NotifyAfterInstallEntry(AfterInstall);
- end;
- end;
- procedure ProcessNeedRestartEvent;
- begin
- if (CodeRunner <> nil) and CodeRunner.FunctionExists('NeedRestart', True) then begin
- if not NeedsRestart then begin
- try
- if CodeRunner.RunBooleanFunctions('NeedRestart', [''], bcTrue, False, False) then begin
- NeedsRestart := True;
- Log('Will restart because NeedRestart returned True.');
- end;
- except
- Log('NeedRestart raised an exception.');
- Application.HandleException(nil);
- end;
- end
- else
- Log('Not calling NeedRestart because a restart has already been deemed necessary.');
- end;
- end;
- procedure ProcessComponentEntries;
- begin
- for var I := 0 to Entries[seComponent].Count-1 do begin
- with PSetupComponentEntry(Entries[seComponent][I])^ do begin
- if ShouldProcessEntry(WizardComponents, nil, Name, '', Languages, '') and (coRestart in Options) then begin
- NeedsRestart := True;
- Break;
- end;
- end;
- end;
- end;
- procedure ProcessTasksEntries;
- begin
- for var I := 0 to Entries[seTask].Count-1 do begin
- with PSetupTaskEntry(Entries[seTask][I])^ do begin
- if ShouldProcessEntry(nil, WizardTasks, '', Name, Languages, '') and (toRestart in Options) then begin
- NeedsRestart := True;
- Break;
- end;
- end;
- end;
- end;
- procedure ShutdownApplications;
- const
- ERROR_FAIL_SHUTDOWN = 351;
- ForcedStrings: array [Boolean] of String = ('', ' (forced)');
- ForcedActionFlag: array [Boolean] of ULONG = (0, RmForceShutdown);
- var
- Forced: Boolean;
- Error: DWORD;
- begin
- Forced := InitForceCloseApplications or
- ((shForceCloseApplications in SetupHeader.Options) and not InitNoForceCloseApplications);
- Log('Shutting down applications using our files.' + ForcedStrings[Forced]);
- RmDoRestart := True;
- Error := RmShutdown(RmSessionHandle, ForcedActionFlag[Forced], nil);
- while Error = ERROR_FAIL_SHUTDOWN do begin
- Log('Some applications could not be shut down.');
- if AbortRetryIgnoreTaskDialogMsgBox(
- SetupMessages[msgErrorCloseApplications],
- [SetupMessages[msgAbortRetryIgnoreRetry], SetupMessages[msgAbortRetryIgnoreIgnore], SetupMessages[msgAbortRetryIgnoreCancel]]) then
- Break;
- Log('Retrying to shut down applications using our files.' + ForcedStrings[Forced]);
- Error := RmShutdown(RmSessionHandle, ForcedActionFlag[Forced], nil);
- end;
- { Close session on all errors except for ERROR_FAIL_SHUTDOWN, should still call RmRestart in that case. }
- if (Error <> ERROR_SUCCESS) and (Error <> ERROR_FAIL_SHUTDOWN) then begin
- RmEndSession(RmSessionHandle);
- LogFmt('RmShutdown returned an error: %d', [Error]);
- RmDoRestart := False;
- end;
- end;
- end.
|