| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152 |
- unit Setup.InstFunc;
- {
- Inno Setup
- Copyright (C) 1997-2025 Jordan Russell
- Portions by Martijn Laan
- For conditions of distribution and use, see LICENSE.TXT.
- Misc. installation functions. Used only by the Setup project.
- }
- interface
- uses
- Windows, SysUtils, Diagnostics, SHA256,
- Shared.CommonFunc, Shared.FileClass,
- Setup.DownloadFileFunc, Compression.SevenZipDecoder;
- type
- PSimpleStringListArray = ^TSimpleStringListArray;
- TSimpleStringListArray = array[0..$7FFFFFFF div SizeOf(String) - 1] of String;
- TSimpleStringList = class
- private
- FList: PSimpleStringListArray;
- FCount, FCapacity: Integer;
- function Get(Index: Integer): String;
- procedure SetCapacity(NewCapacity: Integer);
- public
- destructor Destroy; override;
- procedure Add(const S: String);
- procedure AddIfDoesntExist(const S: String);
- procedure Clear;
- function IndexOf(const S: String): Integer;
- property Count: Integer read FCount;
- property Items[Index: Integer]: String read Get; default;
- end;
- TDeleteDirProc = function(const DisableFsRedir: Boolean; const DirName: String;
- const Param: Pointer): Boolean;
- TDeleteFileProc = function(const DisableFsRedir: Boolean; const FileName: String;
- const Param: Pointer): Boolean;
- TEnumFROFilenamesProc = procedure(const Filename: String; Param: Pointer);
- { Must keep this in synch with Compiler.ScriptFunc.pas: }
- TExecWait = (ewNoWait, ewWaitUntilTerminated, ewWaitUntilIdle);
- { Only reports progress at start or finish, or if at least 50 ms passed since last report }
- TProgressThrottler = class
- private
- FOnDownloadProgress: TOnDownloadProgress;
- FOnExtractionProgress: TOnExtractionProgress;
- FStopWatch: TStopWatch;
- FLastOkProgress: Int64;
- function ThrottleOk(const Progress, ProgressMax: Int64): Boolean;
- public
- constructor Create(const OnDownloadProgress: TOnDownloadProgress); overload;
- constructor Create(const OnExtractionProgress: TOnExtractionProgress); overload;
- procedure Reset;
- function OnDownloadProgress(const Url, BaseName: string; const Progress, ProgressMax: Int64): Boolean;
- function OnExtractionProgress(const ArchiveName, FileName: string; const Progress, ProgressMax: Int64): Boolean;
- end;
- function CheckForMutexes(const Mutexes: String): Boolean;
- procedure CreateMutexes(const Mutexes: String);
- function DecrementSharedCount(const RegView: TRegView; const Filename: String): Boolean;
- function DelTree(const DisableFsRedir: Boolean; const Path: String;
- const IsDir, DeleteFiles, DeleteSubdirsAlso, BreakOnError: Boolean;
- const DeleteDirProc: TDeleteDirProc; const DeleteFileProc: TDeleteFileProc;
- const Param: Pointer): Boolean;
- procedure EnumFileReplaceOperationsFilenames(const EnumFunc: TEnumFROFilenamesProc;
- Param: Pointer);
- function GetComputerNameString: String;
- function GetFileDateTime(const DisableFsRedir: Boolean; const Filename: String;
- var DateTime: TFileTime): Boolean;
- function GetSHA256OfFile(const DisableFsRedir: Boolean; const Filename: String): TSHA256Digest; overload;
- function GetSHA256OfFile(const F: TFile): TSHA256Digest; overload;
- function GetSHA256OfAnsiString(const S: AnsiString): TSHA256Digest;
- function GetSHA256OfUnicodeString(const S: UnicodeString): TSHA256Digest;
- function GetRegRootKeyName(const RootKey: HKEY): String;
- function GetSpaceOnDisk(const DisableFsRedir: Boolean; const DriveRoot: String;
- var FreeBytes, TotalBytes: Int64): Boolean;
- function GetSpaceOnNearestMountPoint(const DisableFsRedir: Boolean;
- const StartDir: String; var FreeBytes, TotalBytes: Int64): Boolean;
- function GetUserNameString: String;
- procedure IncrementSharedCount(const RegView: TRegView; const Filename: String;
- const AlreadyExisted: Boolean);
- function InstExec(const DisableFsRedir: Boolean; const Filename, Params: String;
- WorkingDir: String; const Wait: TExecWait; const ShowCmd: Integer;
- const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
- var ResultCode: DWORD): Boolean;
- function InstShellExec(const Verb, Filename, Params: String; WorkingDir: String;
- const Wait: TExecWait; const ShowCmd: Integer;
- const ProcessMessagesProc: TProcedure; var ResultCode: DWORD): Boolean;
- procedure InternalError(const Id: String);
- procedure InternalErrorFmt(const S: String; const Args: array of const);
- function IsDirEmpty(const DisableFsRedir: Boolean; const Dir: String): Boolean;
- function IsProtectedSystemFile(const DisableFsRedir: Boolean;
- const Filename: String): Boolean;
- function MakePendingFileRenameOperationsChecksum: TSHA256Digest;
- function ModifyPifFile(const Filename: String; const CloseOnExit: Boolean): Boolean;
- procedure RaiseOleError(const FunctionName: String; const ResultCode: HRESULT);
- procedure RefreshEnvironment;
- function ReplaceSystemDirWithSysWow64(const Path: String): String;
- function ReplaceSystemDirWithSysNative(Path: String; const IsWin64: Boolean): String;
- procedure UnregisterFont(const FontName, FontFilename: String; const PerUserFont: Boolean);
- procedure RestartReplace(const DisableFsRedir: Boolean; TempFile, DestFile: String);
- procedure Win32ErrorMsg(const FunctionName: String);
- procedure Win32ErrorMsgEx(const FunctionName: String; const ErrorCode: DWORD);
- function ForceDirectories(const DisableFsRedir: Boolean; Dir: String): Boolean;
- procedure AddAttributesToFile(const DisableFsRedir: Boolean; const Filename: String; Attribs: Integer);
- implementation
- uses
- Messages, ShellApi, Classes, RegStr, Math,
- PathFunc, UnsignedFunc,
- Shared.SetupTypes, Shared.SetupMessageIDs,
- SetupLdrAndSetup.InstFunc, SetupLdrAndSetup.Messages, Setup.RedirFunc;
- procedure InternalError(const Id: String);
- begin
- raise Exception.Create(FmtSetupMessage1(msgErrorInternal2, Id));
- end;
- procedure InternalErrorFmt(const S: String; const Args: array of const);
- begin
- InternalError(Format(S, Args));
- end;
- procedure Win32ErrorMsgEx(const FunctionName: String; const ErrorCode: DWORD);
- begin
- raise Exception.Create(FmtSetupMessage(msgErrorFunctionFailedWithMessage,
- [FunctionName, IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
- end;
- procedure Win32ErrorMsg(const FunctionName: String);
- begin
- Win32ErrorMsgEx(FunctionName, GetLastError);
- end;
- procedure RaiseOleError(const FunctionName: String; const ResultCode: HRESULT);
- begin
- raise Exception.Create(FmtSetupMessage(msgErrorFunctionFailedWithMessage,
- [FunctionName, IntToHexStr8(ResultCode), Win32ErrorString(DWORD(ResultCode))]));
- end;
- function GetRegRootKeyName(const RootKey: HKEY): String;
- begin
- case UInt32(RootKey) of
- UInt32(HKEY_AUTO): InternalError('GetRegRootKeyName called for HKEY_AUTO');
- UInt32(HKEY_CLASSES_ROOT): Result := 'HKEY_CLASSES_ROOT';
- UInt32(HKEY_CURRENT_USER): Result := 'HKEY_CURRENT_USER';
- UInt32(HKEY_LOCAL_MACHINE): Result := 'HKEY_LOCAL_MACHINE';
- UInt32(HKEY_USERS): Result := 'HKEY_USERS';
- UInt32(HKEY_PERFORMANCE_DATA): Result := 'HKEY_PERFORMANCE_DATA';
- UInt32(HKEY_CURRENT_CONFIG): Result := 'HKEY_CURRENT_CONFIG';
- UInt32(HKEY_DYN_DATA): Result := 'HKEY_DYN_DATA';
- else
- { unknown - shouldn't get here }
- Result := Format('[%x]', [UInt32(RootKey)]);
- end;
- end;
- function ReplaceSystemDirWithSysWow64(const Path: String): String;
- { If the user is running 64-bit Windows and Path begins with
- 'x:\windows\system32' it replaces it with 'x:\windows\syswow64', like the
- file system redirector would do. Otherwise, Path is returned unchanged. }
- var
- SysWow64Dir, SysDir: String;
- L: Integer;
- begin
- SysWow64Dir := GetSysWow64Dir;
- if SysWow64Dir <> '' then begin
- SysDir := GetSystemDir;
- { x:\windows\system32 -> x:\windows\syswow64
- x:\windows\system32\ -> x:\windows\syswow64\
- x:\windows\system32\filename -> x:\windows\syswow64\filename
- x:\windows\system32x -> x:\windows\syswow64x <- yes, like Windows! }
- L := Length(SysDir);
- if (Length(Path) = L) or
- ((Length(Path) > L) and not PathCharIsTrailByte(Path, L+1)) then begin
- { ^ avoid splitting a double-byte character }
- if PathCompare(Copy(Path, 1, L), SysDir) = 0 then begin
- Result := SysWow64Dir + Copy(Path, L+1, Maxint);
- Exit;
- end;
- end;
- end;
- Result := Path;
- end;
- function ReplaceSystemDirWithSysNative(Path: String; const IsWin64: Boolean): String;
- { If Path begins with 'x:\windows\system32\' it replaces it with
- 'x:\windows\sysnative\' and if Path equals 'x:\windows\system32'
- it replaces it with 'x:\windows\sysnative'. Otherwise, Path is
- returned unchanged. }
- var
- SysNativeDir, SysDir: String;
- L: Integer;
- begin
- SysNativeDir := GetSysNativeDir(IsWin64);
- if SysNativeDir <> '' then begin
- SysDir := GetSystemDir;
- if PathCompare(Path, SysDir) = 0 then begin
- { x:\windows\system32 -> x:\windows\sysnative }
- Result := SysNativeDir;
- Exit;
- end else begin
- { x:\windows\system32\ -> x:\windows\sysnative\
- x:\windows\system32\filename -> x:\windows\sysnative\filename }
- SysDir := AddBackslash(SysDir);
- L := Length(SysDir);
- if (Length(Path) = L) or
- ((Length(Path) > L) and not PathCharIsTrailByte(Path, L+1)) then begin
- { ^ avoid splitting a double-byte character }
- if PathCompare(Copy(Path, 1, L), SysDir) = 0 then begin
- Result := SysNativeDir + Copy(Path, L, Maxint);
- Exit;
- end;
- end;
- end;
- end;
- Result := Path;
- end;
- procedure RestartReplace(const DisableFsRedir: Boolean; TempFile, DestFile: String);
- { Renames TempFile to DestFile the next time Windows is started. If DestFile
- already existed, it will be overwritten. If DestFile is '' then TempFile
- will be deleted.. }
- begin
- TempFile := PathExpand(TempFile);
- if DestFile <> '' then
- DestFile := PathExpand(DestFile);
- if not DisableFsRedir then begin
- { Work around WOW64 bug present in the IA64 and x64 editions of Windows
- XP (3790) and Server 2003 prior to SP1 RC2: MoveFileEx writes filenames
- to the registry verbatim without mapping system32->syswow64. }
- TempFile := ReplaceSystemDirWithSysWow64(TempFile);
- if DestFile <> '' then
- DestFile := ReplaceSystemDirWithSysWow64(DestFile);
- end;
- if not MoveFileExRedir(DisableFsRedir, TempFile, DestFile,
- MOVEFILE_DELAY_UNTIL_REBOOT or MOVEFILE_REPLACE_EXISTING) then
- Win32ErrorMsg('MoveFileEx');
- end;
- function DelTree(const DisableFsRedir: Boolean; const Path: String;
- const IsDir, DeleteFiles, DeleteSubdirsAlso, BreakOnError: Boolean;
- const DeleteDirProc: TDeleteDirProc; const DeleteFileProc: TDeleteFileProc;
- const Param: Pointer): Boolean;
- { Deletes the specified directory including all files and subdirectories in
- it (including those with hidden, system, and read-only attributes). Returns
- True if it was able to successfully remove everything. If BreakOnError is
- set to True it will stop and return False the first time a delete failed or
- DeleteDirProc/DeleteFileProc returned False. }
- var
- BasePath, FindSpec: String;
- H: THandle;
- FindData: TWin32FindData;
- S: String;
- begin
- Result := True;
- if DeleteFiles and
- (not IsDir or IsDirectoryAndNotReparsePointRedir(DisableFsRedir, Path)) then begin
- if IsDir then begin
- BasePath := AddBackslash(Path);
- FindSpec := BasePath + '*';
- end
- else begin
- BasePath := PathExtractPath(Path);
- FindSpec := Path;
- end;
- H := FindFirstFileRedir(DisableFsRedir, FindSpec, FindData);
- if H <> INVALID_HANDLE_VALUE then begin
- try
- repeat
- S := FindData.cFileName;
- if (S <> '.') and (S <> '..') then begin
- if FindData.dwFileAttributes and FILE_ATTRIBUTE_READONLY <> 0 then begin
- { Strip the read-only attribute if this is a file, or if it's a
- directory and we're deleting subdirectories also }
- if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0) or DeleteSubdirsAlso then
- SetFileAttributesRedir(DisableFsRedir, BasePath + S,
- FindData.dwFileAttributes and not FILE_ATTRIBUTE_READONLY);
- end;
- if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
- if Assigned(DeleteFileProc) then begin
- if not DeleteFileProc(DisableFsRedir, BasePath + S, Param) then
- Result := False;
- end
- else begin
- if not DeleteFileRedir(DisableFsRedir, BasePath + S) then
- Result := False;
- end;
- end
- else begin
- if DeleteSubdirsAlso then
- if not DelTree(DisableFsRedir, BasePath + S, True, True, True, BreakOnError,
- DeleteDirProc, DeleteFileProc, Param) then
- Result := False;
- end;
- end;
- until (BreakOnError and not Result) or not FindNextFile(H, FindData);
- finally
- Windows.FindClose(H);
- end;
- end;
- end;
- if (not BreakOnError or Result) and IsDir then begin
- if Assigned(DeleteDirProc) then begin
- if not DeleteDirProc(DisableFsRedir, Path, Param) then
- Result := False;
- end
- else begin
- if not RemoveDirectoryRedir(DisableFsRedir, Path) then
- Result := False;
- end;
- end;
- end;
- function IsDirEmpty(const DisableFsRedir: Boolean; const Dir: String): Boolean;
- { Returns True if Dir contains no files or subdirectories.
- Note: If Dir does not exist or lacks list permission, False will be
- returned. }
- var
- H: THandle;
- FindData: TWin32FindData;
- begin
- H := FindFirstFileRedir(DisableFsRedir, AddBackslash(Dir) + '*', FindData);
- if H <> INVALID_HANDLE_VALUE then begin
- try
- Result := True;
- while True do begin
- if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
- { Found a file }
- Result := False;
- Break;
- end;
- if (StrComp(FindData.cFileName, '.') <> 0) and
- (StrComp(FindData.cFileName, '..') <> 0) then begin
- { Found a subdirectory }
- Result := False;
- Break;
- end;
- if not FindNextFile(H, FindData) then begin
- if GetLastError <> ERROR_NO_MORE_FILES then begin
- { Exited the loop early due to some unexpected error. The directory
- might not be empty, so return False }
- Result := False;
- end;
- Break;
- end;
- end;
- finally
- Windows.FindClose(H);
- end;
- end
- else begin
- { The directory may not exist, or it may lack list permission }
- Result := False;
- end;
- end;
- procedure IncrementSharedCount(const RegView: TRegView; const Filename: String;
- const AlreadyExisted: Boolean);
- const
- SharedDLLsKey = REGSTR_PATH_SETUP + '\SharedDLLs'; {don't localize}
- var
- K: HKEY;
- Disp, Size, CurType, NewType: DWORD;
- CountStr: String;
- FilenameP: PChar;
- begin
- const ErrorCode = Cardinal(RegCreateKeyExView(RegView, HKEY_LOCAL_MACHINE, SharedDLLsKey, 0, nil,
- REG_OPTION_NON_VOLATILE, KEY_QUERY_VALUE or KEY_SET_VALUE, nil, K, @Disp));
- if ErrorCode <> ERROR_SUCCESS then
- raise Exception.Create(FmtSetupMessage(msgErrorRegOpenKey,
- [GetRegRootKeyName(HKEY_LOCAL_MACHINE), SharedDLLsKey]) + SNewLine2 +
- FmtSetupMessage(msgErrorFunctionFailedWithMessage,
- ['RegCreateKeyEx', IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
- FilenameP := PChar(Filename);
- var Count := 0;
- NewType := REG_DWORD;
- try
- if RegQueryValueEx(K, FilenameP, nil, @CurType, nil, @Size) = ERROR_SUCCESS then
- case CurType of
- REG_SZ:
- if RegQueryStringValue(K, FilenameP, CountStr) then begin
- Count := StrToInt(CountStr);
- NewType := REG_SZ;
- end;
- REG_BINARY: begin
- if (Size >= 1) and (Size <= 4) then begin
- if RegQueryValueEx(K, FilenameP, nil, nil, PByte(@Count), @Size) <> ERROR_SUCCESS then
- { ^ relies on the high 3 bytes of Count being initialized to 0 }
- Abort;
- NewType := REG_BINARY;
- end;
- end;
- REG_DWORD: begin
- Size := SizeOf(DWORD);
- if RegQueryValueEx(K, FilenameP, nil, nil, PByte(@Count), @Size) <> ERROR_SUCCESS then
- Abort;
- end;
- end;
- except
- Count := 0;
- end;
- if Count < 0 then
- Count := 0; { just in case... }
- if (Count = 0) and AlreadyExisted then
- Inc(Count);
- Inc(Count);
- case NewType of
- REG_SZ: begin
- CountStr := IntToStr(Count);
- RegSetValueEx(K, FilenameP, 0, NewType, PChar(CountStr), (ULength(CountStr)+1)*SizeOf(CountStr[1]));
- end;
- REG_BINARY, REG_DWORD:
- RegSetValueEx(K, FilenameP, 0, NewType, @Count, SizeOf(Count));
- end;
- RegCloseKey(K);
- end;
- function DecrementSharedCount(const RegView: TRegView;
- const Filename: String): Boolean;
- { Attempts to decrement the shared file reference count of Filename. Returns
- True if the count reached zero (meaning it's OK to delete the file). }
- const
- SharedDLLsKey = REGSTR_PATH_SETUP + '\SharedDLLs'; {don't localize}
- var
- K: HKEY;
- CountRead: Boolean;
- CurType, Size: DWORD;
- CountStr: String;
- begin
- Result := False;
- const ErrorCode = Cardinal(RegOpenKeyExView(RegView, HKEY_LOCAL_MACHINE, SharedDLLsKey, 0,
- KEY_QUERY_VALUE or KEY_SET_VALUE, K));
- if ErrorCode = ERROR_FILE_NOT_FOUND then
- Exit;
- if ErrorCode <> ERROR_SUCCESS then
- raise Exception.Create(FmtSetupMessage(msgErrorRegOpenKey,
- [GetRegRootKeyName(HKEY_LOCAL_MACHINE), SharedDLLsKey]) + SNewLine2 +
- FmtSetupMessage(msgErrorFunctionFailedWithMessage,
- ['RegOpenKeyEx', IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
- try
- if RegQueryValueEx(K, PChar(Filename), nil, @CurType, nil, @Size) <> ERROR_SUCCESS then
- Exit;
- CountRead := False;
- var Count := 0;
- try
- case CurType of
- REG_SZ:
- if RegQueryStringValue(K, PChar(Filename), CountStr) then begin
- Count := StrToInt(CountStr);
- CountRead := True;
- end;
- REG_BINARY: begin
- if (Size >= 1) and (Size <= 4) then begin
- if RegQueryValueEx(K, PChar(Filename), nil, nil, PByte(@Count), @Size) = ERROR_SUCCESS then
- { ^ relies on the high 3 bytes of Count being initialized to 0 }
- CountRead := True;
- end;
- end;
- REG_DWORD: begin
- Size := SizeOf(DWORD);
- if RegQueryValueEx(K, PChar(Filename), nil, nil, PByte(@Count), @Size) = ERROR_SUCCESS then
- CountRead := True;
- end;
- end;
- except
- { don't propagate exceptions (e.g. from StrToInt) }
- end;
- { If we failed to read the count, or it's in some type we don't recognize,
- don't touch it }
- if not CountRead then
- Exit;
- Dec(Count);
- if Count <= 0 then begin
- Result := True;
- RegDeleteValue(K, PChar(Filename));
- end
- else begin
- case CurType of
- REG_SZ: begin
- CountStr := IntToStr(Count);
- RegSetValueEx(K, PChar(Filename), 0, REG_SZ, PChar(CountStr), (ULength(CountStr)+1)*SizeOf(Char));
- end;
- REG_BINARY, REG_DWORD:
- RegSetValueEx(K, PChar(Filename), 0, CurType, @Count, SizeOf(Count));
- end;
- end;
- finally
- RegCloseKey(K);
- end;
- end;
- function GetFileDateTime(const DisableFsRedir: Boolean; const Filename: String;
- var DateTime: TFileTime): Boolean;
- var
- Handle: THandle;
- FindData: TWin32FindData;
- begin
- Handle := FindFirstFileRedir(DisableFsRedir, Filename, FindData);
- if Handle <> INVALID_HANDLE_VALUE then begin
- Windows.FindClose(Handle);
- if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
- DateTime := FindData.ftLastWriteTime;
- Result := True;
- Exit;
- end;
- end;
- Result := False;
- DateTime.dwLowDateTime := 0;
- DateTime.dwHighDateTime := 0;
- end;
- function GetSHA256OfFile(const DisableFsRedir: Boolean; const Filename: String): TSHA256Digest;
- { Gets SHA-256 sum as a string of the file Filename. An exception will be raised upon
- failure. }
- begin
- const F = TFileRedir.Create(DisableFsRedir, Filename, fdOpenExisting, faRead, fsReadWrite);
- try
- Result := GetSHA256OfFile(F);
- finally
- F.Free;
- end;
- end;
- function GetSHA256OfFile(const F: TFile): TSHA256Digest;
- { Gets SHA-256 sum as a string of the file F. An exception will be raised upon
- failure. }
- var
- Buf: array[0..65535] of Byte;
- begin
- F.Seek(0);
- var Context: TSHA256Context;
- SHA256Init(Context);
- while True do begin
- var NumRead := F.Read(Buf, SizeOf(Buf));
- if NumRead = 0 then
- Break;
- SHA256Update(Context, Buf, NumRead);
- end;
- Result := SHA256Final(Context);
- end;
- function GetSHA256OfAnsiString(const S: AnsiString): TSHA256Digest;
- begin
- Result := SHA256Buf(Pointer(S)^, ULength(S)*SizeOf(S[1]));
- end;
- function GetSHA256OfUnicodeString(const S: UnicodeString): TSHA256Digest;
- begin
- Result := SHA256Buf(Pointer(S)^, ULength(S)*SizeOf(S[1]));
- end;
- var
- SFCInitialized: Boolean;
- SfcIsFileProtectedFunc: function(RpcHandle: THandle; ProtFileName: PWideChar): BOOL; stdcall;
- function IsProtectedSystemFile(const DisableFsRedir: Boolean;
- const Filename: String): Boolean;
- { Returns True if the specified file is protected by Windows File Protection
- (and therefore can't be replaced). }
- var
- M: HMODULE;
- FN: String;
- begin
- if not SFCInitialized then begin
- M := SafeLoadLibrary(PChar(AddBackslash(GetSystemDir) + 'sfc.dll'),
- SEM_NOOPENFILEERRORBOX);
- if M <> 0 then
- SfcIsFileProtectedFunc := GetProcAddress(M, 'SfcIsFileProtected');
- SFCInitialized := True;
- end;
- if Assigned(SfcIsFileProtectedFunc) then begin
- { The function only accepts fully qualified paths. Also, as of
- IA-64 2003 SP1 and x64 XP, it does not respect file system redirection,
- so a call to ReplaceSystemDirWithSysWow64 is needed. }
- FN := PathExpand(Filename);
- if not DisableFsRedir then
- FN := ReplaceSystemDirWithSysWow64(FN);
- Result := SfcIsFileProtectedFunc(0, PChar(FN));
- end
- else
- Result := False; { Should never happen }
- end;
- procedure HandleProcessWait(ProcessHandle: THandle; const Wait: TExecWait;
- const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
- var ResultCode: DWORD);
- begin
- try
- if Wait = ewWaitUntilIdle then begin
- repeat
- ProcessMessagesProc;
- until WaitForInputIdle(ProcessHandle, 50) <> WAIT_TIMEOUT;
- end;
- if Wait = ewWaitUntilTerminated then begin
- { Wait until the process returns, but still process any messages that
- arrive and read the output if requested. }
- var WaitMilliseconds := Cardinal(IfThen(OutputReader <> nil, 50, INFINITE));
- var WaitResult: DWORD := 0;
- repeat
- { Process any pending messages first because MsgWaitForMultipleObjects
- (called below) only returns when *new* messages arrive, unless there's
- a timeout }
- if WaitResult <> WAIT_TIMEOUT then
- ProcessMessagesProc;
- if OutputReader <> nil then
- OutputReader.Read(False);
- WaitResult := MsgWaitForMultipleObjects(1, ProcessHandle, False,
- WaitMilliseconds, QS_ALLINPUT);
- until (WaitResult <> WAIT_OBJECT_0+1) and (WaitResult <> WAIT_TIMEOUT);
- { Process messages once more in case MsgWaitForMultipleObjects saw the
- process terminate and new messages arrive simultaneously. (Can't leave
- unprocessed messages waiting, or a subsequent call to WaitMessage
- won't see them.) }
- ProcessMessagesProc;
- if OutputReader <> nil then
- OutputReader.Read(True);
- end;
- { Get the exit code. Will be set to STILL_ACTIVE if not yet available }
- if not GetExitCodeProcess(ProcessHandle, DWORD(ResultCode)) then
- ResultCode := DWORD(-1); { just in case }
- finally
- CloseHandle(ProcessHandle);
- end;
- end;
- function InstExec(const DisableFsRedir: Boolean; const Filename, Params: String;
- WorkingDir: String; const Wait: TExecWait; const ShowCmd: Integer;
- const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
- var ResultCode: DWORD): Boolean;
- var
- CmdLine: String;
- StartupInfo: TStartupInfo;
- ProcessInfo: TProcessInformation;
- begin
- {Also see IsppFuncs' Exec }
- if Filename = '>' then
- CmdLine := Params
- else begin
- CmdLine := '"' + Filename + '"';
- if Params <> '' then
- CmdLine := CmdLine + ' ' + Params;
- if SameText(PathExtractExt(Filename), '.bat') or
- SameText(PathExtractExt(Filename), '.cmd') then begin
- { Use our own handling for .bat and .cmd files since passing them straight
- to CreateProcess on Windows NT 4.0 has problems: it doesn't properly
- quote the command line it passes to cmd.exe. This didn't work before:
- Filename: "c:\batch.bat"; Parameters: """abc"""
- And other Windows versions might have unknown quirks too, since
- CreateProcess isn't documented to accept .bat files in the first place. }
- { With cmd.exe, the whole command line must be quoted for quoted
- parameters to work. For example, this fails:
- cmd.exe /c "z:\blah.bat" "test"
- But this works:
- cmd.exe /c ""z:\blah.bat" "test""
- }
- CmdLine := '"' + AddBackslash(GetSystemDir) + 'cmd.exe" /C "' + CmdLine + '"'
- end;
- if WorkingDir = '' then
- WorkingDir := PathExtractDir(Filename);
- end;
- FillChar(StartupInfo, SizeOf(StartupInfo), 0);
- StartupInfo.cb := SizeOf(StartupInfo);
- StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
- StartupInfo.wShowWindow := Word(ShowCmd);
- if WorkingDir = '' then
- WorkingDir := GetSystemDir;
- var InheritHandles := False;
- var dwCreationFlags: DWORD := CREATE_DEFAULT_ERROR_MODE;
- if (OutputReader <> nil) and (Wait = ewWaitUntilTerminated) then begin
- OutputReader.UpdateStartupInfo(StartupInfo);
- InheritHandles := True;
- dwCreationFlags := dwCreationFlags or CREATE_NO_WINDOW;
- end;
- Result := CreateProcessRedir(DisableFsRedir, nil, PChar(CmdLine), nil, nil,
- InheritHandles, dwCreationFlags, nil, PChar(WorkingDir),
- StartupInfo, ProcessInfo);
- if not Result then begin
- ResultCode := GetLastError;
- Exit;
- end;
- { Don't need the thread handle, so close it now }
- CloseHandle(ProcessInfo.hThread);
- if OutputReader <> nil then
- OutputReader.NotifyCreateProcessDone;
- HandleProcessWait(ProcessInfo.hProcess, Wait, ProcessMessagesProc,
- OutputReader, ResultCode);
- end;
- function InstShellExec(const Verb, Filename, Params: String; WorkingDir: String;
- const Wait: TExecWait; const ShowCmd: Integer;
- const ProcessMessagesProc: TProcedure; var ResultCode: DWORD): Boolean;
- var
- Info: TShellExecuteInfo;
- begin
- if WorkingDir = '' then begin
- WorkingDir := PathExtractDir(Filename);
- if WorkingDir = '' then
- WorkingDir := GetSystemDir;
- end;
- FillChar(Info, SizeOf(Info), 0);
- Info.cbSize := SizeOf(Info);
- Info.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_FLAG_DDEWAIT or
- SEE_MASK_NOCLOSEPROCESS;
- if Verb <> '' then
- Info.lpVerb := PChar(Verb);
- Info.lpFile := PChar(Filename);
- Info.lpParameters := PChar(Params);
- Info.lpDirectory := PChar(WorkingDir);
- Info.nShow := ShowCmd;
- Result := ShellExecuteEx(@Info);
- if not Result then begin
- ResultCode := GetLastError;
- Exit;
- end;
- ResultCode := STILL_ACTIVE;
- { A process handle won't always be returned, e.g. if DDE was used }
- if Info.hProcess <> 0 then
- HandleProcessWait(Info.hProcess, Wait, ProcessMessagesProc, nil, ResultCode);
- end;
- function CheckForOrCreateMutexes(Mutexes: String; const Create: Boolean): Boolean;
- function MutexPos(const S: String): Integer;
- begin
- for var I := 1 to Length(S) do
- if (S[I] = ',') and ((I = 1) or (S[I-1] <> '\')) then
- Exit(I);
- Result := 0;
- end;
- { Returns True if any of the mutexes in the comma-separated Mutexes string
- exist and Create is False }
- var
- I: Integer;
- M: String;
- H: THandle;
- begin
- Result := False;
- repeat
- I := MutexPos(Mutexes);
- if I = 0 then I := Maxint;
- M := Trim(Copy(Mutexes, 1, I-1));
- if M <> '' then begin
- StringChange(M, '\,', ',');
- if Create then begin
- CreateMutex(M)
- end else begin
- H := OpenMutex(SYNCHRONIZE, False, PChar(M));
- if H <> 0 then begin
- CloseHandle(H);
- Result := True;
- Break;
- end;
- end;
- end;
- Delete(Mutexes, 1, I);
- until Mutexes = '';
- end;
- function CheckForMutexes(const Mutexes: String): Boolean;
- begin
- Result := CheckForOrCreateMutexes(Mutexes, False);
- end;
- procedure CreateMutexes(const Mutexes: String);
- begin
- CheckForOrCreateMutexes(Mutexes, True);
- end;
- function ModifyPifFile(const Filename: String; const CloseOnExit: Boolean): Boolean;
- { Changes the "Close on exit" setting of a .pif file. Returns True if it was
- able to make the change. }
- var
- F: TFile;
- B: Byte;
- begin
- { Note: Specs on the .pif format were taken from
- http://smsoft.chat.ru/en/pifdoc.htm }
- Result := False;
- F := TFile.Create(Filename, fdOpenExisting, faReadWrite, fsNone);
- try
- { Is it a valid .pif file? }
- if F.Size >= $171 then begin
- F.Seek($63);
- F.ReadBuffer(B, SizeOf(B));
- { Toggle the "Close on exit" bit }
- if (B and $10 <> 0) <> CloseOnExit then begin
- B := B xor $10;
- F.Seek($63);
- F.WriteBuffer(B, SizeOf(B));
- end;
- Result := True;
- end;
- finally
- F.Free;
- end;
- end;
- function GetComputerNameString: String;
- var
- Buf: array[0..MAX_COMPUTERNAME_LENGTH] of Char;
- Size: DWORD;
- begin
- Size := SizeOf(Buf) div SizeOf(Buf[0]);
- if GetComputerName(Buf, Size) then
- Result := Buf
- else
- Result := '';
- end;
- function GetUserNameString: String;
- var
- Buf: array[0..256] of Char; { 256 = UNLEN }
- BufSize: DWORD;
- begin
- BufSize := SizeOf(Buf) div SizeOf(Buf[0]);
- if GetUserName(Buf, BufSize) then
- Result := Buf
- else
- Result := '';
- end;
- function MakePendingFileRenameOperationsChecksum: TSHA256Digest;
- { Calculates a checksum of the current PendingFileRenameOperations registry
- value The caller can use this checksum to determine if
- PendingFileRenameOperations was changed (perhaps by another program). }
- var
- Context: TSHA256Context;
- K: HKEY;
- S: String;
- begin
- SHA256Init(Context);
- try
- if RegOpenKeyExView(rvDefault, HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager',
- 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
- if RegQueryMultiStringValue(K, 'PendingFileRenameOperations', S) then
- SHA256Update(Context, S[1], ULength(S)*SizeOf(S[1]));
- { When "PendingFileRenameOperations" is full, it spills over into
- "PendingFileRenameOperations2" }
- if RegQueryMultiStringValue(K, 'PendingFileRenameOperations2', S) then
- SHA256Update(Context, S[1], ULength(S)*SizeOf(S[1]));
- RegCloseKey(K);
- end;
- except
- { don't propagate exceptions }
- end;
- Result := SHA256Final(Context);
- end;
- procedure EnumFileReplaceOperationsFilenames(const EnumFunc: TEnumFROFilenamesProc;
- Param: Pointer);
- { Enumerates all the filenames in the current PendingFileRenameOperations
- registry value or WININIT.INI file. The function does not distinguish between
- source and destination filenames; it enumerates both. }
- procedure DoValue(const K: HKEY; const ValueName: PChar);
- var
- S: String;
- P, PEnd: PChar;
- begin
- if not RegQueryMultiStringValue(K, ValueName, S) then
- Exit;
- P := PChar(S);
- PEnd := P + Length(S);
- while P < PEnd do begin
- if P[0] = '!' then
- { Note: '!' means that MoveFileEx was called with the
- MOVEFILE_REPLACE_EXISTING flag }
- Inc(P)
- else if (P[0] = '*') and CharInSet(P[1], ['1', '2']) then
- { Note: '*1' and '*2' _seem_ to mean
- - On the source filename, *1 means the file exists, *2 otherwise
- - On the destination filename, *1 means the path exists, *2 otherwise }
- Inc(P, 2);
- if StrLComp(P, '\??\', 4) = 0 then begin
- Inc(P, 4);
- if P[0] <> #0 then
- EnumFunc(P, Param);
- end;
- Inc(P, StrLen(P) + 1);
- end;
- end;
- var
- K: HKEY;
- begin
- if RegOpenKeyExView(rvDefault, HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager',
- 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
- try
- DoValue(K, 'PendingFileRenameOperations');
- { When "PendingFileRenameOperations" is full, it spills over into
- "PendingFileRenameOperations2" }
- DoValue(K, 'PendingFileRenameOperations2');
- finally
- RegCloseKey(K);
- end;
- end;
- end;
- procedure UnregisterFont(const FontName, FontFilename: String; const PerUserFont: Boolean);
- var
- RootKey, K: HKEY;
- begin
- if PerUserFont then
- RootKey := HKEY_CURRENT_USER
- else
- RootKey := HKEY_LOCAL_MACHINE;
- if RegOpenKeyExView(rvDefault, RootKey, 'Software\Microsoft\Windows NT\CurrentVersion\Fonts',
- 0, KEY_SET_VALUE, K) = ERROR_SUCCESS then begin
- RegDeleteValue(K, PChar(FontName));
- RegCloseKey(K);
- end;
- if RemoveFontResource(PChar(FontFilename)) then
- SendNotifyMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
- end;
- function GetSpaceOnDisk(const DisableFsRedir: Boolean; const DriveRoot: String;
- var FreeBytes, TotalBytes: Int64): Boolean;
- var
- GetDiskFreeSpaceExFunc: function(lpDirectoryName: PChar;
- lpFreeBytesAvailable: PLargeInteger; lpTotalNumberOfBytes: PLargeInteger;
- lpTotalNumberOfFreeBytes: PLargeInteger): BOOL; stdcall;
- PrevState: TPreviousFsRedirectionState;
- SectorsPerCluster, BytesPerSector, FreeClusters, TotalClusters: Cardinal;
- begin
- { NOTE: The docs claim that GetDiskFreeSpace supports UNC paths on
- Windows 95 OSR2 and later. But that does not seem to be the case in my
- tests; it fails with error 50 on Windows 95 through Me.
- GetDiskFreeSpaceEx, however, *does* succeed with UNC paths, so use it
- if available. }
- GetDiskFreeSpaceExFunc := GetProcAddress(GetModuleHandle(kernel32),
- 'GetDiskFreeSpaceExW');
- if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
- Result := False;
- Exit;
- end;
- try
- if Assigned(@GetDiskFreeSpaceExFunc) then begin
- Result := GetDiskFreeSpaceExFunc(PChar(AddBackslash(PathExpand(DriveRoot))),
- @FreeBytes, @TotalBytes, nil);
- end
- else begin
- Result := GetDiskFreeSpace(PChar(AddBackslash(PathExtractDrive(PathExpand(DriveRoot)))),
- SectorsPerCluster, BytesPerSector, FreeClusters, TotalClusters);
- if Result then begin
- { The result of GetDiskFreeSpace does not cap at 2GB, so we must use a
- 64-bit multiply operation to avoid an overflow. }
- FreeBytes := Int64(BytesPerSector * SectorsPerCluster) * FreeClusters;
- TotalBytes := Int64(BytesPerSector * SectorsPerCluster) * TotalClusters;
- end;
- end;
- finally
- RestoreFsRedirection(PrevState);
- end;
- end;
- function GetSpaceOnNearestMountPoint(const DisableFsRedir: Boolean;
- const StartDir: String; var FreeBytes, TotalBytes: Int64): Boolean;
- { Gets the free and total space available on the specified directory. If that
- fails (e.g. if the directory does not exist), then it strips off the last
- component of the path and tries again. This repeats until it reaches the
- root. Returns True if successful. }
- var
- Dir: String;
- LastLen: Integer;
- begin
- Result := False;
- Dir := RemoveBackslashUnlessRoot(StartDir);
- LastLen := 0;
- while Length(Dir) <> LastLen do begin
- if GetSpaceOnDisk(DisableFsRedir, Dir, FreeBytes, TotalBytes) then begin
- Result := True;
- Break;
- end;
- LastLen := Length(Dir);
- Dir := PathExtractDir(Dir);
- end;
- end;
- procedure RefreshEnvironment;
- { Notifies other applications (Explorer) that environment variables have
- changed. Based on code from KB article 104011. }
- var
- MsgResult: DWORD_PTR;
- begin
- { Note: We originally used SendNotifyMessage to broadcast the message but it
- turned out that while it worked fine on NT 4 and 2000 it didn't work on XP
- -- the string "Environment" in lParam would be garbled on the receiving
- end (why I'm not exactly sure). We now use SendMessageTimeout as directed
- in the KB article 104011. It isn't as elegant since it could cause us to
- be delayed if another app is hung, but it'll have to do. }
- SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
- LPARAM(PChar('Environment')), SMTO_ABORTIFHUNG, 5000, @MsgResult);
- end;
- function ForceDirectories(const DisableFsRedir: Boolean; Dir: String): Boolean;
- begin
- Dir := RemoveBackslashUnlessRoot(Dir);
- if (PathExtractPath(Dir) = Dir) or DirExistsRedir(DisableFsRedir, Dir) then
- Result := True
- else
- Result := ForceDirectories(DisableFsRedir, PathExtractPath(Dir)) and
- CreateDirectoryRedir(DisableFsRedir, Dir);
- end;
- procedure AddAttributesToFile(const DisableFsRedir: Boolean;
- const Filename: String; Attribs: Integer);
- var
- ExistingAttr: DWORD;
- begin
- if Attribs <> 0 then begin
- ExistingAttr := GetFileAttributesRedir(DisableFsRedir, Filename);
- if ExistingAttr <> INVALID_FILE_ATTRIBUTES then
- SetFileAttributesRedir(DisableFsRedir, Filename,
- (ExistingAttr and not FILE_ATTRIBUTE_NORMAL) or DWORD(Attribs));
- end;
- end;
- { TSimpleStringList }
- procedure TSimpleStringList.Add(const S: String);
- var
- Delta: Integer;
- begin
- if FCount = FCapacity then begin
- if FCapacity > 64 then Delta := FCapacity div 4 else
- if FCapacity > 8 then Delta := 16 else
- Delta := 4;
- SetCapacity(FCapacity + Delta);
- end;
- FList^[FCount] := S;
- Inc(FCount);
- end;
- procedure TSimpleStringList.AddIfDoesntExist(const S: String);
- begin
- if IndexOf(S) = -1 then
- Add(S);
- end;
- procedure TSimpleStringList.SetCapacity(NewCapacity: Integer);
- begin
- ReallocMem(FList, NewCapacity * SizeOf(Pointer));
- if NewCapacity > FCapacity then
- FillChar(FList^[FCapacity], (NewCapacity - FCapacity) * SizeOf(Pointer), 0);
- FCapacity := NewCapacity;
- end;
- procedure TSimpleStringList.Clear;
- begin
- if FCount <> 0 then Finalize(FList^[0], FCount);
- FCount := 0;
- SetCapacity(0);
- end;
- function TSimpleStringList.Get(Index: Integer): String;
- begin
- Result := FList^[Index];
- end;
- function TSimpleStringList.IndexOf(const S: String): Integer;
- { Note: This is case-sensitive, unlike TStringList.IndexOf }
- var
- I: Integer;
- begin
- Result := -1;
- for I := 0 to FCount-1 do
- if FList^[I] = S then begin
- Result := I;
- Break;
- end;
- end;
- destructor TSimpleStringList.Destroy;
- begin
- Clear;
- inherited Destroy;
- end;
- { TProgressThrottler }
- constructor TProgressThrottler.Create(const OnDownloadProgress: TOnDownloadProgress);
- begin
- inherited Create;
- FOnDownloadProgress := OnDownloadProgress;
- end;
- constructor TProgressThrottler.Create(const OnExtractionProgress: TOnExtractionProgress);
- begin
- inherited Create;
- FOnExtractionProgress := OnExtractionProgress;
- end;
- procedure TProgressThrottler.Reset;
- begin
- FStopWatch.Stop;
- end;
- function TProgressThrottler.ThrottleOk(const Progress, ProgressMax: Int64): Boolean;
- begin
- if FStopWatch.IsRunning then begin
- Result := ((Progress = ProgressMax) and (FLastOkProgress <> ProgressMax)) or (FStopWatch.ElapsedMilliseconds >= 50);
- if Result then
- FStopWatch.Reset;
- end else begin
- Result := True;
- FStopWatch := TStopwatch.StartNew;
- end;
- if Result then
- FLastOkProgress := Progress;
- end;
- function TProgressThrottler.OnDownloadProgress(const Url, BaseName: string; const Progress,
- ProgressMax: Int64): Boolean;
- begin
- if Assigned(FOnDownloadProgress) and ThrottleOk(Progress, ProgressMax) then begin
- Result := FOnDownloadProgress(Url, BaseName, Progress, ProgressMax)
- end else
- Result := True;
- end;
- function TProgressThrottler.OnExtractionProgress(const ArchiveName, FileName: string;
- const Progress, ProgressMax: Int64): Boolean;
- begin
- if Assigned(FOnExtractionProgress) and ThrottleOk(Progress, ProgressMax) then
- Result := FOnExtractionProgress(ArchiveName, FileName, Progress, ProgressMax)
- else
- Result := True;
- end;
- end.
|