12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136 |
- 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, Shared.Int64Em, SHA256, Shared.CommonFunc, Shared.FileClass;
- type
- PSimpleStringListArray = ^TSimpleStringListArray;
- TSimpleStringListArray = array[0..$1FFFFFFE] 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);
- 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 GenerateNonRandomUniqueTempDir(const LimitCurrentUserSidAccess: Boolean;
- Path: String; var TempDir: String): Boolean;
- 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: Integer64): Boolean;
- function GetSpaceOnNearestMountPoint(const DisableFsRedir: Boolean;
- const StartDir: String; var FreeBytes, TotalBytes: Integer64): 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: Integer): Boolean;
- function InstShellExec(const Verb, Filename, Params: String; WorkingDir: String;
- const Wait: TExecWait; const ShowCmd: Integer;
- const ProcessMessagesProc: TProcedure; var ResultCode: Integer): 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 RaiseFunctionFailedError(const FunctionName: String);
- 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 SplitNewParamStr(const Index: Integer; var AName, AValue: String);
- procedure Win32ErrorMsg(const FunctionName: String);
- procedure Win32ErrorMsgEx(const FunctionName: String; const ErrorCode: DWORD);
- function ForceDirectories(const DisableFsRedir: Boolean; Dir: String): Boolean;
- implementation
- uses
- Messages, ShellApi, PathFunc, SetupLdrAndSetup.InstFunc, SetupLdrAndSetup.Messages,
- Shared.SetupMessageIDs, SetupLdrAndSetup.RedirFunc, Shared.SetupTypes,
- Classes, RegStr, Math;
- 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(ResultCode)]));
- end;
- procedure RaiseFunctionFailedError(const FunctionName: String);
- begin
- raise Exception.Create(FmtSetupMessage1(msgErrorFunctionFailedNoCode,
- FunctionName));
- end;
- function GetRegRootKeyName(const RootKey: HKEY): String;
- begin
- case RootKey of
- HKEY_AUTO: InternalError('GetRegRootKeyName called for HKEY_AUTO');
- HKEY_CLASSES_ROOT: Result := 'HKEY_CLASSES_ROOT';
- HKEY_CURRENT_USER: Result := 'HKEY_CURRENT_USER';
- HKEY_LOCAL_MACHINE: Result := 'HKEY_LOCAL_MACHINE';
- HKEY_USERS: Result := 'HKEY_USERS';
- HKEY_PERFORMANCE_DATA: Result := 'HKEY_PERFORMANCE_DATA';
- HKEY_CURRENT_CONFIG: Result := 'HKEY_CURRENT_CONFIG';
- HKEY_DYN_DATA: Result := 'HKEY_DYN_DATA';
- else
- { unknown - shouldn't get here }
- Result := Format('[%x]', [Cardinal(RootKey)]);
- end;
- end;
- function GenerateNonRandomUniqueTempDir(const LimitCurrentUserSidAccess: Boolean;
- Path: String; var TempDir: String): Boolean;
- { Creates a new temporary directory with a non-random name. Returns True if an
- existing directory was re-created. This is called by Uninstall. A non-random
- name is used because the uninstaller EXE isn't able to delete itself; if it were
- random, there would be one directory added each time an uninstaller is run. }
- var
- Rand, RandOrig: Longint; { These are actually NOT random in any way }
- ErrorCode: DWORD;
- begin
- Path := AddBackslash(Path);
- RandOrig := $123456;
- Rand := RandOrig;
- repeat
- Result := False;
- Inc(Rand);
- if Rand > $1FFFFFF then Rand := 0;
- if Rand = RandOrig then
- { practically impossible to go through 33 million possibilities,
- but check "just in case"... }
- raise Exception.Create(FmtSetupMessage1(msgErrorTooManyFilesInDir,
- RemoveBackslashUnlessRoot(Path)));
- { Generate a "random" name }
- TempDir := Path + 'iu-' + IntToBase32(Rand) + '.tmp';
- if DirExists(TempDir) then begin
- if not DeleteDirTree(TempDir) then Continue;
- Result := True;
- end else if NewFileExists(TempDir) then
- if not DeleteFile(TempDir) then Continue;
- if CreateSafeDirectory(LimitCurrentUserSidAccess, TempDir, ErrorCode) then Break;
- if ErrorCode <> ERROR_ALREADY_EXISTS then
- raise Exception.Create(FmtSetupMessage(msgLastErrorMessage,
- [FmtSetupMessage1(msgErrorCreatingDir, TempDir), IntToStr(ErrorCode),
- Win32ErrorString(ErrorCode)]));
- until False; // continue until a new directory was created
- 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
- ErrorCode: Longint;
- K: HKEY;
- Disp, Size, Count, CurType, NewType: DWORD;
- CountStr: String;
- FilenameP: PChar;
- begin
- ErrorCode := 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);
- 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, @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, @Count, @Size) <> ERROR_SUCCESS then
- Abort;
- end;
- end;
- except
- Count := 0;
- end;
- if Integer(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), (Length(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
- ErrorCode: Longint;
- K: HKEY;
- CountRead: Boolean;
- Count, CurType, Size: DWORD;
- CountStr: String;
- begin
- Result := False;
- ErrorCode := 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;
- 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, @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, @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 Integer(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), (Length(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)^, Length(S)*SizeOf(S[1]));
- end;
- function GetSHA256OfUnicodeString(const S: UnicodeString): TSHA256Digest;
- begin
- Result := SHA256Buf(Pointer(S)^, Length(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: Integer);
- 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 := 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 := -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: Integer): 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 := 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: Integer): 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], Length(S)*SizeOf(S[1]));
- { When "PendingFileRenameOperations" is full, it spills over into
- "PendingFileRenameOperations2" }
- if RegQueryMultiStringValue(K, 'PendingFileRenameOperations2', S) then
- SHA256Update(Context, S[1], Length(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);
- 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: Integer64): 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))),
- @TLargeInteger(Int64Rec(FreeBytes)), @TLargeInteger(Int64Rec(TotalBytes)), nil);
- end
- else begin
- Result := GetDiskFreeSpace(PChar(AddBackslash(PathExtractDrive(PathExpand(DriveRoot)))),
- DWORD(SectorsPerCluster), DWORD(BytesPerSector), DWORD(FreeClusters),
- DWORD(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. }
- Multiply32x32to64(BytesPerSector * SectorsPerCluster, FreeClusters,
- FreeBytes);
- Multiply32x32to64(BytesPerSector * SectorsPerCluster, TotalClusters,
- TotalBytes);
- end;
- end;
- finally
- RestoreFsRedirection(PrevState);
- end;
- end;
- function GetSpaceOnNearestMountPoint(const DisableFsRedir: Boolean;
- const StartDir: String; var FreeBytes, TotalBytes: Integer64): 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;
- procedure SplitNewParamStr(const Index: Integer; var AName, AValue: String);
- { Reads a command line parameter. If it is in the form "/PARAM=VALUE" then
- AName is set to "/PARAM=" and AValue is set to "VALUE". Otherwise, the full
- parameter is stored in AName, and AValue is set to an empty string. }
- var
- S: String;
- P: Integer;
- begin
- S := NewParamStr(Index);
- if (S <> '') and (S[1] = '/') then begin
- P := PathPos('=', S);
- if P <> 0 then begin
- AName := Copy(S, 1, P);
- AValue := Copy(S, P+1, Maxint);
- Exit;
- end;
- end;
- AName := S;
- AValue := '';
- 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;
- { 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;
- end.
|