Setup.InstFunc.pas 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136
  1. unit Setup.InstFunc;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Misc. installation functions. Used only by the Setup project.
  8. }
  9. interface
  10. uses
  11. Windows, SysUtils, Shared.Int64Em, SHA256, Shared.CommonFunc, Shared.FileClass;
  12. type
  13. PSimpleStringListArray = ^TSimpleStringListArray;
  14. TSimpleStringListArray = array[0..$1FFFFFFE] of String;
  15. TSimpleStringList = class
  16. private
  17. FList: PSimpleStringListArray;
  18. FCount, FCapacity: Integer;
  19. function Get(Index: Integer): String;
  20. procedure SetCapacity(NewCapacity: Integer);
  21. public
  22. destructor Destroy; override;
  23. procedure Add(const S: String);
  24. procedure AddIfDoesntExist(const S: String);
  25. procedure Clear;
  26. function IndexOf(const S: String): Integer;
  27. property Count: Integer read FCount;
  28. property Items[Index: Integer]: String read Get; default;
  29. end;
  30. TDeleteDirProc = function(const DisableFsRedir: Boolean; const DirName: String;
  31. const Param: Pointer): Boolean;
  32. TDeleteFileProc = function(const DisableFsRedir: Boolean; const FileName: String;
  33. const Param: Pointer): Boolean;
  34. TEnumFROFilenamesProc = procedure(const Filename: String; Param: Pointer);
  35. { Must keep this in synch with Compiler.ScriptFunc.pas: }
  36. TExecWait = (ewNoWait, ewWaitUntilTerminated, ewWaitUntilIdle);
  37. function CheckForMutexes(const Mutexes: String): Boolean;
  38. procedure CreateMutexes(const Mutexes: String);
  39. function DecrementSharedCount(const RegView: TRegView; const Filename: String): Boolean;
  40. function DelTree(const DisableFsRedir: Boolean; const Path: String;
  41. const IsDir, DeleteFiles, DeleteSubdirsAlso, BreakOnError: Boolean;
  42. const DeleteDirProc: TDeleteDirProc; const DeleteFileProc: TDeleteFileProc;
  43. const Param: Pointer): Boolean;
  44. procedure EnumFileReplaceOperationsFilenames(const EnumFunc: TEnumFROFilenamesProc;
  45. Param: Pointer);
  46. function GenerateNonRandomUniqueTempDir(const LimitCurrentUserSidAccess: Boolean;
  47. Path: String; var TempDir: String): Boolean;
  48. function GetComputerNameString: String;
  49. function GetFileDateTime(const DisableFsRedir: Boolean; const Filename: String;
  50. var DateTime: TFileTime): Boolean;
  51. function GetSHA256OfFile(const DisableFsRedir: Boolean; const Filename: String): TSHA256Digest; overload;
  52. function GetSHA256OfFile(const F: TFile): TSHA256Digest; overload;
  53. function GetSHA256OfAnsiString(const S: AnsiString): TSHA256Digest;
  54. function GetSHA256OfUnicodeString(const S: UnicodeString): TSHA256Digest;
  55. function GetRegRootKeyName(const RootKey: HKEY): String;
  56. function GetSpaceOnDisk(const DisableFsRedir: Boolean; const DriveRoot: String;
  57. var FreeBytes, TotalBytes: Integer64): Boolean;
  58. function GetSpaceOnNearestMountPoint(const DisableFsRedir: Boolean;
  59. const StartDir: String; var FreeBytes, TotalBytes: Integer64): Boolean;
  60. function GetUserNameString: String;
  61. procedure IncrementSharedCount(const RegView: TRegView; const Filename: String;
  62. const AlreadyExisted: Boolean);
  63. function InstExec(const DisableFsRedir: Boolean; const Filename, Params: String;
  64. WorkingDir: String; const Wait: TExecWait; const ShowCmd: Integer;
  65. const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
  66. var ResultCode: Integer): Boolean;
  67. function InstShellExec(const Verb, Filename, Params: String; WorkingDir: String;
  68. const Wait: TExecWait; const ShowCmd: Integer;
  69. const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
  70. procedure InternalError(const Id: String);
  71. procedure InternalErrorFmt(const S: String; const Args: array of const);
  72. function IsDirEmpty(const DisableFsRedir: Boolean; const Dir: String): Boolean;
  73. function IsProtectedSystemFile(const DisableFsRedir: Boolean;
  74. const Filename: String): Boolean;
  75. function MakePendingFileRenameOperationsChecksum: TSHA256Digest;
  76. function ModifyPifFile(const Filename: String; const CloseOnExit: Boolean): Boolean;
  77. procedure RaiseFunctionFailedError(const FunctionName: String);
  78. procedure RaiseOleError(const FunctionName: String; const ResultCode: HRESULT);
  79. procedure RefreshEnvironment;
  80. function ReplaceSystemDirWithSysWow64(const Path: String): String;
  81. function ReplaceSystemDirWithSysNative(Path: String; const IsWin64: Boolean): String;
  82. procedure UnregisterFont(const FontName, FontFilename: String; const PerUserFont: Boolean);
  83. procedure RestartReplace(const DisableFsRedir: Boolean; TempFile, DestFile: String);
  84. procedure SplitNewParamStr(const Index: Integer; var AName, AValue: String);
  85. procedure Win32ErrorMsg(const FunctionName: String);
  86. procedure Win32ErrorMsgEx(const FunctionName: String; const ErrorCode: DWORD);
  87. function ForceDirectories(const DisableFsRedir: Boolean; Dir: String): Boolean;
  88. implementation
  89. uses
  90. Messages, ShellApi, PathFunc, SetupLdrAndSetup.InstFunc, SetupLdrAndSetup.Messages,
  91. Shared.SetupMessageIDs, SetupLdrAndSetup.RedirFunc, Shared.SetupTypes,
  92. Classes, RegStr, Math;
  93. procedure InternalError(const Id: String);
  94. begin
  95. raise Exception.Create(FmtSetupMessage1(msgErrorInternal2, Id));
  96. end;
  97. procedure InternalErrorFmt(const S: String; const Args: array of const);
  98. begin
  99. InternalError(Format(S, Args));
  100. end;
  101. procedure Win32ErrorMsgEx(const FunctionName: String; const ErrorCode: DWORD);
  102. begin
  103. raise Exception.Create(FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  104. [FunctionName, IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
  105. end;
  106. procedure Win32ErrorMsg(const FunctionName: String);
  107. begin
  108. Win32ErrorMsgEx(FunctionName, GetLastError);
  109. end;
  110. procedure RaiseOleError(const FunctionName: String; const ResultCode: HRESULT);
  111. begin
  112. raise Exception.Create(FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  113. [FunctionName, IntToHexStr8(ResultCode), Win32ErrorString(ResultCode)]));
  114. end;
  115. procedure RaiseFunctionFailedError(const FunctionName: String);
  116. begin
  117. raise Exception.Create(FmtSetupMessage1(msgErrorFunctionFailedNoCode,
  118. FunctionName));
  119. end;
  120. function GetRegRootKeyName(const RootKey: HKEY): String;
  121. begin
  122. case RootKey of
  123. HKEY_AUTO: InternalError('GetRegRootKeyName called for HKEY_AUTO');
  124. HKEY_CLASSES_ROOT: Result := 'HKEY_CLASSES_ROOT';
  125. HKEY_CURRENT_USER: Result := 'HKEY_CURRENT_USER';
  126. HKEY_LOCAL_MACHINE: Result := 'HKEY_LOCAL_MACHINE';
  127. HKEY_USERS: Result := 'HKEY_USERS';
  128. HKEY_PERFORMANCE_DATA: Result := 'HKEY_PERFORMANCE_DATA';
  129. HKEY_CURRENT_CONFIG: Result := 'HKEY_CURRENT_CONFIG';
  130. HKEY_DYN_DATA: Result := 'HKEY_DYN_DATA';
  131. else
  132. { unknown - shouldn't get here }
  133. Result := Format('[%x]', [Cardinal(RootKey)]);
  134. end;
  135. end;
  136. function GenerateNonRandomUniqueTempDir(const LimitCurrentUserSidAccess: Boolean;
  137. Path: String; var TempDir: String): Boolean;
  138. { Creates a new temporary directory with a non-random name. Returns True if an
  139. existing directory was re-created. This is called by Uninstall. A non-random
  140. name is used because the uninstaller EXE isn't able to delete itself; if it were
  141. random, there would be one directory added each time an uninstaller is run. }
  142. var
  143. Rand, RandOrig: Longint; { These are actually NOT random in any way }
  144. ErrorCode: DWORD;
  145. begin
  146. Path := AddBackslash(Path);
  147. RandOrig := $123456;
  148. Rand := RandOrig;
  149. repeat
  150. Result := False;
  151. Inc(Rand);
  152. if Rand > $1FFFFFF then Rand := 0;
  153. if Rand = RandOrig then
  154. { practically impossible to go through 33 million possibilities,
  155. but check "just in case"... }
  156. raise Exception.Create(FmtSetupMessage1(msgErrorTooManyFilesInDir,
  157. RemoveBackslashUnlessRoot(Path)));
  158. { Generate a "random" name }
  159. TempDir := Path + 'iu-' + IntToBase32(Rand) + '.tmp';
  160. if DirExists(TempDir) then begin
  161. if not DeleteDirTree(TempDir) then Continue;
  162. Result := True;
  163. end else if NewFileExists(TempDir) then
  164. if not DeleteFile(TempDir) then Continue;
  165. if CreateSafeDirectory(LimitCurrentUserSidAccess, TempDir, ErrorCode) then Break;
  166. if ErrorCode <> ERROR_ALREADY_EXISTS then
  167. raise Exception.Create(FmtSetupMessage(msgLastErrorMessage,
  168. [FmtSetupMessage1(msgErrorCreatingDir, TempDir), IntToStr(ErrorCode),
  169. Win32ErrorString(ErrorCode)]));
  170. until False; // continue until a new directory was created
  171. end;
  172. function ReplaceSystemDirWithSysWow64(const Path: String): String;
  173. { If the user is running 64-bit Windows and Path begins with
  174. 'x:\windows\system32' it replaces it with 'x:\windows\syswow64', like the
  175. file system redirector would do. Otherwise, Path is returned unchanged. }
  176. var
  177. SysWow64Dir, SysDir: String;
  178. L: Integer;
  179. begin
  180. SysWow64Dir := GetSysWow64Dir;
  181. if SysWow64Dir <> '' then begin
  182. SysDir := GetSystemDir;
  183. { x:\windows\system32 -> x:\windows\syswow64
  184. x:\windows\system32\ -> x:\windows\syswow64\
  185. x:\windows\system32\filename -> x:\windows\syswow64\filename
  186. x:\windows\system32x -> x:\windows\syswow64x <- yes, like Windows! }
  187. L := Length(SysDir);
  188. if (Length(Path) = L) or
  189. ((Length(Path) > L) and not PathCharIsTrailByte(Path, L+1)) then begin
  190. { ^ avoid splitting a double-byte character }
  191. if PathCompare(Copy(Path, 1, L), SysDir) = 0 then begin
  192. Result := SysWow64Dir + Copy(Path, L+1, Maxint);
  193. Exit;
  194. end;
  195. end;
  196. end;
  197. Result := Path;
  198. end;
  199. function ReplaceSystemDirWithSysNative(Path: String; const IsWin64: Boolean): String;
  200. { If Path begins with 'x:\windows\system32\' it replaces it with
  201. 'x:\windows\sysnative\' and if Path equals 'x:\windows\system32'
  202. it replaces it with 'x:\windows\sysnative'. Otherwise, Path is
  203. returned unchanged. }
  204. var
  205. SysNativeDir, SysDir: String;
  206. L: Integer;
  207. begin
  208. SysNativeDir := GetSysNativeDir(IsWin64);
  209. if SysNativeDir <> '' then begin
  210. SysDir := GetSystemDir;
  211. if PathCompare(Path, SysDir) = 0 then begin
  212. { x:\windows\system32 -> x:\windows\sysnative }
  213. Result := SysNativeDir;
  214. Exit;
  215. end else begin
  216. { x:\windows\system32\ -> x:\windows\sysnative\
  217. x:\windows\system32\filename -> x:\windows\sysnative\filename }
  218. SysDir := AddBackslash(SysDir);
  219. L := Length(SysDir);
  220. if (Length(Path) = L) or
  221. ((Length(Path) > L) and not PathCharIsTrailByte(Path, L+1)) then begin
  222. { ^ avoid splitting a double-byte character }
  223. if PathCompare(Copy(Path, 1, L), SysDir) = 0 then begin
  224. Result := SysNativeDir + Copy(Path, L, Maxint);
  225. Exit;
  226. end;
  227. end;
  228. end;
  229. end;
  230. Result := Path;
  231. end;
  232. procedure RestartReplace(const DisableFsRedir: Boolean; TempFile, DestFile: String);
  233. { Renames TempFile to DestFile the next time Windows is started. If DestFile
  234. already existed, it will be overwritten. If DestFile is '' then TempFile
  235. will be deleted.. }
  236. begin
  237. TempFile := PathExpand(TempFile);
  238. if DestFile <> '' then
  239. DestFile := PathExpand(DestFile);
  240. if not DisableFsRedir then begin
  241. { Work around WOW64 bug present in the IA64 and x64 editions of Windows
  242. XP (3790) and Server 2003 prior to SP1 RC2: MoveFileEx writes filenames
  243. to the registry verbatim without mapping system32->syswow64. }
  244. TempFile := ReplaceSystemDirWithSysWow64(TempFile);
  245. if DestFile <> '' then
  246. DestFile := ReplaceSystemDirWithSysWow64(DestFile);
  247. end;
  248. if not MoveFileExRedir(DisableFsRedir, TempFile, DestFile,
  249. MOVEFILE_DELAY_UNTIL_REBOOT or MOVEFILE_REPLACE_EXISTING) then
  250. Win32ErrorMsg('MoveFileEx');
  251. end;
  252. function DelTree(const DisableFsRedir: Boolean; const Path: String;
  253. const IsDir, DeleteFiles, DeleteSubdirsAlso, BreakOnError: Boolean;
  254. const DeleteDirProc: TDeleteDirProc; const DeleteFileProc: TDeleteFileProc;
  255. const Param: Pointer): Boolean;
  256. { Deletes the specified directory including all files and subdirectories in
  257. it (including those with hidden, system, and read-only attributes). Returns
  258. True if it was able to successfully remove everything. If BreakOnError is
  259. set to True it will stop and return False the first time a delete failed or
  260. DeleteDirProc/DeleteFileProc returned False. }
  261. var
  262. BasePath, FindSpec: String;
  263. H: THandle;
  264. FindData: TWin32FindData;
  265. S: String;
  266. begin
  267. Result := True;
  268. if DeleteFiles and
  269. (not IsDir or IsDirectoryAndNotReparsePointRedir(DisableFsRedir, Path)) then begin
  270. if IsDir then begin
  271. BasePath := AddBackslash(Path);
  272. FindSpec := BasePath + '*';
  273. end
  274. else begin
  275. BasePath := PathExtractPath(Path);
  276. FindSpec := Path;
  277. end;
  278. H := FindFirstFileRedir(DisableFsRedir, FindSpec, FindData);
  279. if H <> INVALID_HANDLE_VALUE then begin
  280. try
  281. repeat
  282. S := FindData.cFileName;
  283. if (S <> '.') and (S <> '..') then begin
  284. if FindData.dwFileAttributes and FILE_ATTRIBUTE_READONLY <> 0 then begin
  285. { Strip the read-only attribute if this is a file, or if it's a
  286. directory and we're deleting subdirectories also }
  287. if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0) or DeleteSubdirsAlso then
  288. SetFileAttributesRedir(DisableFsRedir, BasePath + S,
  289. FindData.dwFileAttributes and not FILE_ATTRIBUTE_READONLY);
  290. end;
  291. if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
  292. if Assigned(DeleteFileProc) then begin
  293. if not DeleteFileProc(DisableFsRedir, BasePath + S, Param) then
  294. Result := False;
  295. end
  296. else begin
  297. if not DeleteFileRedir(DisableFsRedir, BasePath + S) then
  298. Result := False;
  299. end;
  300. end
  301. else begin
  302. if DeleteSubdirsAlso then
  303. if not DelTree(DisableFsRedir, BasePath + S, True, True, True, BreakOnError,
  304. DeleteDirProc, DeleteFileProc, Param) then
  305. Result := False;
  306. end;
  307. end;
  308. until (BreakOnError and not Result) or not FindNextFile(H, FindData);
  309. finally
  310. Windows.FindClose(H);
  311. end;
  312. end;
  313. end;
  314. if (not BreakOnError or Result) and IsDir then begin
  315. if Assigned(DeleteDirProc) then begin
  316. if not DeleteDirProc(DisableFsRedir, Path, Param) then
  317. Result := False;
  318. end
  319. else begin
  320. if not RemoveDirectoryRedir(DisableFsRedir, Path) then
  321. Result := False;
  322. end;
  323. end;
  324. end;
  325. function IsDirEmpty(const DisableFsRedir: Boolean; const Dir: String): Boolean;
  326. { Returns True if Dir contains no files or subdirectories.
  327. Note: If Dir does not exist or lacks list permission, False will be
  328. returned. }
  329. var
  330. H: THandle;
  331. FindData: TWin32FindData;
  332. begin
  333. H := FindFirstFileRedir(DisableFsRedir, AddBackslash(Dir) + '*', FindData);
  334. if H <> INVALID_HANDLE_VALUE then begin
  335. try
  336. Result := True;
  337. while True do begin
  338. if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
  339. { Found a file }
  340. Result := False;
  341. Break;
  342. end;
  343. if (StrComp(FindData.cFileName, '.') <> 0) and
  344. (StrComp(FindData.cFileName, '..') <> 0) then begin
  345. { Found a subdirectory }
  346. Result := False;
  347. Break;
  348. end;
  349. if not FindNextFile(H, FindData) then begin
  350. if GetLastError <> ERROR_NO_MORE_FILES then begin
  351. { Exited the loop early due to some unexpected error. The directory
  352. might not be empty, so return False }
  353. Result := False;
  354. end;
  355. Break;
  356. end;
  357. end;
  358. finally
  359. Windows.FindClose(H);
  360. end;
  361. end
  362. else begin
  363. { The directory may not exist, or it may lack list permission }
  364. Result := False;
  365. end;
  366. end;
  367. procedure IncrementSharedCount(const RegView: TRegView; const Filename: String;
  368. const AlreadyExisted: Boolean);
  369. const
  370. SharedDLLsKey = REGSTR_PATH_SETUP + '\SharedDLLs'; {don't localize}
  371. var
  372. ErrorCode: Longint;
  373. K: HKEY;
  374. Disp, Size, Count, CurType, NewType: DWORD;
  375. CountStr: String;
  376. FilenameP: PChar;
  377. begin
  378. ErrorCode := RegCreateKeyExView(RegView, HKEY_LOCAL_MACHINE, SharedDLLsKey, 0, nil,
  379. REG_OPTION_NON_VOLATILE, KEY_QUERY_VALUE or KEY_SET_VALUE, nil, K, @Disp);
  380. if ErrorCode <> ERROR_SUCCESS then
  381. raise Exception.Create(FmtSetupMessage(msgErrorRegOpenKey,
  382. [GetRegRootKeyName(HKEY_LOCAL_MACHINE), SharedDLLsKey]) + SNewLine2 +
  383. FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  384. ['RegCreateKeyEx', IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
  385. FilenameP := PChar(Filename);
  386. Count := 0;
  387. NewType := REG_DWORD;
  388. try
  389. if RegQueryValueEx(K, FilenameP, nil, @CurType, nil, @Size) = ERROR_SUCCESS then
  390. case CurType of
  391. REG_SZ:
  392. if RegQueryStringValue(K, FilenameP, CountStr) then begin
  393. Count := StrToInt(CountStr);
  394. NewType := REG_SZ;
  395. end;
  396. REG_BINARY: begin
  397. if (Size >= 1) and (Size <= 4) then begin
  398. if RegQueryValueEx(K, FilenameP, nil, nil, @Count, @Size) <> ERROR_SUCCESS then
  399. { ^ relies on the high 3 bytes of Count being initialized to 0 }
  400. Abort;
  401. NewType := REG_BINARY;
  402. end;
  403. end;
  404. REG_DWORD: begin
  405. Size := SizeOf(DWORD);
  406. if RegQueryValueEx(K, FilenameP, nil, nil, @Count, @Size) <> ERROR_SUCCESS then
  407. Abort;
  408. end;
  409. end;
  410. except
  411. Count := 0;
  412. end;
  413. if Integer(Count) < 0 then Count := 0; { just in case... }
  414. if (Count = 0) and AlreadyExisted then
  415. Inc(Count);
  416. Inc(Count);
  417. case NewType of
  418. REG_SZ: begin
  419. CountStr := IntToStr(Count);
  420. RegSetValueEx(K, FilenameP, 0, NewType, PChar(CountStr), (Length(CountStr)+1)*SizeOf(CountStr[1]));
  421. end;
  422. REG_BINARY, REG_DWORD:
  423. RegSetValueEx(K, FilenameP, 0, NewType, @Count, SizeOf(Count));
  424. end;
  425. RegCloseKey(K);
  426. end;
  427. function DecrementSharedCount(const RegView: TRegView;
  428. const Filename: String): Boolean;
  429. { Attempts to decrement the shared file reference count of Filename. Returns
  430. True if the count reached zero (meaning it's OK to delete the file). }
  431. const
  432. SharedDLLsKey = REGSTR_PATH_SETUP + '\SharedDLLs'; {don't localize}
  433. var
  434. ErrorCode: Longint;
  435. K: HKEY;
  436. CountRead: Boolean;
  437. Count, CurType, Size: DWORD;
  438. CountStr: String;
  439. begin
  440. Result := False;
  441. ErrorCode := RegOpenKeyExView(RegView, HKEY_LOCAL_MACHINE, SharedDLLsKey, 0,
  442. KEY_QUERY_VALUE or KEY_SET_VALUE, K);
  443. if ErrorCode = ERROR_FILE_NOT_FOUND then
  444. Exit;
  445. if ErrorCode <> ERROR_SUCCESS then
  446. raise Exception.Create(FmtSetupMessage(msgErrorRegOpenKey,
  447. [GetRegRootKeyName(HKEY_LOCAL_MACHINE), SharedDLLsKey]) + SNewLine2 +
  448. FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  449. ['RegOpenKeyEx', IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
  450. try
  451. if RegQueryValueEx(K, PChar(Filename), nil, @CurType, nil, @Size) <> ERROR_SUCCESS then
  452. Exit;
  453. CountRead := False;
  454. Count := 0;
  455. try
  456. case CurType of
  457. REG_SZ:
  458. if RegQueryStringValue(K, PChar(Filename), CountStr) then begin
  459. Count := StrToInt(CountStr);
  460. CountRead := True;
  461. end;
  462. REG_BINARY: begin
  463. if (Size >= 1) and (Size <= 4) then begin
  464. if RegQueryValueEx(K, PChar(Filename), nil, nil, @Count, @Size) = ERROR_SUCCESS then
  465. { ^ relies on the high 3 bytes of Count being initialized to 0 }
  466. CountRead := True;
  467. end;
  468. end;
  469. REG_DWORD: begin
  470. Size := SizeOf(DWORD);
  471. if RegQueryValueEx(K, PChar(Filename), nil, nil, @Count, @Size) = ERROR_SUCCESS then
  472. CountRead := True;
  473. end;
  474. end;
  475. except
  476. { don't propagate exceptions (e.g. from StrToInt) }
  477. end;
  478. { If we failed to read the count, or it's in some type we don't recognize,
  479. don't touch it }
  480. if not CountRead then
  481. Exit;
  482. Dec(Count);
  483. if Integer(Count) <= 0 then begin
  484. Result := True;
  485. RegDeleteValue(K, PChar(Filename));
  486. end
  487. else begin
  488. case CurType of
  489. REG_SZ: begin
  490. CountStr := IntToStr(Count);
  491. RegSetValueEx(K, PChar(Filename), 0, REG_SZ, PChar(CountStr), (Length(CountStr)+1)*SizeOf(Char));
  492. end;
  493. REG_BINARY, REG_DWORD:
  494. RegSetValueEx(K, PChar(Filename), 0, CurType, @Count, SizeOf(Count));
  495. end;
  496. end;
  497. finally
  498. RegCloseKey(K);
  499. end;
  500. end;
  501. function GetFileDateTime(const DisableFsRedir: Boolean; const Filename: String;
  502. var DateTime: TFileTime): Boolean;
  503. var
  504. Handle: THandle;
  505. FindData: TWin32FindData;
  506. begin
  507. Handle := FindFirstFileRedir(DisableFsRedir, Filename, FindData);
  508. if Handle <> INVALID_HANDLE_VALUE then begin
  509. Windows.FindClose(Handle);
  510. if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
  511. DateTime := FindData.ftLastWriteTime;
  512. Result := True;
  513. Exit;
  514. end;
  515. end;
  516. Result := False;
  517. DateTime.dwLowDateTime := 0;
  518. DateTime.dwHighDateTime := 0;
  519. end;
  520. function GetSHA256OfFile(const DisableFsRedir: Boolean; const Filename: String): TSHA256Digest;
  521. { Gets SHA-256 sum as a string of the file Filename. An exception will be raised upon
  522. failure. }
  523. begin
  524. const F = TFileRedir.Create(DisableFsRedir, Filename, fdOpenExisting, faRead, fsReadWrite);
  525. try
  526. Result := GetSHA256OfFile(F);
  527. finally
  528. F.Free;
  529. end;
  530. end;
  531. function GetSHA256OfFile(const F: TFile): TSHA256Digest;
  532. { Gets SHA-256 sum as a string of the file F. An exception will be raised upon
  533. failure. }
  534. var
  535. Buf: array[0..65535] of Byte;
  536. begin
  537. F.Seek(0);
  538. var Context: TSHA256Context;
  539. SHA256Init(Context);
  540. while True do begin
  541. var NumRead := F.Read(Buf, SizeOf(Buf));
  542. if NumRead = 0 then
  543. Break;
  544. SHA256Update(Context, Buf, NumRead);
  545. end;
  546. Result := SHA256Final(Context);
  547. end;
  548. function GetSHA256OfAnsiString(const S: AnsiString): TSHA256Digest;
  549. begin
  550. Result := SHA256Buf(Pointer(S)^, Length(S)*SizeOf(S[1]));
  551. end;
  552. function GetSHA256OfUnicodeString(const S: UnicodeString): TSHA256Digest;
  553. begin
  554. Result := SHA256Buf(Pointer(S)^, Length(S)*SizeOf(S[1]));
  555. end;
  556. var
  557. SFCInitialized: Boolean;
  558. SfcIsFileProtectedFunc: function(RpcHandle: THandle; ProtFileName: PWideChar): BOOL; stdcall;
  559. function IsProtectedSystemFile(const DisableFsRedir: Boolean;
  560. const Filename: String): Boolean;
  561. { Returns True if the specified file is protected by Windows File Protection
  562. (and therefore can't be replaced). }
  563. var
  564. M: HMODULE;
  565. FN: String;
  566. begin
  567. if not SFCInitialized then begin
  568. M := SafeLoadLibrary(PChar(AddBackslash(GetSystemDir) + 'sfc.dll'),
  569. SEM_NOOPENFILEERRORBOX);
  570. if M <> 0 then
  571. SfcIsFileProtectedFunc := GetProcAddress(M, 'SfcIsFileProtected');
  572. SFCInitialized := True;
  573. end;
  574. if Assigned(SfcIsFileProtectedFunc) then begin
  575. { The function only accepts fully qualified paths. Also, as of
  576. IA-64 2003 SP1 and x64 XP, it does not respect file system redirection,
  577. so a call to ReplaceSystemDirWithSysWow64 is needed. }
  578. FN := PathExpand(Filename);
  579. if not DisableFsRedir then
  580. FN := ReplaceSystemDirWithSysWow64(FN);
  581. Result := SfcIsFileProtectedFunc(0, PChar(FN));
  582. end
  583. else
  584. Result := False; { Should never happen }
  585. end;
  586. procedure HandleProcessWait(ProcessHandle: THandle; const Wait: TExecWait;
  587. const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
  588. var ResultCode: Integer);
  589. begin
  590. try
  591. if Wait = ewWaitUntilIdle then begin
  592. repeat
  593. ProcessMessagesProc;
  594. until WaitForInputIdle(ProcessHandle, 50) <> WAIT_TIMEOUT;
  595. end;
  596. if Wait = ewWaitUntilTerminated then begin
  597. { Wait until the process returns, but still process any messages that
  598. arrive and read the output if requested. }
  599. var WaitMilliseconds := IfThen(OutputReader <> nil, 50, INFINITE);
  600. var WaitResult: DWORD := 0;
  601. repeat
  602. { Process any pending messages first because MsgWaitForMultipleObjects
  603. (called below) only returns when *new* messages arrive, unless there's
  604. a timeout }
  605. if WaitResult <> WAIT_TIMEOUT then
  606. ProcessMessagesProc;
  607. if OutputReader <> nil then
  608. OutputReader.Read(False);
  609. WaitResult := MsgWaitForMultipleObjects(1, ProcessHandle, False,
  610. WaitMilliseconds, QS_ALLINPUT);
  611. until (WaitResult <> WAIT_OBJECT_0+1) and (WaitResult <> WAIT_TIMEOUT);
  612. { Process messages once more in case MsgWaitForMultipleObjects saw the
  613. process terminate and new messages arrive simultaneously. (Can't leave
  614. unprocessed messages waiting, or a subsequent call to WaitMessage
  615. won't see them.) }
  616. ProcessMessagesProc;
  617. if OutputReader <> nil then
  618. OutputReader.Read(True);
  619. end;
  620. { Get the exit code. Will be set to STILL_ACTIVE if not yet available }
  621. if not GetExitCodeProcess(ProcessHandle, DWORD(ResultCode)) then
  622. ResultCode := -1; { just in case }
  623. finally
  624. CloseHandle(ProcessHandle);
  625. end;
  626. end;
  627. function InstExec(const DisableFsRedir: Boolean; const Filename, Params: String;
  628. WorkingDir: String; const Wait: TExecWait; const ShowCmd: Integer;
  629. const ProcessMessagesProc: TProcedure; const OutputReader: TCreateProcessOutputReader;
  630. var ResultCode: Integer): Boolean;
  631. var
  632. CmdLine: String;
  633. StartupInfo: TStartupInfo;
  634. ProcessInfo: TProcessInformation;
  635. begin
  636. {Also see IsppFuncs' Exec }
  637. if Filename = '>' then
  638. CmdLine := Params
  639. else begin
  640. CmdLine := '"' + Filename + '"';
  641. if Params <> '' then
  642. CmdLine := CmdLine + ' ' + Params;
  643. if SameText(PathExtractExt(Filename), '.bat') or
  644. SameText(PathExtractExt(Filename), '.cmd') then begin
  645. { Use our own handling for .bat and .cmd files since passing them straight
  646. to CreateProcess on Windows NT 4.0 has problems: it doesn't properly
  647. quote the command line it passes to cmd.exe. This didn't work before:
  648. Filename: "c:\batch.bat"; Parameters: """abc"""
  649. And other Windows versions might have unknown quirks too, since
  650. CreateProcess isn't documented to accept .bat files in the first place. }
  651. { With cmd.exe, the whole command line must be quoted for quoted
  652. parameters to work. For example, this fails:
  653. cmd.exe /c "z:\blah.bat" "test"
  654. But this works:
  655. cmd.exe /c ""z:\blah.bat" "test""
  656. }
  657. CmdLine := '"' + AddBackslash(GetSystemDir) + 'cmd.exe" /C "' + CmdLine + '"'
  658. end;
  659. if WorkingDir = '' then
  660. WorkingDir := PathExtractDir(Filename);
  661. end;
  662. FillChar(StartupInfo, SizeOf(StartupInfo), 0);
  663. StartupInfo.cb := SizeOf(StartupInfo);
  664. StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  665. StartupInfo.wShowWindow := ShowCmd;
  666. if WorkingDir = '' then
  667. WorkingDir := GetSystemDir;
  668. var InheritHandles := False;
  669. var dwCreationFlags: DWORD := CREATE_DEFAULT_ERROR_MODE;
  670. if (OutputReader <> nil) and (Wait = ewWaitUntilTerminated) then begin
  671. OutputReader.UpdateStartupInfo(StartupInfo);
  672. InheritHandles := True;
  673. dwCreationFlags := dwCreationFlags or CREATE_NO_WINDOW;
  674. end;
  675. Result := CreateProcessRedir(DisableFsRedir, nil, PChar(CmdLine), nil, nil,
  676. InheritHandles, dwCreationFlags, nil, PChar(WorkingDir),
  677. StartupInfo, ProcessInfo);
  678. if not Result then begin
  679. ResultCode := GetLastError;
  680. Exit;
  681. end;
  682. { Don't need the thread handle, so close it now }
  683. CloseHandle(ProcessInfo.hThread);
  684. if OutputReader <> nil then
  685. OutputReader.NotifyCreateProcessDone;
  686. HandleProcessWait(ProcessInfo.hProcess, Wait, ProcessMessagesProc,
  687. OutputReader, ResultCode);
  688. end;
  689. function InstShellExec(const Verb, Filename, Params: String; WorkingDir: String;
  690. const Wait: TExecWait; const ShowCmd: Integer;
  691. const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
  692. var
  693. Info: TShellExecuteInfo;
  694. begin
  695. if WorkingDir = '' then begin
  696. WorkingDir := PathExtractDir(Filename);
  697. if WorkingDir = '' then
  698. WorkingDir := GetSystemDir;
  699. end;
  700. FillChar(Info, SizeOf(Info), 0);
  701. Info.cbSize := SizeOf(Info);
  702. Info.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_FLAG_DDEWAIT or
  703. SEE_MASK_NOCLOSEPROCESS;
  704. if Verb <> '' then
  705. Info.lpVerb := PChar(Verb);
  706. Info.lpFile := PChar(Filename);
  707. Info.lpParameters := PChar(Params);
  708. Info.lpDirectory := PChar(WorkingDir);
  709. Info.nShow := ShowCmd;
  710. Result := ShellExecuteEx(@Info);
  711. if not Result then begin
  712. ResultCode := GetLastError;
  713. Exit;
  714. end;
  715. ResultCode := STILL_ACTIVE;
  716. { A process handle won't always be returned, e.g. if DDE was used }
  717. if Info.hProcess <> 0 then
  718. HandleProcessWait(Info.hProcess, Wait, ProcessMessagesProc, nil, ResultCode);
  719. end;
  720. function CheckForOrCreateMutexes(Mutexes: String; const Create: Boolean): Boolean;
  721. function MutexPos(const S: String): Integer;
  722. begin
  723. for var I := 1 to Length(S) do
  724. if (S[I] = ',') and ((I = 1) or (S[I-1] <> '\')) then
  725. Exit(I);
  726. Result := 0;
  727. end;
  728. { Returns True if any of the mutexes in the comma-separated Mutexes string
  729. exist and Create is False }
  730. var
  731. I: Integer;
  732. M: String;
  733. H: THandle;
  734. begin
  735. Result := False;
  736. repeat
  737. I := MutexPos(Mutexes);
  738. if I = 0 then I := Maxint;
  739. M := Trim(Copy(Mutexes, 1, I-1));
  740. if M <> '' then begin
  741. StringChange(M, '\,', ',');
  742. if Create then begin
  743. CreateMutex(M)
  744. end else begin
  745. H := OpenMutex(SYNCHRONIZE, False, PChar(M));
  746. if H <> 0 then begin
  747. CloseHandle(H);
  748. Result := True;
  749. Break;
  750. end;
  751. end;
  752. end;
  753. Delete(Mutexes, 1, I);
  754. until Mutexes = '';
  755. end;
  756. function CheckForMutexes(const Mutexes: String): Boolean;
  757. begin
  758. Result := CheckForOrCreateMutexes(Mutexes, False);
  759. end;
  760. procedure CreateMutexes(const Mutexes: String);
  761. begin
  762. CheckForOrCreateMutexes(Mutexes, True);
  763. end;
  764. function ModifyPifFile(const Filename: String; const CloseOnExit: Boolean): Boolean;
  765. { Changes the "Close on exit" setting of a .pif file. Returns True if it was
  766. able to make the change. }
  767. var
  768. F: TFile;
  769. B: Byte;
  770. begin
  771. { Note: Specs on the .pif format were taken from
  772. http://smsoft.chat.ru/en/pifdoc.htm }
  773. Result := False;
  774. F := TFile.Create(Filename, fdOpenExisting, faReadWrite, fsNone);
  775. try
  776. { Is it a valid .pif file? }
  777. if F.Size >= $171 then begin
  778. F.Seek($63);
  779. F.ReadBuffer(B, SizeOf(B));
  780. { Toggle the "Close on exit" bit }
  781. if (B and $10 <> 0) <> CloseOnExit then begin
  782. B := B xor $10;
  783. F.Seek($63);
  784. F.WriteBuffer(B, SizeOf(B));
  785. end;
  786. Result := True;
  787. end;
  788. finally
  789. F.Free;
  790. end;
  791. end;
  792. function GetComputerNameString: String;
  793. var
  794. Buf: array[0..MAX_COMPUTERNAME_LENGTH] of Char;
  795. Size: DWORD;
  796. begin
  797. Size := SizeOf(Buf) div SizeOf(Buf[0]);
  798. if GetComputerName(Buf, Size) then
  799. Result := Buf
  800. else
  801. Result := '';
  802. end;
  803. function GetUserNameString: String;
  804. var
  805. Buf: array[0..256] of Char; { 256 = UNLEN }
  806. BufSize: DWORD;
  807. begin
  808. BufSize := SizeOf(Buf) div SizeOf(Buf[0]);
  809. if GetUserName(Buf, BufSize) then
  810. Result := Buf
  811. else
  812. Result := '';
  813. end;
  814. function MakePendingFileRenameOperationsChecksum: TSHA256Digest;
  815. { Calculates a checksum of the current PendingFileRenameOperations registry
  816. value The caller can use this checksum to determine if
  817. PendingFileRenameOperations was changed (perhaps by another program). }
  818. var
  819. Context: TSHA256Context;
  820. K: HKEY;
  821. S: String;
  822. begin
  823. SHA256Init(Context);
  824. try
  825. if RegOpenKeyExView(rvDefault, HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager',
  826. 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  827. if RegQueryMultiStringValue(K, 'PendingFileRenameOperations', S) then
  828. SHA256Update(Context, S[1], Length(S)*SizeOf(S[1]));
  829. { When "PendingFileRenameOperations" is full, it spills over into
  830. "PendingFileRenameOperations2" }
  831. if RegQueryMultiStringValue(K, 'PendingFileRenameOperations2', S) then
  832. SHA256Update(Context, S[1], Length(S)*SizeOf(S[1]));
  833. RegCloseKey(K);
  834. end;
  835. except
  836. { don't propagate exceptions }
  837. end;
  838. Result := SHA256Final(Context);
  839. end;
  840. procedure EnumFileReplaceOperationsFilenames(const EnumFunc: TEnumFROFilenamesProc;
  841. Param: Pointer);
  842. { Enumerates all the filenames in the current PendingFileRenameOperations
  843. registry value or WININIT.INI file. The function does not distinguish between
  844. source and destination filenames; it enumerates both. }
  845. procedure DoValue(const K: HKEY; const ValueName: PChar);
  846. var
  847. S: String;
  848. P, PEnd: PChar;
  849. begin
  850. if not RegQueryMultiStringValue(K, ValueName, S) then
  851. Exit;
  852. P := PChar(S);
  853. PEnd := P + Length(S);
  854. while P < PEnd do begin
  855. if P[0] = '!' then
  856. { Note: '!' means that MoveFileEx was called with the
  857. MOVEFILE_REPLACE_EXISTING flag }
  858. Inc(P);
  859. if StrLComp(P, '\??\', 4) = 0 then begin
  860. Inc(P, 4);
  861. if P[0] <> #0 then
  862. EnumFunc(P, Param);
  863. end;
  864. Inc(P, StrLen(P) + 1);
  865. end;
  866. end;
  867. var
  868. K: HKEY;
  869. begin
  870. if RegOpenKeyExView(rvDefault, HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager',
  871. 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  872. try
  873. DoValue(K, 'PendingFileRenameOperations');
  874. { When "PendingFileRenameOperations" is full, it spills over into
  875. "PendingFileRenameOperations2" }
  876. DoValue(K, 'PendingFileRenameOperations2');
  877. finally
  878. RegCloseKey(K);
  879. end;
  880. end;
  881. end;
  882. procedure UnregisterFont(const FontName, FontFilename: String; const PerUserFont: Boolean);
  883. var
  884. RootKey, K: HKEY;
  885. begin
  886. if PerUserFont then
  887. RootKey := HKEY_CURRENT_USER
  888. else
  889. RootKey := HKEY_LOCAL_MACHINE;
  890. if RegOpenKeyExView(rvDefault, RootKey, 'Software\Microsoft\Windows NT\CurrentVersion\Fonts',
  891. 0, KEY_SET_VALUE, K) = ERROR_SUCCESS then begin
  892. RegDeleteValue(K, PChar(FontName));
  893. RegCloseKey(K);
  894. end;
  895. if RemoveFontResource(PChar(FontFilename)) then
  896. SendNotifyMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
  897. end;
  898. function GetSpaceOnDisk(const DisableFsRedir: Boolean; const DriveRoot: String;
  899. var FreeBytes, TotalBytes: Integer64): Boolean;
  900. var
  901. GetDiskFreeSpaceExFunc: function(lpDirectoryName: PChar;
  902. lpFreeBytesAvailable: PLargeInteger; lpTotalNumberOfBytes: PLargeInteger;
  903. lpTotalNumberOfFreeBytes: PLargeInteger): BOOL; stdcall;
  904. PrevState: TPreviousFsRedirectionState;
  905. SectorsPerCluster, BytesPerSector, FreeClusters, TotalClusters: Cardinal;
  906. begin
  907. { NOTE: The docs claim that GetDiskFreeSpace supports UNC paths on
  908. Windows 95 OSR2 and later. But that does not seem to be the case in my
  909. tests; it fails with error 50 on Windows 95 through Me.
  910. GetDiskFreeSpaceEx, however, *does* succeed with UNC paths, so use it
  911. if available. }
  912. GetDiskFreeSpaceExFunc := GetProcAddress(GetModuleHandle(kernel32),
  913. 'GetDiskFreeSpaceExW');
  914. if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
  915. Result := False;
  916. Exit;
  917. end;
  918. try
  919. if Assigned(@GetDiskFreeSpaceExFunc) then begin
  920. Result := GetDiskFreeSpaceExFunc(PChar(AddBackslash(PathExpand(DriveRoot))),
  921. @TLargeInteger(Int64Rec(FreeBytes)), @TLargeInteger(Int64Rec(TotalBytes)), nil);
  922. end
  923. else begin
  924. Result := GetDiskFreeSpace(PChar(AddBackslash(PathExtractDrive(PathExpand(DriveRoot)))),
  925. DWORD(SectorsPerCluster), DWORD(BytesPerSector), DWORD(FreeClusters),
  926. DWORD(TotalClusters));
  927. if Result then begin
  928. { The result of GetDiskFreeSpace does not cap at 2GB, so we must use a
  929. 64-bit multiply operation to avoid an overflow. }
  930. Multiply32x32to64(BytesPerSector * SectorsPerCluster, FreeClusters,
  931. FreeBytes);
  932. Multiply32x32to64(BytesPerSector * SectorsPerCluster, TotalClusters,
  933. TotalBytes);
  934. end;
  935. end;
  936. finally
  937. RestoreFsRedirection(PrevState);
  938. end;
  939. end;
  940. function GetSpaceOnNearestMountPoint(const DisableFsRedir: Boolean;
  941. const StartDir: String; var FreeBytes, TotalBytes: Integer64): Boolean;
  942. { Gets the free and total space available on the specified directory. If that
  943. fails (e.g. if the directory does not exist), then it strips off the last
  944. component of the path and tries again. This repeats until it reaches the
  945. root. Returns True if successful. }
  946. var
  947. Dir: String;
  948. LastLen: Integer;
  949. begin
  950. Result := False;
  951. Dir := RemoveBackslashUnlessRoot(StartDir);
  952. LastLen := 0;
  953. while Length(Dir) <> LastLen do begin
  954. if GetSpaceOnDisk(DisableFsRedir, Dir, FreeBytes, TotalBytes) then begin
  955. Result := True;
  956. Break;
  957. end;
  958. LastLen := Length(Dir);
  959. Dir := PathExtractDir(Dir);
  960. end;
  961. end;
  962. procedure RefreshEnvironment;
  963. { Notifies other applications (Explorer) that environment variables have
  964. changed. Based on code from KB article 104011. }
  965. var
  966. MsgResult: DWORD_PTR;
  967. begin
  968. { Note: We originally used SendNotifyMessage to broadcast the message but it
  969. turned out that while it worked fine on NT 4 and 2000 it didn't work on XP
  970. -- the string "Environment" in lParam would be garbled on the receiving
  971. end (why I'm not exactly sure). We now use SendMessageTimeout as directed
  972. in the KB article 104011. It isn't as elegant since it could cause us to
  973. be delayed if another app is hung, but it'll have to do. }
  974. SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
  975. LPARAM(PChar('Environment')), SMTO_ABORTIFHUNG, 5000, @MsgResult);
  976. end;
  977. procedure SplitNewParamStr(const Index: Integer; var AName, AValue: String);
  978. { Reads a command line parameter. If it is in the form "/PARAM=VALUE" then
  979. AName is set to "/PARAM=" and AValue is set to "VALUE". Otherwise, the full
  980. parameter is stored in AName, and AValue is set to an empty string. }
  981. var
  982. S: String;
  983. P: Integer;
  984. begin
  985. S := NewParamStr(Index);
  986. if (S <> '') and (S[1] = '/') then begin
  987. P := PathPos('=', S);
  988. if P <> 0 then begin
  989. AName := Copy(S, 1, P);
  990. AValue := Copy(S, P+1, Maxint);
  991. Exit;
  992. end;
  993. end;
  994. AName := S;
  995. AValue := '';
  996. end;
  997. function ForceDirectories(const DisableFsRedir: Boolean; Dir: String): Boolean;
  998. begin
  999. Dir := RemoveBackslashUnlessRoot(Dir);
  1000. if (PathExtractPath(Dir) = Dir) or DirExistsRedir(DisableFsRedir, Dir) then
  1001. Result := True
  1002. else
  1003. Result := ForceDirectories(DisableFsRedir, PathExtractPath(Dir)) and
  1004. CreateDirectoryRedir(DisableFsRedir, Dir);
  1005. end;
  1006. { TSimpleStringList }
  1007. procedure TSimpleStringList.Add(const S: String);
  1008. var
  1009. Delta: Integer;
  1010. begin
  1011. if FCount = FCapacity then begin
  1012. if FCapacity > 64 then Delta := FCapacity div 4 else
  1013. if FCapacity > 8 then Delta := 16 else
  1014. Delta := 4;
  1015. SetCapacity(FCapacity + Delta);
  1016. end;
  1017. FList^[FCount] := S;
  1018. Inc(FCount);
  1019. end;
  1020. procedure TSimpleStringList.AddIfDoesntExist(const S: String);
  1021. begin
  1022. if IndexOf(S) = -1 then
  1023. Add(S);
  1024. end;
  1025. procedure TSimpleStringList.SetCapacity(NewCapacity: Integer);
  1026. begin
  1027. ReallocMem(FList, NewCapacity * SizeOf(Pointer));
  1028. if NewCapacity > FCapacity then
  1029. FillChar(FList^[FCapacity], (NewCapacity - FCapacity) * SizeOf(Pointer), 0);
  1030. FCapacity := NewCapacity;
  1031. end;
  1032. procedure TSimpleStringList.Clear;
  1033. begin
  1034. if FCount <> 0 then Finalize(FList^[0], FCount);
  1035. FCount := 0;
  1036. SetCapacity(0);
  1037. end;
  1038. function TSimpleStringList.Get(Index: Integer): String;
  1039. begin
  1040. Result := FList^[Index];
  1041. end;
  1042. function TSimpleStringList.IndexOf(const S: String): Integer;
  1043. { Note: This is case-sensitive, unlike TStringList.IndexOf }
  1044. var
  1045. I: Integer;
  1046. begin
  1047. Result := -1;
  1048. for I := 0 to FCount-1 do
  1049. if FList^[I] = S then begin
  1050. Result := I;
  1051. Break;
  1052. end;
  1053. end;
  1054. destructor TSimpleStringList.Destroy;
  1055. begin
  1056. Clear;
  1057. inherited Destroy;
  1058. end;
  1059. end.