InstFunc.pas 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448
  1. unit 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
  8. }
  9. interface
  10. uses
  11. Windows, SysUtils, Struct, Int64Em, MD5, SHA1, CmnFunc2;
  12. {$I VERSION.INC}
  13. type
  14. PSimpleStringListArray = ^TSimpleStringListArray;
  15. TSimpleStringListArray = array[0..$1FFFFFFE] of String;
  16. TSimpleStringList = class
  17. private
  18. FList: PSimpleStringListArray;
  19. FCount, FCapacity: Integer;
  20. function Get(Index: Integer): String;
  21. procedure SetCapacity(NewCapacity: Integer);
  22. public
  23. destructor Destroy; override;
  24. procedure Add(const S: String);
  25. procedure AddIfDoesntExist(const S: String);
  26. procedure Clear;
  27. function IndexOf(const S: String): Integer;
  28. property Count: Integer read FCount;
  29. property Items[Index: Integer]: String read Get; default;
  30. end;
  31. TDeleteDirProc = function(const DisableFsRedir: Boolean; const DirName: String;
  32. const Param: Pointer): Boolean;
  33. TDeleteFileProc = function(const DisableFsRedir: Boolean; const FileName: String;
  34. const Param: Pointer): Boolean;
  35. TEnumFROFilenamesProc = procedure(const Filename: String; Param: Pointer);
  36. { Must keep this in synch with ScriptFunc_C: }
  37. TExecWait = (ewNoWait, ewWaitUntilTerminated, ewWaitUntilIdle);
  38. TDetermineDefaultLanguageResult = (ddNoMatch, ddMatch, ddMatchLangParameter);
  39. TGetLanguageEntryProc = function(Index: Integer; var Entry: PSetupLanguageEntry): Boolean;
  40. function CheckForMutexes(const Mutexes: String): Boolean;
  41. procedure CreateMutexes(const Mutexes: String);
  42. function CreateTempDir: String;
  43. function DecrementSharedCount(const RegView: TRegView; const Filename: String): Boolean;
  44. procedure DelayDeleteFile(const DisableFsRedir: Boolean; const Filename: String;
  45. const MaxTries, FirstRetryDelayMS, SubsequentRetryDelayMS: Integer);
  46. function DelTree(const DisableFsRedir: Boolean; const Path: String;
  47. const IsDir, DeleteFiles, DeleteSubdirsAlso, BreakOnError: Boolean;
  48. const DeleteDirProc: TDeleteDirProc; const DeleteFileProc: TDeleteFileProc;
  49. const Param: Pointer): Boolean;
  50. function DetermineDefaultLanguage(const GetLanguageEntryProc: TGetLanguageEntryProc;
  51. const Method: TSetupLanguageDetectionMethod; const LangParameter: String;
  52. var ResultIndex: Integer): TDetermineDefaultLanguageResult;
  53. procedure EnumFileReplaceOperationsFilenames(const EnumFunc: TEnumFROFilenamesProc;
  54. Param: Pointer);
  55. function GenerateNonRandomUniqueTempDir(Path: String; var TempDir: String): Boolean;
  56. function GenerateUniqueName(const DisableFsRedir: Boolean; Path: String;
  57. const Extension: String): String;
  58. function GetComputerNameString: String;
  59. function GetFileDateTime(const DisableFsRedir: Boolean; const Filename: String;
  60. var DateTime: TFileTime): Boolean;
  61. function GetMD5OfFile(const DisableFsRedir: Boolean; const Filename: String): TMD5Digest;
  62. function GetMD5OfAnsiString(const S: AnsiString): TMD5Digest;
  63. function GetMD5OfUnicodeString(const S: UnicodeString): TMD5Digest;
  64. function GetSHA1OfFile(const DisableFsRedir: Boolean; const Filename: String): TSHA1Digest;
  65. function GetSHA1OfAnsiString(const S: AnsiString): TSHA1Digest;
  66. function GetSHA1OfUnicodeString(const S: UnicodeString): TSHA1Digest;
  67. function GetSHA256OfFile(const DisableFsRedir: Boolean; const Filename: String): String;
  68. function GetSHA256OfAnsiString(const S: AnsiString): String;
  69. function GetSHA256OfUnicodeString(const S: UnicodeString): String;
  70. function GetRegRootKeyName(const RootKey: HKEY): String;
  71. function GetSpaceOnDisk(const DisableFsRedir: Boolean; const DriveRoot: String;
  72. var FreeBytes, TotalBytes: Integer64): Boolean;
  73. function GetSpaceOnNearestMountPoint(const DisableFsRedir: Boolean;
  74. const StartDir: String; var FreeBytes, TotalBytes: Integer64): Boolean;
  75. function GetUserNameString: String;
  76. procedure IncrementSharedCount(const RegView: TRegView; const Filename: String;
  77. const AlreadyExisted: Boolean);
  78. function InstExec(const DisableFsRedir: Boolean; const Filename, Params: String;
  79. WorkingDir: String; const Wait: TExecWait; const ShowCmd: Integer;
  80. const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
  81. function InstShellExec(const Verb, Filename, Params: String; WorkingDir: String;
  82. const Wait: TExecWait; const ShowCmd: Integer;
  83. const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
  84. procedure InternalError(const Id: String);
  85. procedure InternalErrorFmt(const S: String; const Args: array of const);
  86. function IsDirEmpty(const DisableFsRedir: Boolean; const Dir: String): Boolean;
  87. function IsProtectedSystemFile(const DisableFsRedir: Boolean;
  88. const Filename: String): Boolean;
  89. function MakePendingFileRenameOperationsChecksum: TMD5Digest;
  90. function ModifyPifFile(const Filename: String; const CloseOnExit: Boolean): Boolean;
  91. procedure RaiseFunctionFailedError(const FunctionName: String);
  92. procedure RaiseOleError(const FunctionName: String; const ResultCode: HRESULT);
  93. procedure RefreshEnvironment;
  94. function ReplaceSystemDirWithSysWow64(const Path: String): String;
  95. function ReplaceSystemDirWithSysNative(Path: String; const IsWin64: Boolean): String;
  96. procedure UnregisterFont(const FontName, FontFilename: String; const PerUserFont: Boolean);
  97. function RestartComputer: Boolean;
  98. procedure RestartReplace(const DisableFsRedir: Boolean; TempFile, DestFile: String);
  99. procedure SplitNewParamStr(const Index: Integer; var AName, AValue: String);
  100. procedure Win32ErrorMsg(const FunctionName: String);
  101. procedure Win32ErrorMsgEx(const FunctionName: String; const ErrorCode: DWORD);
  102. function ForceDirectories(const DisableFsRedir: Boolean; Dir: String): Boolean;
  103. implementation
  104. uses
  105. Messages, ShellApi, PathFunc, Msgs, MsgIDs, FileClass, RedirFunc, SetupTypes,
  106. Hash, Classes, RegStr;
  107. procedure InternalError(const Id: String);
  108. begin
  109. raise Exception.Create(FmtSetupMessage1(msgErrorInternal2, Id));
  110. end;
  111. procedure InternalErrorFmt(const S: String; const Args: array of const);
  112. begin
  113. InternalError(Format(S, Args));
  114. end;
  115. procedure Win32ErrorMsgEx(const FunctionName: String; const ErrorCode: DWORD);
  116. begin
  117. raise Exception.Create(FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  118. [FunctionName, IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
  119. end;
  120. procedure Win32ErrorMsg(const FunctionName: String);
  121. begin
  122. Win32ErrorMsgEx(FunctionName, GetLastError);
  123. end;
  124. procedure RaiseOleError(const FunctionName: String; const ResultCode: HRESULT);
  125. begin
  126. raise Exception.Create(FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  127. [FunctionName, IntToHexStr8(ResultCode), Win32ErrorString(ResultCode)]));
  128. end;
  129. procedure RaiseFunctionFailedError(const FunctionName: String);
  130. begin
  131. raise Exception.Create(FmtSetupMessage1(msgErrorFunctionFailedNoCode,
  132. FunctionName));
  133. end;
  134. function GetRegRootKeyName(const RootKey: HKEY): String;
  135. begin
  136. case RootKey of
  137. HKEY_AUTO: InternalError('GetRegRootKeyName called for HKEY_AUTO');
  138. HKEY_CLASSES_ROOT: Result := 'HKEY_CLASSES_ROOT';
  139. HKEY_CURRENT_USER: Result := 'HKEY_CURRENT_USER';
  140. HKEY_LOCAL_MACHINE: Result := 'HKEY_LOCAL_MACHINE';
  141. HKEY_USERS: Result := 'HKEY_USERS';
  142. HKEY_PERFORMANCE_DATA: Result := 'HKEY_PERFORMANCE_DATA';
  143. HKEY_CURRENT_CONFIG: Result := 'HKEY_CURRENT_CONFIG';
  144. HKEY_DYN_DATA: Result := 'HKEY_DYN_DATA';
  145. else
  146. { unknown - shouldn't get here }
  147. Result := Format('[%x]', [Cardinal(RootKey)]);
  148. end;
  149. end;
  150. function ConvertStringSecurityDescriptorToSecurityDescriptorW(
  151. StringSecurityDescriptor: PWideChar;
  152. StringSDRevision: DWORD; var ppSecurityDescriptor: Pointer;
  153. dummy: Pointer): BOOL; stdcall; external advapi32;
  154. function CreateSafeDirectory(Path: PWideChar; var ErrorCode: DWORD): Boolean;
  155. { Creates a protected directory if it's a subdirectory of c:\WINDOWS\TEMP,
  156. otherwise creates a normal directory. }
  157. const
  158. SDDL_REVISION_1 = 1;
  159. var
  160. CurrentUserSid, StringSecurityDescriptor: String;
  161. pSecurityDescriptor: Pointer;
  162. SecurityAttr: TSecurityAttributes;
  163. begin
  164. if Pos(PathLowercase(AddBackslash(GetSystemWinDir) + 'TEMP\'),
  165. PathLowercase(PathExpand(Path))) <> 1 then begin
  166. Result := CreateDirectoryW(Path, nil);
  167. if not Result then
  168. ErrorCode := GetLastError;
  169. Exit;
  170. end;
  171. CurrentUserSid := GetCurrentUserSid;
  172. if CurrentUserSid = '' then
  173. CurrentUserSid := 'OW'; // OW: owner rights
  174. StringSecurityDescriptor :=
  175. // D: adds a Discretionary ACL ("DACL", i.e. access control via SIDs)
  176. // P: prevents DACL from being modified by inherited ACLs
  177. 'D:P' +
  178. // A: "allow"
  179. // OICI: "object and container inherit",
  180. // i.e. files and directories created within the new directory
  181. // inherit these permissions
  182. // 0x001F01FF: corresponds to `FILE_ALL_ACCESS`
  183. '(A;OICI;0x001F01FF;;;' + CurrentUserSid + ')' + // current user
  184. '(A;OICI;0x001F01FF;;;BA)' + // BA: built-in administrator
  185. '(A;OICI;0x001F01FF;;;SY)'; // SY: local SYSTEM account
  186. if not ConvertStringSecurityDescriptorToSecurityDescriptorW(
  187. PWideChar(StringSecurityDescriptor), SDDL_REVISION_1, pSecurityDescriptor, nil
  188. ) then begin
  189. ErrorCode := GetLastError;
  190. Result := False;
  191. Exit;
  192. end;
  193. SecurityAttr.nLength := SizeOf(SecurityAttr);
  194. SecurityAttr.bInheritHandle := False;
  195. SecurityAttr.lpSecurityDescriptor := pSecurityDescriptor;
  196. Result := CreateDirectoryW(Path, @SecurityAttr);
  197. if not Result then
  198. ErrorCode := GetLastError;
  199. LocalFree(pSecurityDescriptor);
  200. end;
  201. function IntToBase32(Number: Longint): String;
  202. const
  203. Table: array[0..31] of Char = '0123456789ABCDEFGHIJKLMNOPQRSTUV';
  204. var
  205. I: Integer;
  206. begin
  207. Result := '';
  208. for I := 0 to 4 do begin
  209. Insert(Table[Number and 31], Result, 1);
  210. Number := Number shr 5;
  211. end;
  212. end;
  213. function GenerateUniqueName(const DisableFsRedir: Boolean; Path: String;
  214. const Extension: String): String;
  215. var
  216. Rand, RandOrig: Longint;
  217. Filename: String;
  218. begin
  219. Path := AddBackslash(Path);
  220. RandOrig := Random($2000000);
  221. Rand := RandOrig;
  222. repeat
  223. Inc(Rand);
  224. if Rand > $1FFFFFF then Rand := 0;
  225. if Rand = RandOrig then
  226. { practically impossible to go through 33 million possibilities,
  227. but check "just in case"... }
  228. raise Exception.Create(FmtSetupMessage1(msgErrorTooManyFilesInDir,
  229. RemoveBackslashUnlessRoot(Path)));
  230. { Generate a random name }
  231. Filename := Path + 'is-' + IntToBase32(Rand) + Extension;
  232. until not FileOrDirExistsRedir(DisableFsRedir, Filename);
  233. Result := Filename;
  234. end;
  235. function GenerateNonRandomUniqueTempDir(Path: String; var TempDir: String): Boolean;
  236. { Creates a new temporary directory with a non-random name. Returns True if an
  237. existing directory was re-created. }
  238. var
  239. Rand, RandOrig: Longint; { These are actually NOT random in any way }
  240. ErrorCode: DWORD;
  241. begin
  242. Path := AddBackslash(Path);
  243. RandOrig := $123456;
  244. Rand := RandOrig;
  245. repeat
  246. Result := False;
  247. Inc(Rand);
  248. if Rand > $1FFFFFF then Rand := 0;
  249. if Rand = RandOrig then
  250. { practically impossible to go through 33 million possibilities,
  251. but check "just in case"... }
  252. raise Exception.Create(FmtSetupMessage1(msgErrorTooManyFilesInDir,
  253. RemoveBackslashUnlessRoot(Path)));
  254. { Generate a random name }
  255. TempDir := Path + 'iu-' + IntToBase32(Rand) + '.tmp';
  256. if DirExists(TempDir) then begin
  257. if not DeleteDirTree(TempDir) then Continue;
  258. Result := True;
  259. end else if NewFileExists(TempDir) then
  260. if not DeleteFile(TempDir) then Continue;
  261. if CreateSafeDirectory(PChar(TempDir), ErrorCode) then Break;
  262. if ErrorCode <> ERROR_ALREADY_EXISTS then
  263. raise Exception.Create(FmtSetupMessage(msgLastErrorMessage,
  264. [FmtSetupMessage1(msgErrorCreatingDir, TempDir), IntToStr(ErrorCode),
  265. Win32ErrorString(ErrorCode)]));
  266. until False; // continue until a new directory was created
  267. end;
  268. function CreateTempDir: String;
  269. var
  270. Dir: String;
  271. ErrorCode: DWORD;
  272. begin
  273. while True do begin
  274. Dir := GenerateUniqueName(False, GetTempDir, '.tmp');
  275. if CreateSafeDirectory(PChar(Dir), ErrorCode) then
  276. Break;
  277. if ErrorCode <> ERROR_ALREADY_EXISTS then
  278. raise Exception.Create(FmtSetupMessage(msgLastErrorMessage,
  279. [FmtSetupMessage1(msgErrorCreatingDir, Dir), IntToStr(ErrorCode),
  280. Win32ErrorString(ErrorCode)]));
  281. end;
  282. Result := Dir;
  283. end;
  284. function ReplaceSystemDirWithSysWow64(const Path: String): String;
  285. { If the user is running 64-bit Windows and Path begins with
  286. 'x:\windows\system32' it replaces it with 'x:\windows\syswow64', like the
  287. file system redirector would do. Otherwise, Path is returned unchanged. }
  288. var
  289. SysWow64Dir, SysDir: String;
  290. L: Integer;
  291. begin
  292. SysWow64Dir := GetSysWow64Dir;
  293. if SysWow64Dir <> '' then begin
  294. SysDir := GetSystemDir;
  295. { x:\windows\system32 -> x:\windows\syswow64
  296. x:\windows\system32\ -> x:\windows\syswow64\
  297. x:\windows\system32\filename -> x:\windows\syswow64\filename
  298. x:\windows\system32x -> x:\windows\syswow64x <- yes, like Windows! }
  299. L := Length(SysDir);
  300. if (Length(Path) = L) or
  301. ((Length(Path) > L) and not PathCharIsTrailByte(Path, L+1)) then begin
  302. { ^ avoid splitting a double-byte character }
  303. if PathCompare(Copy(Path, 1, L), SysDir) = 0 then begin
  304. Result := SysWow64Dir + Copy(Path, L+1, Maxint);
  305. Exit;
  306. end;
  307. end;
  308. end;
  309. Result := Path;
  310. end;
  311. function ReplaceSystemDirWithSysNative(Path: String; const IsWin64: Boolean): String;
  312. { If Path begins with 'x:\windows\system32\' it replaces it with
  313. 'x:\windows\sysnative\' and if Path equals 'x:\windows\system32'
  314. it replaces it with 'x:\windows\sysnative'. Otherwise, Path is
  315. returned unchanged. }
  316. var
  317. SysNativeDir, SysDir: String;
  318. L: Integer;
  319. begin
  320. SysNativeDir := GetSysNativeDir(IsWin64);
  321. if SysNativeDir <> '' then begin
  322. SysDir := GetSystemDir;
  323. if PathCompare(Path, SysDir) = 0 then begin
  324. { x:\windows\system32 -> x:\windows\sysnative }
  325. Result := SysNativeDir;
  326. Exit;
  327. end else begin
  328. { x:\windows\system32\ -> x:\windows\sysnative\
  329. x:\windows\system32\filename -> x:\windows\sysnative\filename }
  330. SysDir := AddBackslash(SysDir);
  331. L := Length(SysDir);
  332. if (Length(Path) = L) or
  333. ((Length(Path) > L) and not PathCharIsTrailByte(Path, L+1)) then begin
  334. { ^ avoid splitting a double-byte character }
  335. if PathCompare(Copy(Path, 1, L), SysDir) = 0 then begin
  336. Result := SysNativeDir + Copy(Path, L, Maxint);
  337. Exit;
  338. end;
  339. end;
  340. end;
  341. end;
  342. Result := Path;
  343. end;
  344. procedure RestartReplace(const DisableFsRedir: Boolean; TempFile, DestFile: String);
  345. { Renames TempFile to DestFile the next time Windows is started. If DestFile
  346. already existed, it will be overwritten. If DestFile is '' then TempFile
  347. will be deleted.. }
  348. begin
  349. TempFile := PathExpand(TempFile);
  350. if DestFile <> '' then
  351. DestFile := PathExpand(DestFile);
  352. if not DisableFsRedir then begin
  353. { Work around WOW64 bug present in the IA64 and x64 editions of Windows
  354. XP (3790) and Server 2003 prior to SP1 RC2: MoveFileEx writes filenames
  355. to the registry verbatim without mapping system32->syswow64. }
  356. TempFile := ReplaceSystemDirWithSysWow64(TempFile);
  357. if DestFile <> '' then
  358. DestFile := ReplaceSystemDirWithSysWow64(DestFile);
  359. end;
  360. if not MoveFileExRedir(DisableFsRedir, TempFile, DestFile,
  361. MOVEFILE_DELAY_UNTIL_REBOOT or MOVEFILE_REPLACE_EXISTING) then
  362. Win32ErrorMsg('MoveFileEx');
  363. end;
  364. function DelTree(const DisableFsRedir: Boolean; const Path: String;
  365. const IsDir, DeleteFiles, DeleteSubdirsAlso, BreakOnError: Boolean;
  366. const DeleteDirProc: TDeleteDirProc; const DeleteFileProc: TDeleteFileProc;
  367. const Param: Pointer): Boolean;
  368. { Deletes the specified directory including all files and subdirectories in
  369. it (including those with hidden, system, and read-only attributes). Returns
  370. True if it was able to successfully remove everything. If BreakOnError is
  371. set to True it will stop and return False the first time a delete failed or
  372. DeleteDirProc/DeleteFileProc returned False. }
  373. var
  374. BasePath, FindSpec: String;
  375. H: THandle;
  376. FindData: TWin32FindData;
  377. S: String;
  378. begin
  379. Result := True;
  380. if DeleteFiles and
  381. (not IsDir or IsDirectoryAndNotReparsePointRedir(DisableFsRedir, Path)) then begin
  382. if IsDir then begin
  383. BasePath := AddBackslash(Path);
  384. FindSpec := BasePath + '*';
  385. end
  386. else begin
  387. BasePath := PathExtractPath(Path);
  388. FindSpec := Path;
  389. end;
  390. H := FindFirstFileRedir(DisableFsRedir, FindSpec, FindData);
  391. if H <> INVALID_HANDLE_VALUE then begin
  392. try
  393. repeat
  394. S := FindData.cFileName;
  395. if (S <> '.') and (S <> '..') then begin
  396. if FindData.dwFileAttributes and FILE_ATTRIBUTE_READONLY <> 0 then begin
  397. { Strip the read-only attribute if this is a file, or if it's a
  398. directory and we're deleting subdirectories also }
  399. if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0) or DeleteSubdirsAlso then
  400. SetFileAttributesRedir(DisableFsRedir, BasePath + S,
  401. FindData.dwFileAttributes and not FILE_ATTRIBUTE_READONLY);
  402. end;
  403. if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
  404. if Assigned(DeleteFileProc) then begin
  405. if not DeleteFileProc(DisableFsRedir, BasePath + S, Param) then
  406. Result := False;
  407. end
  408. else begin
  409. if not DeleteFileRedir(DisableFsRedir, BasePath + S) then
  410. Result := False;
  411. end;
  412. end
  413. else begin
  414. if DeleteSubdirsAlso then
  415. if not DelTree(DisableFsRedir, BasePath + S, True, True, True, BreakOnError,
  416. DeleteDirProc, DeleteFileProc, Param) then
  417. Result := False;
  418. end;
  419. end;
  420. until (BreakOnError and not Result) or not FindNextFile(H, FindData);
  421. finally
  422. Windows.FindClose(H);
  423. end;
  424. end;
  425. end;
  426. if (not BreakOnError or Result) and IsDir then begin
  427. if Assigned(DeleteDirProc) then begin
  428. if not DeleteDirProc(DisableFsRedir, Path, Param) then
  429. Result := False;
  430. end
  431. else begin
  432. if not RemoveDirectoryRedir(DisableFsRedir, Path) then
  433. Result := False;
  434. end;
  435. end;
  436. end;
  437. function IsDirEmpty(const DisableFsRedir: Boolean; const Dir: String): Boolean;
  438. { Returns True if Dir contains no files or subdirectories.
  439. Note: If Dir does not exist or lacks list permission, False will be
  440. returned. }
  441. var
  442. H: THandle;
  443. FindData: TWin32FindData;
  444. begin
  445. H := FindFirstFileRedir(DisableFsRedir, AddBackslash(Dir) + '*', FindData);
  446. if H <> INVALID_HANDLE_VALUE then begin
  447. try
  448. Result := True;
  449. while True do begin
  450. if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
  451. { Found a file }
  452. Result := False;
  453. Break;
  454. end;
  455. if (StrComp(FindData.cFileName, '.') <> 0) and
  456. (StrComp(FindData.cFileName, '..') <> 0) then begin
  457. { Found a subdirectory }
  458. Result := False;
  459. Break;
  460. end;
  461. if not FindNextFile(H, FindData) then begin
  462. if GetLastError <> ERROR_NO_MORE_FILES then begin
  463. { Exited the loop early due to some unexpected error. The directory
  464. might not be empty, so return False }
  465. Result := False;
  466. end;
  467. Break;
  468. end;
  469. end;
  470. finally
  471. Windows.FindClose(H);
  472. end;
  473. end
  474. else begin
  475. { The directory may not exist, or it may lack list permission }
  476. Result := False;
  477. end;
  478. end;
  479. procedure IncrementSharedCount(const RegView: TRegView; const Filename: String;
  480. const AlreadyExisted: Boolean);
  481. const
  482. SharedDLLsKey = REGSTR_PATH_SETUP + '\SharedDLLs'; {don't localize}
  483. var
  484. ErrorCode: Longint;
  485. K: HKEY;
  486. Disp, Size, Count, CurType, NewType: DWORD;
  487. CountStr: String;
  488. FilenameP: PChar;
  489. begin
  490. ErrorCode := RegCreateKeyExView(RegView, HKEY_LOCAL_MACHINE, SharedDLLsKey, 0, nil,
  491. REG_OPTION_NON_VOLATILE, KEY_QUERY_VALUE or KEY_SET_VALUE, nil, K, @Disp);
  492. if ErrorCode <> ERROR_SUCCESS then
  493. raise Exception.Create(FmtSetupMessage(msgErrorRegOpenKey,
  494. [GetRegRootKeyName(HKEY_LOCAL_MACHINE), SharedDLLsKey]) + SNewLine2 +
  495. FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  496. ['RegCreateKeyEx', IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
  497. FilenameP := PChar(Filename);
  498. Count := 0;
  499. NewType := REG_DWORD;
  500. try
  501. if RegQueryValueEx(K, FilenameP, nil, @CurType, nil, @Size) = ERROR_SUCCESS then
  502. case CurType of
  503. REG_SZ:
  504. if RegQueryStringValue(K, FilenameP, CountStr) then begin
  505. Count := StrToInt(CountStr);
  506. NewType := REG_SZ;
  507. end;
  508. REG_BINARY: begin
  509. if (Size >= 1) and (Size <= 4) then begin
  510. if RegQueryValueEx(K, FilenameP, nil, nil, @Count, @Size) <> ERROR_SUCCESS then
  511. { ^ relies on the high 3 bytes of Count being initialized to 0 }
  512. Abort;
  513. NewType := REG_BINARY;
  514. end;
  515. end;
  516. REG_DWORD: begin
  517. Size := SizeOf(DWORD);
  518. if RegQueryValueEx(K, FilenameP, nil, nil, @Count, @Size) <> ERROR_SUCCESS then
  519. Abort;
  520. end;
  521. end;
  522. except
  523. Count := 0;
  524. end;
  525. if Integer(Count) < 0 then Count := 0; { just in case... }
  526. if (Count = 0) and AlreadyExisted then
  527. Inc(Count);
  528. Inc(Count);
  529. case NewType of
  530. REG_SZ: begin
  531. CountStr := IntToStr(Count);
  532. RegSetValueEx(K, FilenameP, 0, NewType, PChar(CountStr), (Length(CountStr)+1)*SizeOf(CountStr[1]));
  533. end;
  534. REG_BINARY, REG_DWORD:
  535. RegSetValueEx(K, FilenameP, 0, NewType, @Count, SizeOf(Count));
  536. end;
  537. RegCloseKey(K);
  538. end;
  539. function DecrementSharedCount(const RegView: TRegView;
  540. const Filename: String): Boolean;
  541. { Attempts to decrement the shared file reference count of Filename. Returns
  542. True if the count reached zero (meaning it's OK to delete the file). }
  543. const
  544. SharedDLLsKey = REGSTR_PATH_SETUP + '\SharedDLLs'; {don't localize}
  545. var
  546. ErrorCode: Longint;
  547. K: HKEY;
  548. CountRead: Boolean;
  549. Count, CurType, Size: DWORD;
  550. CountStr: String;
  551. begin
  552. Result := False;
  553. ErrorCode := RegOpenKeyExView(RegView, HKEY_LOCAL_MACHINE, SharedDLLsKey, 0,
  554. KEY_QUERY_VALUE or KEY_SET_VALUE, K);
  555. if ErrorCode = ERROR_FILE_NOT_FOUND then
  556. Exit;
  557. if ErrorCode <> ERROR_SUCCESS then
  558. raise Exception.Create(FmtSetupMessage(msgErrorRegOpenKey,
  559. [GetRegRootKeyName(HKEY_LOCAL_MACHINE), SharedDLLsKey]) + SNewLine2 +
  560. FmtSetupMessage(msgErrorFunctionFailedWithMessage,
  561. ['RegOpenKeyEx', IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
  562. try
  563. if RegQueryValueEx(K, PChar(Filename), nil, @CurType, nil, @Size) <> ERROR_SUCCESS then
  564. Exit;
  565. CountRead := False;
  566. Count := 0;
  567. try
  568. case CurType of
  569. REG_SZ:
  570. if RegQueryStringValue(K, PChar(Filename), CountStr) then begin
  571. Count := StrToInt(CountStr);
  572. CountRead := True;
  573. end;
  574. REG_BINARY: begin
  575. if (Size >= 1) and (Size <= 4) then begin
  576. if RegQueryValueEx(K, PChar(Filename), nil, nil, @Count, @Size) = ERROR_SUCCESS then
  577. { ^ relies on the high 3 bytes of Count being initialized to 0 }
  578. CountRead := True;
  579. end;
  580. end;
  581. REG_DWORD: begin
  582. Size := SizeOf(DWORD);
  583. if RegQueryValueEx(K, PChar(Filename), nil, nil, @Count, @Size) = ERROR_SUCCESS then
  584. CountRead := True;
  585. end;
  586. end;
  587. except
  588. { don't propagate exceptions (e.g. from StrToInt) }
  589. end;
  590. { If we failed to read the count, or it's in some type we don't recognize,
  591. don't touch it }
  592. if not CountRead then
  593. Exit;
  594. Dec(Count);
  595. if Integer(Count) <= 0 then begin
  596. Result := True;
  597. RegDeleteValue(K, PChar(Filename));
  598. end
  599. else begin
  600. case CurType of
  601. REG_SZ: begin
  602. CountStr := IntToStr(Count);
  603. RegSetValueEx(K, PChar(Filename), 0, REG_SZ, PChar(CountStr), (Length(CountStr)+1)*SizeOf(Char));
  604. end;
  605. REG_BINARY, REG_DWORD:
  606. RegSetValueEx(K, PChar(Filename), 0, CurType, @Count, SizeOf(Count));
  607. end;
  608. end;
  609. finally
  610. RegCloseKey(K);
  611. end;
  612. end;
  613. function GetFileDateTime(const DisableFsRedir: Boolean; const Filename: String;
  614. var DateTime: TFileTime): Boolean;
  615. var
  616. Handle: THandle;
  617. FindData: TWin32FindData;
  618. begin
  619. Handle := FindFirstFileRedir(DisableFsRedir, Filename, FindData);
  620. if Handle <> INVALID_HANDLE_VALUE then begin
  621. Windows.FindClose(Handle);
  622. if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then begin
  623. DateTime := FindData.ftLastWriteTime;
  624. Result := True;
  625. Exit;
  626. end;
  627. end;
  628. Result := False;
  629. DateTime.dwLowDateTime := 0;
  630. DateTime.dwHighDateTime := 0;
  631. end;
  632. function GetMD5OfFile(const DisableFsRedir: Boolean; const Filename: String): TMD5Digest;
  633. { Gets MD5 sum of the file Filename. An exception will be raised upon
  634. failure. }
  635. var
  636. F: TFile;
  637. NumRead: Cardinal;
  638. Context: TMD5Context;
  639. Buf: array[0..65535] of Byte;
  640. begin
  641. MD5Init(Context);
  642. F := TFileRedir.Create(DisableFsRedir, Filename, fdOpenExisting, faRead, fsReadWrite);
  643. try
  644. while True do begin
  645. NumRead := F.Read(Buf, SizeOf(Buf));
  646. if NumRead = 0 then Break;
  647. MD5Update(Context, Buf, NumRead);
  648. end;
  649. finally
  650. F.Free;
  651. end;
  652. Result := MD5Final(Context);
  653. end;
  654. function GetSHA1OfFile(const DisableFsRedir: Boolean; const Filename: String): TSHA1Digest;
  655. { Gets SHA-1 sum of the file Filename. An exception will be raised upon
  656. failure. }
  657. var
  658. F: TFile;
  659. NumRead: Cardinal;
  660. Context: TSHA1Context;
  661. Buf: array[0..65535] of Byte;
  662. begin
  663. SHA1Init(Context);
  664. F := TFileRedir.Create(DisableFsRedir, Filename, fdOpenExisting, faRead, fsReadWrite);
  665. try
  666. while True do begin
  667. NumRead := F.Read(Buf, SizeOf(Buf));
  668. if NumRead = 0 then Break;
  669. SHA1Update(Context, Buf, NumRead);
  670. end;
  671. finally
  672. F.Free;
  673. end;
  674. Result := SHA1Final(Context);
  675. end;
  676. function GetSHA256OfFile(const DisableFsRedir: Boolean; const Filename: String): String;
  677. { Gets SHA-256 sum as a string of the file Filename. An exception will be raised upon
  678. failure. }
  679. var
  680. PrevState: TPreviousFsRedirectionState;
  681. begin
  682. if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then
  683. InternalError('GetSHA256OfFile: DisableFsRedirectionIf failed.');
  684. try
  685. Result := THashSHA2.GetHashStringFromFile(Filename, SHA256);
  686. finally
  687. RestoreFsRedirection(PrevState);
  688. end;
  689. end;
  690. function GetMD5OfAnsiString(const S: AnsiString): TMD5Digest;
  691. begin
  692. Result := MD5Buf(Pointer(S)^, Length(S)*SizeOf(S[1]));
  693. end;
  694. function GetMD5OfUnicodeString(const S: UnicodeString): TMD5Digest;
  695. begin
  696. Result := MD5Buf(Pointer(S)^, Length(S)*SizeOf(S[1]));
  697. end;
  698. function GetSHA1OfAnsiString(const S: AnsiString): TSHA1Digest;
  699. begin
  700. Result := SHA1Buf(Pointer(S)^, Length(S)*SizeOf(S[1]));
  701. end;
  702. function GetSHA1OfUnicodeString(const S: UnicodeString): TSHA1Digest;
  703. begin
  704. Result := SHA1Buf(Pointer(S)^, Length(S)*SizeOf(S[1]));
  705. end;
  706. function GetSHA256OfAnsiString(const S: AnsiString): String;
  707. var
  708. M: TMemoryStream;
  709. begin
  710. M := TMemoryStream.Create;
  711. try
  712. M.Write(Pointer(S)^, Length(S)*SizeOf(S[1]));
  713. M.Seek(0, soFromBeginning);
  714. Result := THashSHA2.GetHashString(M, SHA256);
  715. finally
  716. M.Free;
  717. end;
  718. end;
  719. function GetSHA256OfUnicodeString(const S: UnicodeString): String;
  720. var
  721. M: TMemoryStream;
  722. begin
  723. M := TMemoryStream.Create;
  724. try
  725. M.Write(Pointer(S)^, Length(S)*SizeOf(S[1]));
  726. M.Seek(0, soFromBeginning);
  727. Result := THashSHA2.GetHashString(M, SHA256);
  728. finally
  729. M.Free;
  730. end;
  731. end;
  732. var
  733. SFCInitialized: Boolean;
  734. SfcIsFileProtectedFunc: function(RpcHandle: THandle; ProtFileName: PWideChar): BOOL; stdcall;
  735. function IsProtectedSystemFile(const DisableFsRedir: Boolean;
  736. const Filename: String): Boolean;
  737. { Returns True if the specified file is protected by Windows File Protection
  738. (and therefore can't be replaced). }
  739. var
  740. M: HMODULE;
  741. FN: String;
  742. begin
  743. if not SFCInitialized then begin
  744. M := SafeLoadLibrary(PChar(AddBackslash(GetSystemDir) + 'sfc.dll'),
  745. SEM_NOOPENFILEERRORBOX);
  746. if M <> 0 then
  747. SfcIsFileProtectedFunc := GetProcAddress(M, 'SfcIsFileProtected');
  748. SFCInitialized := True;
  749. end;
  750. if Assigned(SfcIsFileProtectedFunc) then begin
  751. { The function only accepts fully qualified paths. Also, as of
  752. IA-64 2003 SP1 and x64 XP, it does not respect file system redirection,
  753. so a call to ReplaceSystemDirWithSysWow64 is needed. }
  754. FN := PathExpand(Filename);
  755. if not DisableFsRedir then
  756. FN := ReplaceSystemDirWithSysWow64(FN);
  757. Result := SfcIsFileProtectedFunc(0, PChar(FN));
  758. end
  759. else
  760. Result := False; { Should never happen }
  761. end;
  762. procedure HandleProcessWait(ProcessHandle: THandle; const Wait: TExecWait;
  763. const ProcessMessagesProc: TProcedure; var ResultCode: Integer);
  764. begin
  765. try
  766. if Wait = ewWaitUntilIdle then begin
  767. repeat
  768. ProcessMessagesProc;
  769. until WaitForInputIdle(ProcessHandle, 50) <> WAIT_TIMEOUT;
  770. end;
  771. if Wait = ewWaitUntilTerminated then begin
  772. { Wait until the process returns, but still process any messages that
  773. arrive. }
  774. repeat
  775. { Process any pending messages first because MsgWaitForMultipleObjects
  776. (called below) only returns when *new* messages arrive }
  777. ProcessMessagesProc;
  778. until MsgWaitForMultipleObjects(1, ProcessHandle, False, INFINITE, QS_ALLINPUT) <> WAIT_OBJECT_0+1;
  779. { Process messages once more in case MsgWaitForMultipleObjects saw the
  780. process terminate and new messages arrive simultaneously. (Can't leave
  781. unprocessed messages waiting, or a subsequent call to WaitMessage
  782. won't see them.) }
  783. ProcessMessagesProc;
  784. end;
  785. { Get the exit code. Will be set to STILL_ACTIVE if not yet available }
  786. if not GetExitCodeProcess(ProcessHandle, DWORD(ResultCode)) then
  787. ResultCode := -1; { just in case }
  788. finally
  789. CloseHandle(ProcessHandle);
  790. end;
  791. end;
  792. function InstExec(const DisableFsRedir: Boolean; const Filename, Params: String;
  793. WorkingDir: String; const Wait: TExecWait; const ShowCmd: Integer;
  794. const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
  795. var
  796. CmdLine: String;
  797. StartupInfo: TStartupInfo;
  798. ProcessInfo: TProcessInformation;
  799. begin
  800. if Filename = '>' then
  801. CmdLine := Params
  802. else begin
  803. CmdLine := '"' + Filename + '"';
  804. if Params <> '' then
  805. CmdLine := CmdLine + ' ' + Params;
  806. if (CompareText(PathExtractExt(Filename), '.bat') = 0) or
  807. (CompareText(PathExtractExt(Filename), '.cmd') = 0) then begin
  808. { Use our own handling for .bat and .cmd files since passing them straight
  809. to CreateProcess on Windows NT 4.0 has problems: it doesn't properly
  810. quote the command line it passes to cmd.exe. This didn't work before:
  811. Filename: "c:\batch.bat"; Parameters: """abc"""
  812. And other Windows versions might have unknown quirks too, since
  813. CreateProcess isn't documented to accept .bat files in the first place. }
  814. { With cmd.exe, the whole command line must be quoted for quoted
  815. parameters to work. For example, this fails:
  816. cmd.exe /c "z:\blah.bat" "test"
  817. But this works:
  818. cmd.exe /c ""z:\blah.bat" "test""
  819. }
  820. CmdLine := '"' + AddBackslash(GetSystemDir) + 'cmd.exe" /C "' + CmdLine + '"'
  821. end;
  822. if WorkingDir = '' then
  823. WorkingDir := PathExtractDir(Filename);
  824. end;
  825. FillChar(StartupInfo, SizeOf(StartupInfo), 0);
  826. StartupInfo.cb := SizeOf(StartupInfo);
  827. StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  828. StartupInfo.wShowWindow := ShowCmd;
  829. if WorkingDir = '' then
  830. WorkingDir := GetSystemDir;
  831. Result := CreateProcessRedir(DisableFsRedir, nil, PChar(CmdLine), nil, nil, False,
  832. CREATE_DEFAULT_ERROR_MODE, nil, PChar(WorkingDir), StartupInfo, ProcessInfo);
  833. if not Result then begin
  834. ResultCode := GetLastError;
  835. Exit;
  836. end;
  837. { Don't need the thread handle, so close it now }
  838. CloseHandle(ProcessInfo.hThread);
  839. HandleProcessWait(ProcessInfo.hProcess, Wait, ProcessMessagesProc, ResultCode);
  840. end;
  841. function InstShellExec(const Verb, Filename, Params: String; WorkingDir: String;
  842. const Wait: TExecWait; const ShowCmd: Integer;
  843. const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
  844. var
  845. Info: TShellExecuteInfo;
  846. begin
  847. if WorkingDir = '' then begin
  848. WorkingDir := PathExtractDir(Filename);
  849. if WorkingDir = '' then
  850. WorkingDir := GetSystemDir;
  851. end;
  852. FillChar(Info, SizeOf(Info), 0);
  853. Info.cbSize := SizeOf(Info);
  854. Info.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_FLAG_DDEWAIT or
  855. SEE_MASK_NOCLOSEPROCESS;
  856. if Verb <> '' then
  857. Info.lpVerb := PChar(Verb);
  858. Info.lpFile := PChar(Filename);
  859. Info.lpParameters := PChar(Params);
  860. Info.lpDirectory := PChar(WorkingDir);
  861. Info.nShow := ShowCmd;
  862. Result := ShellExecuteEx(@Info);
  863. if not Result then begin
  864. ResultCode := GetLastError;
  865. Exit;
  866. end;
  867. ResultCode := STILL_ACTIVE;
  868. { A process handle won't always be returned, e.g. if DDE was used }
  869. if Info.hProcess <> 0 then
  870. HandleProcessWait(Info.hProcess, Wait, ProcessMessagesProc, ResultCode);
  871. end;
  872. function CheckForOrCreateMutexes(Mutexes: String; const Create: Boolean): Boolean;
  873. function MutexPos(const S: String): Integer;
  874. var
  875. I: Integer;
  876. begin
  877. for I := 1 to Length(S) do begin
  878. if (S[I] = ',') and ((I = 1) or (S[I-1] <> '\')) then begin
  879. Result := I;
  880. Exit;
  881. end;
  882. end;
  883. Result := 0;
  884. end;
  885. { Returns True if any of the mutexes in the comma-separated Mutexes string
  886. exist and Create is False }
  887. var
  888. I: Integer;
  889. M: String;
  890. H: THandle;
  891. begin
  892. Result := False;
  893. repeat
  894. I := MutexPos(Mutexes);
  895. if I = 0 then I := Maxint;
  896. M := Trim(Copy(Mutexes, 1, I-1));
  897. if M <> '' then begin
  898. StringChange(M, '\,', ',');
  899. if Create then begin
  900. CreateMutex(M)
  901. end else begin
  902. H := OpenMutex(SYNCHRONIZE, False, PChar(M));
  903. if H <> 0 then begin
  904. CloseHandle(H);
  905. Result := True;
  906. Break;
  907. end;
  908. end;
  909. end;
  910. Delete(Mutexes, 1, I);
  911. until Mutexes = '';
  912. end;
  913. function CheckForMutexes(const Mutexes: String): Boolean;
  914. begin
  915. Result := CheckForOrCreateMutexes(Mutexes, False);
  916. end;
  917. procedure CreateMutexes(const Mutexes: String);
  918. begin
  919. CheckForOrCreateMutexes(Mutexes, True);
  920. end;
  921. function ModifyPifFile(const Filename: String; const CloseOnExit: Boolean): Boolean;
  922. { Changes the "Close on exit" setting of a .pif file. Returns True if it was
  923. able to make the change. }
  924. var
  925. F: TFile;
  926. B: Byte;
  927. begin
  928. { Note: Specs on the .pif format were taken from
  929. http://smsoft.chat.ru/en/pifdoc.htm }
  930. Result := False;
  931. F := TFile.Create(Filename, fdOpenExisting, faReadWrite, fsNone);
  932. try
  933. { Is it a valid .pif file? }
  934. if F.Size.Lo >= $171 then begin
  935. F.Seek($63);
  936. F.ReadBuffer(B, SizeOf(B));
  937. { Toggle the "Close on exit" bit }
  938. if (B and $10 <> 0) <> CloseOnExit then begin
  939. B := B xor $10;
  940. F.Seek($63);
  941. F.WriteBuffer(B, SizeOf(B));
  942. end;
  943. Result := True;
  944. end;
  945. finally
  946. F.Free;
  947. end;
  948. end;
  949. function GetComputerNameString: String;
  950. var
  951. Buf: array[0..MAX_COMPUTERNAME_LENGTH] of Char;
  952. Size: DWORD;
  953. begin
  954. Size := SizeOf(Buf) div SizeOf(Buf[0]);
  955. if GetComputerName(Buf, Size) then
  956. Result := Buf
  957. else
  958. Result := '';
  959. end;
  960. function GetUserNameString: String;
  961. var
  962. Buf: array[0..256] of Char; { 256 = UNLEN }
  963. BufSize: DWORD;
  964. begin
  965. BufSize := SizeOf(Buf) div SizeOf(Buf[0]);
  966. if GetUserName(Buf, BufSize) then
  967. Result := Buf
  968. else
  969. Result := '';
  970. end;
  971. { Work around problem in D2's declaration of the function }
  972. function NewAdjustTokenPrivileges(TokenHandle: THandle; DisableAllPrivileges: BOOL;
  973. const NewState: TTokenPrivileges; BufferLength: DWORD;
  974. PreviousState: PTokenPrivileges; ReturnLength: PDWORD): BOOL; stdcall;
  975. external advapi32 name 'AdjustTokenPrivileges';
  976. function RestartComputer: Boolean;
  977. { Restarts the computer. }
  978. var
  979. Token: THandle;
  980. TokenPriv: TTokenPrivileges;
  981. const
  982. SE_SHUTDOWN_NAME = 'SeShutdownPrivilege'; { don't localize }
  983. begin
  984. if not OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY,
  985. Token) then begin
  986. Result := False;
  987. Exit;
  988. end;
  989. LookupPrivilegeValue(nil, SE_SHUTDOWN_NAME, TokenPriv.Privileges[0].Luid);
  990. TokenPriv.PrivilegeCount := 1;
  991. TokenPriv.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
  992. NewAdjustTokenPrivileges(Token, False, TokenPriv, 0, nil, nil);
  993. { Cannot test the return value of AdjustTokenPrivileges. }
  994. if GetLastError <> ERROR_SUCCESS then begin
  995. Result := False;
  996. Exit;
  997. end;
  998. Result := ExitWindowsEx(EWX_REBOOT, 0);
  999. { ExitWindowsEx returns True immediately. The system then asynchronously
  1000. sends WM_QUERYENDSESSION messages to all processes, including the current
  1001. process. The current process is not killed until it has received
  1002. WM_QUERYENDSESSION and WM_ENDSESSION messages. }
  1003. end;
  1004. procedure DelayDeleteFile(const DisableFsRedir: Boolean; const Filename: String;
  1005. const MaxTries, FirstRetryDelayMS, SubsequentRetryDelayMS: Integer);
  1006. { Attempts to delete Filename up to MaxTries times, retrying if the file is
  1007. in use. It sleeps FirstRetryDelayMS msec after the first try, and
  1008. SubsequentRetryDelayMS msec after subsequent tries. }
  1009. var
  1010. I: Integer;
  1011. begin
  1012. for I := 0 to MaxTries-1 do begin
  1013. if I = 1 then
  1014. Sleep(FirstRetryDelayMS)
  1015. else if I > 1 then
  1016. Sleep(SubsequentRetryDelayMS);
  1017. if DeleteFileRedir(DisableFsRedir, Filename) or
  1018. (GetLastError = ERROR_FILE_NOT_FOUND) or
  1019. (GetLastError = ERROR_PATH_NOT_FOUND) then
  1020. Break;
  1021. end;
  1022. end;
  1023. function MakePendingFileRenameOperationsChecksum: TMD5Digest;
  1024. { Calculates a checksum of the current PendingFileRenameOperations registry
  1025. value The caller can use this checksum to determine if
  1026. PendingFileRenameOperations was changed (perhaps by another program). }
  1027. var
  1028. Context: TMD5Context;
  1029. K: HKEY;
  1030. S: String;
  1031. begin
  1032. MD5Init(Context);
  1033. try
  1034. if RegOpenKeyExView(rvDefault, HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager',
  1035. 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  1036. if RegQueryMultiStringValue(K, 'PendingFileRenameOperations', S) then
  1037. MD5Update(Context, S[1], Length(S)*SizeOf(S[1]));
  1038. { When "PendingFileRenameOperations" is full, it spills over into
  1039. "PendingFileRenameOperations2" }
  1040. if RegQueryMultiStringValue(K, 'PendingFileRenameOperations2', S) then
  1041. MD5Update(Context, S[1], Length(S)*SizeOf(S[1]));
  1042. RegCloseKey(K);
  1043. end;
  1044. except
  1045. { don't propagate exceptions }
  1046. end;
  1047. Result := MD5Final(Context);
  1048. end;
  1049. procedure EnumFileReplaceOperationsFilenames(const EnumFunc: TEnumFROFilenamesProc;
  1050. Param: Pointer);
  1051. { Enumerates all the filenames in the current PendingFileRenameOperations
  1052. registry value or WININIT.INI file. The function does not distinguish between
  1053. source and destination filenames; it enumerates both. }
  1054. procedure DoValue(const K: HKEY; const ValueName: PChar);
  1055. var
  1056. S: String;
  1057. P, PEnd: PChar;
  1058. begin
  1059. if not RegQueryMultiStringValue(K, ValueName, S) then
  1060. Exit;
  1061. P := PChar(S);
  1062. PEnd := P + Length(S);
  1063. while P < PEnd do begin
  1064. if P[0] = '!' then
  1065. { Note: '!' means that MoveFileEx was called with the
  1066. MOVEFILE_REPLACE_EXISTING flag }
  1067. Inc(P);
  1068. if StrLComp(P, '\??\', 4) = 0 then begin
  1069. Inc(P, 4);
  1070. if P[0] <> #0 then
  1071. EnumFunc(P, Param);
  1072. end;
  1073. Inc(P, StrLen(P) + 1);
  1074. end;
  1075. end;
  1076. var
  1077. K: HKEY;
  1078. begin
  1079. if RegOpenKeyExView(rvDefault, HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager',
  1080. 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  1081. try
  1082. DoValue(K, 'PendingFileRenameOperations');
  1083. { When "PendingFileRenameOperations" is full, it spills over into
  1084. "PendingFileRenameOperations2" }
  1085. DoValue(K, 'PendingFileRenameOperations2');
  1086. finally
  1087. RegCloseKey(K);
  1088. end;
  1089. end;
  1090. end;
  1091. procedure UnregisterFont(const FontName, FontFilename: String; const PerUserFont: Boolean);
  1092. var
  1093. RootKey, K: HKEY;
  1094. begin
  1095. if PerUserFont then
  1096. RootKey := HKEY_CURRENT_USER
  1097. else
  1098. RootKey := HKEY_LOCAL_MACHINE;
  1099. if RegOpenKeyExView(rvDefault, RootKey, 'Software\Microsoft\Windows NT\CurrentVersion\Fonts',
  1100. 0, KEY_SET_VALUE, K) = ERROR_SUCCESS then begin
  1101. RegDeleteValue(K, PChar(FontName));
  1102. RegCloseKey(K);
  1103. end;
  1104. if RemoveFontResource(PChar(FontFilename)) then
  1105. SendNotifyMessage(HWND_BROADCAST, WM_FONTCHANGE, 0, 0);
  1106. end;
  1107. function GetSpaceOnDisk(const DisableFsRedir: Boolean; const DriveRoot: String;
  1108. var FreeBytes, TotalBytes: Integer64): Boolean;
  1109. var
  1110. GetDiskFreeSpaceExFunc: function(lpDirectoryName: PChar;
  1111. lpFreeBytesAvailable: PLargeInteger; lpTotalNumberOfBytes: PLargeInteger;
  1112. lpTotalNumberOfFreeBytes: PLargeInteger): BOOL; stdcall;
  1113. PrevState: TPreviousFsRedirectionState;
  1114. SectorsPerCluster, BytesPerSector, FreeClusters, TotalClusters: Cardinal;
  1115. begin
  1116. { NOTE: The docs claim that GetDiskFreeSpace supports UNC paths on
  1117. Windows 95 OSR2 and later. But that does not seem to be the case in my
  1118. tests; it fails with error 50 on Windows 95 through Me.
  1119. GetDiskFreeSpaceEx, however, *does* succeed with UNC paths, so use it
  1120. if available. }
  1121. GetDiskFreeSpaceExFunc := GetProcAddress(GetModuleHandle(kernel32),
  1122. 'GetDiskFreeSpaceExW');
  1123. if not DisableFsRedirectionIf(DisableFsRedir, PrevState) then begin
  1124. Result := False;
  1125. Exit;
  1126. end;
  1127. try
  1128. if Assigned(@GetDiskFreeSpaceExFunc) then begin
  1129. Result := GetDiskFreeSpaceExFunc(PChar(AddBackslash(PathExpand(DriveRoot))),
  1130. @TLargeInteger(FreeBytes), @TLargeInteger(TotalBytes), nil);
  1131. end
  1132. else begin
  1133. Result := GetDiskFreeSpace(PChar(AddBackslash(PathExtractDrive(PathExpand(DriveRoot)))),
  1134. DWORD(SectorsPerCluster), DWORD(BytesPerSector), DWORD(FreeClusters),
  1135. DWORD(TotalClusters));
  1136. if Result then begin
  1137. { The result of GetDiskFreeSpace does not cap at 2GB, so we must use a
  1138. 64-bit multiply operation to avoid an overflow. }
  1139. Multiply32x32to64(BytesPerSector * SectorsPerCluster, FreeClusters,
  1140. FreeBytes);
  1141. Multiply32x32to64(BytesPerSector * SectorsPerCluster, TotalClusters,
  1142. TotalBytes);
  1143. end;
  1144. end;
  1145. finally
  1146. RestoreFsRedirection(PrevState);
  1147. end;
  1148. end;
  1149. function GetSpaceOnNearestMountPoint(const DisableFsRedir: Boolean;
  1150. const StartDir: String; var FreeBytes, TotalBytes: Integer64): Boolean;
  1151. { Gets the free and total space available on the specified directory. If that
  1152. fails (e.g. if the directory does not exist), then it strips off the last
  1153. component of the path and tries again. This repeats until it reaches the
  1154. root. Returns True if successful. }
  1155. var
  1156. Dir: String;
  1157. LastLen: Integer;
  1158. begin
  1159. Result := False;
  1160. Dir := RemoveBackslashUnlessRoot(StartDir);
  1161. LastLen := 0;
  1162. while Length(Dir) <> LastLen do begin
  1163. if GetSpaceOnDisk(DisableFsRedir, Dir, FreeBytes, TotalBytes) then begin
  1164. Result := True;
  1165. Break;
  1166. end;
  1167. LastLen := Length(Dir);
  1168. Dir := PathExtractDir(Dir);
  1169. end;
  1170. end;
  1171. procedure RefreshEnvironment;
  1172. { Notifies other applications (Explorer) that environment variables have
  1173. changed. Based on code from KB article 104011. }
  1174. var
  1175. MsgResult: DWORD_PTR;
  1176. begin
  1177. { Note: We originally used SendNotifyMessage to broadcast the message but it
  1178. turned out that while it worked fine on NT 4 and 2000 it didn't work on XP
  1179. -- the string "Environment" in lParam would be garbled on the receiving
  1180. end (why I'm not exactly sure). We now use SendMessageTimeout as directed
  1181. in the KB article 104011. It isn't as elegant since it could cause us to
  1182. be delayed if another app is hung, but it'll have to do. }
  1183. SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
  1184. LPARAM(PChar('Environment')), SMTO_ABORTIFHUNG, 5000, @MsgResult);
  1185. end;
  1186. procedure SplitNewParamStr(const Index: Integer; var AName, AValue: String);
  1187. { Reads a command line parameter. If it is in the form "/PARAM=VALUE" then
  1188. AName is set to "/PARAM=" and AValue is set to "VALUE". Otherwise, the full
  1189. parameter is stored in AName, and AValue is set to an empty string. }
  1190. var
  1191. S: String;
  1192. P: Integer;
  1193. begin
  1194. S := NewParamStr(Index);
  1195. if (S <> '') and (S[1] = '/') then begin
  1196. P := PathPos('=', S);
  1197. if P <> 0 then begin
  1198. AName := Copy(S, 1, P);
  1199. AValue := Copy(S, P+1, Maxint);
  1200. Exit;
  1201. end;
  1202. end;
  1203. AName := S;
  1204. AValue := '';
  1205. end;
  1206. function DetermineDefaultLanguage(const GetLanguageEntryProc: TGetLanguageEntryProc;
  1207. const Method: TSetupLanguageDetectionMethod; const LangParameter: String;
  1208. var ResultIndex: Integer): TDetermineDefaultLanguageResult;
  1209. { Finds the index of the language entry that most closely matches the user's
  1210. UI language / locale. If no match is found, ResultIndex is set to 0. }
  1211. function GetCodePageFromLangID(const ALangID: LANGID): Integer;
  1212. const
  1213. LOCALE_RETURN_NUMBER = $20000000;
  1214. var
  1215. CodePage: DWORD;
  1216. begin
  1217. if GetLocaleInfo(ALangID, LOCALE_IDEFAULTANSICODEPAGE or LOCALE_RETURN_NUMBER,
  1218. PChar(@CodePage), SizeOf(CodePage) div SizeOf(Char)) > 0 then
  1219. Result := Integer(CodePage)
  1220. else
  1221. Result := -1;
  1222. end;
  1223. var
  1224. I: Integer;
  1225. LangEntry: PSetupLanguageEntry;
  1226. UILang: LANGID;
  1227. begin
  1228. ResultIndex := 0;
  1229. Result := ddNoMatch;
  1230. if LangParameter <> '' then begin
  1231. { Use the language specified on the command line, if available }
  1232. I := 0;
  1233. while GetLanguageEntryProc(I, LangEntry) do begin
  1234. if CompareText(LangParameter, LangEntry.Name) = 0 then begin
  1235. ResultIndex := I;
  1236. Result := ddMatchLangParameter;
  1237. Exit;
  1238. end;
  1239. Inc(I);
  1240. end;
  1241. end;
  1242. case Method of
  1243. ldUILanguage: UILang := GetUILanguage;
  1244. ldLocale: UILang := GetUserDefaultLangID;
  1245. else
  1246. { ldNone }
  1247. UILang := 0;
  1248. end;
  1249. if UILang <> 0 then begin
  1250. { Look for a primary + sub language ID match }
  1251. I := 0;
  1252. while GetLanguageEntryProc(I, LangEntry) do begin
  1253. if LangEntry.LanguageID = UILang then begin
  1254. ResultIndex := I;
  1255. Result := ddMatch;
  1256. Exit;
  1257. end;
  1258. Inc(I);
  1259. end;
  1260. { Look for just a primary language ID match }
  1261. I := 0;
  1262. while GetLanguageEntryProc(I, LangEntry) do begin
  1263. if (LangEntry.LanguageID and $3FF) = (UILang and $3FF) then begin
  1264. { On Unicode, there is no LanguageCodePage filter, so we have to check
  1265. the language IDs to ensure we don't return Simplified Chinese on a
  1266. Traditional Chinese system, or vice versa.
  1267. If the default ANSI code pages associated with the language IDs are
  1268. equal, then there is no Simplified/Traditional discrepancy.
  1269. Simplified Chinese LANGIDs ($0804, $1004) use CP 936
  1270. Traditional Chinese LANGIDs ($0404, $0C04, $1404) use CP 950 }
  1271. if ((UILang and $3FF) <> LANG_CHINESE) or
  1272. (GetCodePageFromLangID(LangEntry.LanguageID) = GetCodePageFromLangID(UILang)) then
  1273. begin
  1274. ResultIndex := I;
  1275. Result := ddMatch;
  1276. Exit;
  1277. end;
  1278. end;
  1279. Inc(I);
  1280. end;
  1281. end;
  1282. end;
  1283. function ForceDirectories(const DisableFsRedir: Boolean; Dir: String): Boolean;
  1284. begin
  1285. Dir := RemoveBackslashUnlessRoot(Dir);
  1286. if (PathExtractPath(Dir) = Dir) or DirExistsRedir(DisableFsRedir, Dir) then
  1287. Result := True
  1288. else
  1289. Result := ForceDirectories(DisableFsRedir, PathExtractPath(Dir)) and
  1290. CreateDirectoryRedir(DisableFsRedir, Dir);
  1291. end;
  1292. { TSimpleStringList }
  1293. procedure TSimpleStringList.Add(const S: String);
  1294. var
  1295. Delta: Integer;
  1296. begin
  1297. if FCount = FCapacity then begin
  1298. if FCapacity > 64 then Delta := FCapacity div 4 else
  1299. if FCapacity > 8 then Delta := 16 else
  1300. Delta := 4;
  1301. SetCapacity(FCapacity + Delta);
  1302. end;
  1303. FList^[FCount] := S;
  1304. Inc(FCount);
  1305. end;
  1306. procedure TSimpleStringList.AddIfDoesntExist(const S: String);
  1307. begin
  1308. if IndexOf(S) = -1 then
  1309. Add(S);
  1310. end;
  1311. procedure TSimpleStringList.SetCapacity(NewCapacity: Integer);
  1312. begin
  1313. ReallocMem(FList, NewCapacity * SizeOf(Pointer));
  1314. if NewCapacity > FCapacity then
  1315. FillChar(FList^[FCapacity], (NewCapacity - FCapacity) * SizeOf(Pointer), 0);
  1316. FCapacity := NewCapacity;
  1317. end;
  1318. procedure TSimpleStringList.Clear;
  1319. begin
  1320. if FCount <> 0 then Finalize(FList^[0], FCount);
  1321. FCount := 0;
  1322. SetCapacity(0);
  1323. end;
  1324. function TSimpleStringList.Get(Index: Integer): String;
  1325. begin
  1326. Result := FList^[Index];
  1327. end;
  1328. function TSimpleStringList.IndexOf(const S: String): Integer;
  1329. { Note: This is case-sensitive, unlike TStringList.IndexOf }
  1330. var
  1331. I: Integer;
  1332. begin
  1333. Result := -1;
  1334. for I := 0 to FCount-1 do
  1335. if FList^[I] = S then begin
  1336. Result := I;
  1337. Break;
  1338. end;
  1339. end;
  1340. destructor TSimpleStringList.Destroy;
  1341. begin
  1342. Clear;
  1343. inherited Destroy;
  1344. end;
  1345. end.