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.Lo >= $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(FreeBytes), @TLargeInteger(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.