Setup.InstFunc.pas 41 KB

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