Setup.InstFunc.pas 42 KB

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