Shared.CommonFunc.pas 73 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119
  1. unit Shared.CommonFunc;
  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. Common non-VCL functions
  8. }
  9. {$B-,R-}
  10. interface
  11. uses
  12. Windows, SysUtils, Classes;
  13. type
  14. TOneShotTimer = record
  15. private
  16. FLastElapsed: Cardinal;
  17. FStartTick: DWORD;
  18. FTimeout: Cardinal;
  19. public
  20. function Expired: Boolean;
  21. procedure SleepUntilExpired;
  22. procedure Start(const Timeout: Cardinal);
  23. function TimeElapsed: Cardinal;
  24. function TimeRemaining: Cardinal;
  25. end;
  26. TStrongRandom = record
  27. strict private
  28. class var
  29. FBCryptGenRandomFunc: function(hAlgorithm: THandle; var pbBuffer;
  30. cbBuffer: ULONG; dwFlags: ULONG): NTSTATUS; stdcall;
  31. class procedure InitBCrypt; static;
  32. public
  33. class procedure GenerateBytes(out Buf; const Count: Cardinal); static;
  34. class function GenerateUInt32: UInt32; static;
  35. class function GenerateUInt32Range(const ARange: UInt32): UInt32; static;
  36. class function GenerateUInt64: UInt64; static;
  37. end;
  38. TFileTimeHelper = record helper for TFileTime
  39. procedure Clear;
  40. function HasTime: Boolean;
  41. end;
  42. TLogProc = procedure(const S: String; const Error, FirstLine: Boolean; const Data: NativeInt);
  43. TOutputMode = (omLog, omCapture);
  44. TCreateProcessOutputReaderPipe = record
  45. OKToRead: Boolean;
  46. PipeRead, PipeWrite: THandle;
  47. Buffer: AnsiString;
  48. CaptureList: TStringList;
  49. end;
  50. TCreateProcessOutputReader = class
  51. private
  52. FMaxTotalBytesToRead: Cardinal;
  53. FMaxTotalLinesToRead: Cardinal;
  54. FTotalBytesRead: Cardinal;
  55. FTotalLinesRead: Cardinal;
  56. FStdInNulDevice: THandle;
  57. FStdOut: TCreateProcessOutputReaderPipe;
  58. FStdErr: TCreateProcessOutputReaderPipe;
  59. FLogProc: TLogProc;
  60. FLogProcData: NativeInt;
  61. FNextLineIsFirstLine: Boolean;
  62. FMode: TOutputMode;
  63. FCaptureOutList: TStringList;
  64. FCaptureErrList: TStringList;
  65. FCaptureError: Boolean;
  66. procedure CloseAndClearHandle(var Handle: THandle);
  67. procedure HandleAndLogErrorFmt(const S: String; const Args: array of const);
  68. public
  69. constructor Create(const ALogProc: TLogProc; const ALogProcData: NativeInt; AMode: TOutputMode = omLog);
  70. destructor Destroy; override;
  71. procedure UpdateStartupInfo(var StartupInfo: TStartupInfo);
  72. procedure NotifyCreateProcessDone;
  73. procedure Read(const LastRead: Boolean);
  74. property CaptureOutList: TStringList read FCaptureOutList;
  75. property CaptureErrList: TStringList read FCaptureErrList;
  76. property CaptureError: Boolean read FCaptureError;
  77. end;
  78. TRegView = (rvDefault, rv32Bit, rv64Bit);
  79. TFileOperationFailingNextAction = (naStopAndFail, naStopAndSucceed, naRetry);
  80. TFileOperationFunc = reference to function(out LastError: Cardinal): Boolean;
  81. TFileOperationFailingProc = reference to procedure(const LastError: Cardinal);
  82. TFileOperationFailingExProc = reference to procedure(const LastError: Cardinal; var RetriesLeft: Integer; var NextAction: TFileOperationFailingNextAction);
  83. TFileOperationFailedProc = reference to procedure(const LastError: Cardinal; var TryOnceMore: Boolean);
  84. const
  85. IsCurrentProcess64Bit = {$IFDEF WIN64} True {$ELSE} False {$ENDIF};
  86. RegViews64Bit = [{$IFDEF WIN64} rvDefault, {$ENDIF} rv64Bit];
  87. function NewFileExists(const Name: String): Boolean;
  88. function DirExists(const Name: String): Boolean;
  89. function FileOrDirExists(const Name: String): Boolean;
  90. function IsDirectoryAndNotReparsePoint(const Name: String): Boolean;
  91. function GetIniString(const Section, Key: String; Default: String; const Filename: String): String;
  92. function GetIniInt(const Section, Key: String; const Default, Min, Max: Longint; const Filename: String): Longint;
  93. function GetIniBool(const Section, Key: String; const Default: Boolean; const Filename: String): Boolean;
  94. function IniKeyExists(const Section, Key, Filename: String): Boolean;
  95. function IsIniSectionEmpty(const Section, Filename: String): Boolean;
  96. function SetIniString(const Section, Key, Value, Filename: String): Boolean;
  97. function SetIniInt(const Section, Key: String; const Value: Longint; const Filename: String): Boolean;
  98. function SetIniBool(const Section, Key: String; const Value: Boolean; const Filename: String): Boolean;
  99. procedure DeleteIniEntry(const Section, Key, Filename: String);
  100. procedure DeleteIniSection(const Section, Filename: String);
  101. function GetEnv(const EnvVar: String): String;
  102. function GetCmdTail: String;
  103. function GetCmdTailEx(StartIndex: Integer): String;
  104. function NewParamCount: Integer;
  105. function NewParamStr(Index: Integer): string;
  106. function AddQuotes(const S: String): String;
  107. function RemoveQuotes(const S: String): String;
  108. function GetWinDir: String;
  109. function GetSystemWinDir: String;
  110. function GetSystemDir: String;
  111. function GetSysWow64Dir: String;
  112. function GetSysNativeDir(const IsWin64: Boolean): String;
  113. function GetTempDir: String;
  114. function StringChange(var S: String; const FromStr, ToStr: String): Integer;
  115. function StringChangeEx(var S: String; const FromStr, ToStr: String;
  116. const SupportDBCS: Boolean): Integer;
  117. function AdjustLength(var S: String; const Res: Cardinal): Boolean;
  118. function ConvertConstPercentStr(var S: String): Boolean;
  119. function ConvertPercentStr(var S: String): Boolean;
  120. function ConstPos(const Ch: Char; const S: String): Integer;
  121. function SkipPastConst(const S: String; const Start: Integer): Integer;
  122. function RegQueryStringValue(H: HKEY; Name: PChar; var ResultStr: String; AllowDWord: Boolean = False): Boolean;
  123. function RegQueryMultiStringValue(H: HKEY; Name: PChar; var ResultStr: String): Boolean;
  124. function RegValueExists(H: HKEY; Name: PChar): Boolean;
  125. function RegCreateKeyExView(const RegView: TRegView; hKey: HKEY; lpSubKey: PChar;
  126. Reserved: DWORD; lpClass: PChar; dwOptions: DWORD; samDesired: REGSAM;
  127. lpSecurityAttributes: PSecurityAttributes; var phkResult: HKEY;
  128. lpdwDisposition: PDWORD): Longint;
  129. function RegOpenKeyExView(const RegView: TRegView; hKey: HKEY; lpSubKey: PChar;
  130. ulOptions: DWORD; samDesired: REGSAM; var phkResult: HKEY): Longint;
  131. function RegDeleteKeyView(const RegView: TRegView; const Key: HKEY; const Name: PChar): Longint;
  132. function RegDeleteKeyIncludingSubkeys(const RegView: TRegView; const Key: HKEY; const Name: PChar): Longint;
  133. function RegDeleteKeyIfEmpty(const RegView: TRegView; const RootKey: HKEY; const SubkeyName: PChar): Longint;
  134. function GetShellFolderPath(const FolderID: Integer): String;
  135. function GetCurrentUserSid: String;
  136. function IsAdminLoggedOn: Boolean;
  137. function IsPowerUserLoggedOn: Boolean;
  138. function IsMultiByteString(const S: AnsiString): Boolean;
  139. function FontExists(const FaceName: String): Boolean;
  140. function GetUILanguage: LANGID;
  141. function RemoveAccelChar(const S: String): String;
  142. function GetTextWidth(const DC: HDC; S: String; const Prefix: Boolean): Integer;
  143. function AddPeriod(const S: String): String;
  144. function GetExceptMessage: String;
  145. function GetPreferredUIFont: String;
  146. function IsWildcard(const Pattern: String): Boolean;
  147. function WildcardMatch(const Text, Pattern: PChar): Boolean;
  148. function IntMax(const A, B: Integer): Integer;
  149. function Win32ErrorString(ErrorCode: DWORD): String;
  150. function DeleteDirTree(const Dir: String): Boolean;
  151. function SetNTFSCompression(const FileOrDir: String; Compress: Boolean): Boolean;
  152. procedure AddToWindowMessageFilterEx(const Wnd: HWND; const Msg: UINT);
  153. function ShutdownBlockReasonCreate(Wnd: HWND; const Reason: String): Boolean;
  154. function ShutdownBlockReasonDestroy(Wnd: HWND): Boolean;
  155. function TryStrToBoolean(const S: String; var BoolResult: Boolean): Boolean;
  156. procedure WaitMessageWithTimeout(const Milliseconds: DWORD);
  157. function MoveFileReplace(const ExistingFileName, NewFileName: String): Boolean;
  158. procedure CreateMutex(const MutexName: String);
  159. function HighContrastActive: Boolean;
  160. function CurrentWindowsVersionAtLeast(const AMajor, AMinor: Byte; const ABuild: Word = 0): Boolean;
  161. function DarkModeActive: Boolean;
  162. function DeleteFileOrDirByHandle(const H: THandle): Boolean;
  163. function CompareInt64(const N1, N2: Int64): Integer;
  164. function HighLowToInt64(const High, Low: UInt32): Int64;
  165. function HighLowToUInt64(const High, Low: UInt32): UInt64;
  166. function FindDataFileSizeToInt64(const FindData: TWin32FindData): Int64;
  167. function FileTimeToUInt64(const FileTime: TFileTime): UInt64;
  168. function StrToWnd(const S: String): HWND;
  169. function PerformFileOperationWithRetries(const MaxRetries: Integer; const AlsoRetryOnAlreadyExists: Boolean;
  170. const Op: TFileOperationFunc; const Failing: TFileOperationFailingProc; const Failed: TFileOperationFailedProc): Boolean; overload;
  171. function PerformFileOperationWithRetries(const MaxRetries: Integer; const AlsoRetryOnAlreadyExists: Boolean;
  172. const Op: TFileOperationFunc; const Failing: TFileOperationFailingExProc; const Failed: TFileOperationFailedProc): Boolean; overload;
  173. function Is64BitPEImage(const Filename: String): Boolean;
  174. implementation
  175. uses
  176. PathFunc, UnsignedFunc,
  177. Shared.FileClass;
  178. function InternalGetFileAttr(const Name: String): DWORD;
  179. begin
  180. Result := GetFileAttributes(PChar(RemoveBackslashUnlessRoot(Name)));
  181. end;
  182. function NewFileExists(const Name: String): Boolean;
  183. { Returns True if the specified file exists.
  184. This function is better than Delphi's FileExists function because it works
  185. on files in directories that don't have "list" permission. There is, however,
  186. one other difference: FileExists allows wildcards, but this function does
  187. not. }
  188. begin
  189. var Attr := GetFileAttributes(PChar(Name));
  190. Result := (Attr <> INVALID_FILE_ATTRIBUTES) and (Attr and faDirectory = 0);
  191. end;
  192. function DirExists(const Name: String): Boolean;
  193. { Returns True if the specified directory name exists. The specified name
  194. may include a trailing backslash.
  195. NOTE: Delphi's FileCtrl unit has a similar function called DirectoryExists.
  196. However, the implementation is different between Delphi 1 and 2. (Delphi 1
  197. does not count hidden or system directories as existing.) }
  198. begin
  199. var Attr := InternalGetFileAttr(Name);
  200. Result := (Attr <> INVALID_FILE_ATTRIBUTES) and (Attr and faDirectory <> 0);
  201. end;
  202. function FileOrDirExists(const Name: String): Boolean;
  203. { Returns True if the specified directory or file name exists. The specified
  204. name may include a trailing backslash. }
  205. begin
  206. Result := InternalGetFileAttr(Name) <> INVALID_FILE_ATTRIBUTES;
  207. end;
  208. function IsDirectoryAndNotReparsePoint(const Name: String): Boolean;
  209. { Returns True if the specified directory exists and is NOT a reparse point. }
  210. const
  211. FILE_ATTRIBUTE_REPARSE_POINT = $00000400;
  212. var
  213. Attr: DWORD;
  214. begin
  215. Attr := GetFileAttributes(PChar(Name));
  216. Result := (Attr <> INVALID_FILE_ATTRIBUTES) and
  217. (Attr and FILE_ATTRIBUTE_DIRECTORY <> 0) and
  218. (Attr and FILE_ATTRIBUTE_REPARSE_POINT = 0);
  219. end;
  220. function GetIniString(const Section, Key: String; Default: String;
  221. const Filename: String): String;
  222. var
  223. BufSize, Len: Cardinal;
  224. begin
  225. { On Windows 9x, Get*ProfileString can modify the lpDefault parameter, so
  226. make sure it's unique and not read-only }
  227. UniqueString(Default);
  228. BufSize := 256;
  229. while True do begin
  230. SetString(Result, nil, BufSize);
  231. if Filename <> '' then
  232. Len := GetPrivateProfileString(PChar(Section), PChar(Key), PChar(Default),
  233. @Result[1], BufSize, PChar(Filename))
  234. else
  235. Len := GetProfileString(PChar(Section), PChar(Key), PChar(Default),
  236. @Result[1], BufSize);
  237. { Work around bug present on Windows NT/2000 (not 95): When lpDefault is
  238. too long to fit in the buffer, nSize is returned (null terminator
  239. counted) instead of nSize-1 (what it's supposed to return). So don't
  240. trust the returned length; calculate it ourself.
  241. Note: This also ensures the string can never include embedded nulls. }
  242. if Len <> 0 then
  243. Len := StrLen(PChar(Result));
  244. { Break if the string fits, or if it's apparently 64 KB or longer.
  245. No point in increasing buffer size past 64 KB because the length
  246. returned by Windows 2000 seems to be mod 65536. And Windows 95 returns
  247. 0 on values longer than ~32 KB.
  248. Note: The docs say the function returns "nSize minus one" if the buffer
  249. is too small, but I'm willing to bet it can be "minus two" if the last
  250. character is double-byte. Let's just be extremely paranoid and check for
  251. BufSize-8. }
  252. if (Len < BufSize-8) or (BufSize >= 65536) then begin
  253. SetLength(Result, Len);
  254. Break;
  255. end;
  256. { Otherwise double the buffer size and try again }
  257. BufSize := BufSize * 2;
  258. end;
  259. end;
  260. function GetIniInt(const Section, Key: String;
  261. const Default, Min, Max: Longint; const Filename: String): Longint;
  262. { Reads a Longint from an INI file. If the Longint read is not between Min/Max
  263. then it returns Default. If Min=Max then Min/Max are ignored }
  264. var
  265. S: String;
  266. E: Integer;
  267. begin
  268. S := GetIniString(Section, Key, '', Filename);
  269. if S = '' then
  270. Result := Default
  271. else begin
  272. Val(S, Result, E);
  273. if (E <> 0) or ((Min <> Max) and ((Result < Min) or (Result > Max))) then
  274. Result := Default;
  275. end;
  276. end;
  277. function GetIniBool(const Section, Key: String; const Default: Boolean;
  278. const Filename: String): Boolean;
  279. begin
  280. Result := GetIniInt(Section, Key, Ord(Default), 0, 0, Filename) <> 0;
  281. end;
  282. function IniKeyExists(const Section, Key, Filename: String): Boolean;
  283. function Equals(const Default: PChar): Boolean;
  284. var
  285. Test: array[0..7] of Char;
  286. begin
  287. Test[0] := #0;
  288. if Filename <> '' then
  289. GetPrivateProfileString(PChar(Section), PChar(Key), Default,
  290. Test, SizeOf(Test) div SizeOf(Test[0]), PChar(Filename))
  291. else
  292. GetProfileString(PChar(Section), PChar(Key), Default,
  293. Test, SizeOf(Test) div SizeOf(Test[0]));
  294. Result := lstrcmp(Test, Default) = 0;
  295. end;
  296. begin
  297. { If the key does not exist, a default string is returned both times. }
  298. Result := not Equals('x1234x') or not Equals('x5678x'); { <- don't change }
  299. end;
  300. function IsIniSectionEmpty(const Section, Filename: String): Boolean;
  301. var
  302. Test: array[0..255] of Char;
  303. begin
  304. Test[0] := #0;
  305. if Filename <> '' then
  306. GetPrivateProfileString(PChar(Section), nil, '', Test,
  307. SizeOf(Test) div SizeOf(Test[0]), PChar(Filename))
  308. else
  309. GetProfileString(PChar(Section), nil, '', Test,
  310. SizeOf(Test) div SizeOf(Test[0]));
  311. Result := Test[0] = #0;
  312. end;
  313. function SetIniString(const Section, Key, Value, Filename: String): Boolean;
  314. begin
  315. if Filename <> '' then
  316. Result := WritePrivateProfileString(PChar(Section), PChar(Key),
  317. PChar(Value), PChar(Filename))
  318. else
  319. Result := WriteProfileString(PChar(Section), PChar(Key),
  320. PChar(Value));
  321. end;
  322. function SetIniInt(const Section, Key: String; const Value: Longint;
  323. const Filename: String): Boolean;
  324. begin
  325. Result := SetIniString(Section, Key, IntToStr(Value), Filename);
  326. end;
  327. function SetIniBool(const Section, Key: String; const Value: Boolean;
  328. const Filename: String): Boolean;
  329. begin
  330. Result := SetIniInt(Section, Key, Ord(Value), Filename);
  331. end;
  332. procedure DeleteIniEntry(const Section, Key, Filename: String);
  333. begin
  334. if Filename <> '' then
  335. WritePrivateProfileString(PChar(Section), PChar(Key),
  336. nil, PChar(Filename))
  337. else
  338. WriteProfileString(PChar(Section), PChar(Key),
  339. nil);
  340. end;
  341. procedure DeleteIniSection(const Section, Filename: String);
  342. begin
  343. if Filename <> '' then
  344. WritePrivateProfileString(PChar(Section), nil, nil,
  345. PChar(Filename))
  346. else
  347. WriteProfileString(PChar(Section), nil, nil);
  348. end;
  349. function GetEnv(const EnvVar: String): String;
  350. { Gets the value of the specified environment variable. (Just like TP's GetEnv) }
  351. var
  352. Res: DWORD;
  353. begin
  354. SetLength(Result, 255);
  355. repeat
  356. Res := GetEnvironmentVariable(PChar(EnvVar), PChar(Result), ULength(Result));
  357. if Res = 0 then begin
  358. Result := '';
  359. Break;
  360. end;
  361. until AdjustLength(Result, Res);
  362. end;
  363. function GetParamStr(const P: PChar; var Param: String): PChar;
  364. function Extract(P: PChar; const Buffer: PChar; var Len: Integer): PChar;
  365. var
  366. InQuote: Boolean;
  367. begin
  368. Len := 0;
  369. InQuote := False;
  370. while (P^ <> #0) and ((P^ > ' ') or InQuote) do begin
  371. if P^ = '"' then
  372. InQuote := not InQuote
  373. else begin
  374. if Assigned(Buffer) then
  375. Buffer[Len] := P^;
  376. Inc(Len);
  377. end;
  378. Inc(P);
  379. end;
  380. Result := P;
  381. end;
  382. var
  383. Len: Integer;
  384. Buffer: String;
  385. begin
  386. Extract(P, nil, Len);
  387. SetString(Buffer, nil, Len);
  388. Result := Extract(P, @Buffer[1], Len);
  389. Param := Buffer;
  390. while (Result^ <> #0) and (Result^ <= ' ') do
  391. Inc(Result);
  392. end;
  393. function GetCmdTail: String;
  394. { Returns all command line parameters passed to the process as a single
  395. string. }
  396. var
  397. S: String;
  398. begin
  399. Result := GetParamStr(GetCommandLine, S);
  400. end;
  401. function GetCmdTailEx(StartIndex: Integer): String;
  402. { Returns all command line parameters passed to the process as a single
  403. string, starting with StartIndex (one-based). }
  404. var
  405. P: PChar;
  406. S: String;
  407. begin
  408. P := GetParamStr(GetCommandLine, S);
  409. while (StartIndex > 1) and (P^ <> #0) do begin
  410. P := GetParamStr(P, S);
  411. Dec(StartIndex);
  412. end;
  413. Result := P;
  414. end;
  415. function NewParamCount: Integer;
  416. var
  417. P: PChar;
  418. S: String;
  419. begin
  420. P := GetParamStr(GetCommandLine, S);
  421. Result := 0;
  422. while P^ <> #0 do begin
  423. Inc(Result);
  424. P := GetParamStr(P, S);
  425. end;
  426. end;
  427. function NewParamStr(Index: Integer): string;
  428. { Returns the Indexth command line parameter, or an empty string if Index is
  429. out of range.
  430. Differences from Delphi's ParamStr:
  431. - No limits on parameter length
  432. - Doesn't ignore empty parameters ("")
  433. - Handles the empty argv[0] case like MSVC: if GetCommandLine() returns
  434. " a b" then NewParamStr(1) should return "a", not "b" }
  435. var
  436. Buffer: array[0..MAX_PATH-1] of Char;
  437. S: String;
  438. P: PChar;
  439. begin
  440. if Index = 0 then begin
  441. SetString(Result, Buffer, GetModuleFileName(0, Buffer, SizeOf(Buffer) div SizeOf(Buffer[0])));
  442. end
  443. else begin
  444. P := GetCommandLine;
  445. while True do begin
  446. if P^ = #0 then begin
  447. S := '';
  448. Break;
  449. end;
  450. P := GetParamStr(P, S);
  451. if Index = 0 then Break;
  452. Dec(Index);
  453. end;
  454. Result := S;
  455. end;
  456. end;
  457. function AddQuotes(const S: String): String;
  458. { Adds a quote (") character to the left and right sides of the string if
  459. the string contains a space and it didn't have quotes already. This is
  460. primarily used when spawning another process with a long filename as one of
  461. the parameters. }
  462. begin
  463. Result := Trim(S);
  464. if (PathPos(' ', Result) <> 0) and
  465. ((Result[1] <> '"') or (PathLastChar(Result)^ <> '"')) then
  466. Result := '"' + Result + '"';
  467. end;
  468. function RemoveQuotes(const S: String): String;
  469. { Opposite of AddQuotes; removes any quotes around the string. }
  470. begin
  471. Result := S;
  472. while (Result <> '') and (Result[1] = '"') do
  473. Delete(Result, 1, 1);
  474. while (Result <> '') and (PathLastChar(Result)^ = '"') do
  475. SetLength(Result, Length(Result)-1);
  476. end;
  477. function ConvertPercentStr(var S: String): Boolean;
  478. { Expands all %-encoded characters in the string (see RFC 2396). Returns True
  479. if all were successfully expanded. }
  480. var
  481. I, C, E: Integer;
  482. N: String;
  483. begin
  484. Result := True;
  485. I := 1;
  486. while I <= Length(S) do begin
  487. if S[I] = '%' then begin
  488. N := Copy(S, I, 3);
  489. if Length(N) <> 3 then begin
  490. Result := False;
  491. Break;
  492. end;
  493. N[1] := '$';
  494. Val(N, C, E);
  495. if E <> 0 then begin
  496. Result := False;
  497. Break;
  498. end;
  499. { delete the two numbers following '%', and replace '%' with the character }
  500. Delete(S, I+1, 2);
  501. S[I] := Chr(C);
  502. end;
  503. Inc(I);
  504. end;
  505. end;
  506. function SkipPastConst(const S: String; const Start: Integer): Integer;
  507. { Returns the character index following the Inno Setup constant embedded
  508. into the string S at index Start.
  509. If the constant is not closed (missing a closing brace), it returns zero. }
  510. var
  511. L, BraceLevel, LastOpenBrace: Integer;
  512. begin
  513. Result := Start;
  514. L := Length(S);
  515. if Result < L then begin
  516. Inc(Result);
  517. if S[Result] = '{' then begin
  518. Inc(Result);
  519. Exit;
  520. end
  521. else begin
  522. BraceLevel := 1;
  523. LastOpenBrace := -1;
  524. while Result <= L do begin
  525. case S[Result] of
  526. '{': begin
  527. if LastOpenBrace <> Result-1 then begin
  528. Inc(BraceLevel);
  529. LastOpenBrace := Result;
  530. end
  531. else
  532. { Skip over '{{' when in an embedded constant }
  533. Dec(BraceLevel);
  534. end;
  535. '}': begin
  536. Dec(BraceLevel);
  537. if BraceLevel = 0 then begin
  538. Inc(Result);
  539. Exit;
  540. end;
  541. end;
  542. end;
  543. Inc(Result);
  544. end;
  545. end;
  546. end;
  547. Result := 0;
  548. end;
  549. function ConvertConstPercentStr(var S: String): Boolean;
  550. { Same as ConvertPercentStr, but is designed to ignore embedded Inno Setup
  551. constants. Any '%' characters between braces are not translated. Two
  552. consecutive braces are ignored. }
  553. var
  554. I, C, E: Integer;
  555. N: String;
  556. begin
  557. Result := True;
  558. I := 1;
  559. while I <= Length(S) do begin
  560. case S[I] of
  561. '{': begin
  562. I := SkipPastConst(S, I);
  563. if I = 0 then begin
  564. Result := False;
  565. Break;
  566. end;
  567. Dec(I); { ...since there's an Inc below }
  568. end;
  569. '%': begin
  570. N := Copy(S, I, 3);
  571. if Length(N) <> 3 then begin
  572. Result := False;
  573. Break;
  574. end;
  575. N[1] := '$';
  576. Val(N, C, E);
  577. if E <> 0 then begin
  578. Result := False;
  579. Break;
  580. end;
  581. { delete the two numbers following '%', and replace '%' with the character }
  582. Delete(S, I+1, 2);
  583. S[I] := Chr(C);
  584. end;
  585. end;
  586. Inc(I);
  587. end;
  588. end;
  589. function ConstPos(const Ch: Char; const S: String): Integer;
  590. { Like the standard Pos function, but skips over any Inno Setup constants
  591. embedded in S }
  592. var
  593. I, L: Integer;
  594. begin
  595. Result := 0;
  596. I := 1;
  597. L := Length(S);
  598. while I <= L do begin
  599. if S[I] = Ch then begin
  600. Result := I;
  601. Break;
  602. end
  603. else if S[I] = '{' then begin
  604. I := SkipPastConst(S, I);
  605. if I = 0 then
  606. Break;
  607. end
  608. else
  609. Inc(I);
  610. end;
  611. end;
  612. function GetWinDir: String;
  613. { Returns fully qualified path of the Windows directory. Only includes a
  614. trailing backslash if the Windows directory is the root directory. }
  615. var
  616. Buf: array[0..MAX_PATH-1] of Char;
  617. begin
  618. GetWindowsDirectory(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
  619. Result := StrPas(Buf);
  620. end;
  621. function GetSystemWindowsDirectoryW(lpBuffer: LPWSTR; uSize: UINT): UINT; stdcall; external kernel32;
  622. function GetSystemWinDir: String;
  623. { Like get GetWinDir but uses GetSystemWindowsDirectory instead of
  624. GetWindowsDirectory: With Terminal Services, the GetSystemWindowsDirectory
  625. function retrieves the path of the system Windows directory, while the
  626. GetWindowsDirectory function retrieves the path of a Windows directory that is
  627. private for each user. On a single-user system, GetSystemWindowsDirectory is
  628. the same as GetWindowsDirectory. }
  629. var
  630. Buf: array[0..MAX_PATH-1] of Char;
  631. begin
  632. GetSystemWindowsDirectoryW(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
  633. Result := StrPas(Buf);
  634. end;
  635. function GetSystemDir: String;
  636. { Returns fully qualified path of the Windows System directory. Only includes a
  637. trailing backslash if the Windows System directory is the root directory. }
  638. var
  639. Buf: array[0..MAX_PATH-1] of Char;
  640. begin
  641. GetSystemDirectory(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
  642. Result := StrPas(Buf);
  643. end;
  644. function GetSysWow64Dir: String;
  645. { Returns fully qualified path of the SysWow64 directory on 64-bit Windows.
  646. Returns '' if there is no SysWow64 directory (e.g. running 32-bit Windows). }
  647. var
  648. GetSystemWow64DirectoryFunc: function(
  649. lpBuffer: PWideChar; uSize: UINT): UINT; stdcall;
  650. Buf: array[0..MAX_PATH] of Char;
  651. begin
  652. Result := '';
  653. GetSystemWow64DirectoryFunc := GetProcAddress(GetModuleHandle(kernel32),
  654. 'GetSystemWow64DirectoryW');
  655. if Assigned(GetSystemWow64DirectoryFunc) then begin
  656. const Res = GetSystemWow64DirectoryFunc(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
  657. if (Res > 0) and (Res < SizeOf(Buf) div SizeOf(Buf[0])) then
  658. Result := Buf;
  659. end;
  660. end;
  661. function GetSysNativeDir(const IsWin64: Boolean): String;
  662. { Returns the special Sysnative alias, without trailing backslash.
  663. Returns '' if there is no Sysnative alias. }
  664. begin
  665. { From MSDN: 32-bit applications can access the native system directory by
  666. substituting %windir%\Sysnative for %windir%\System32. WOW64 recognizes
  667. Sysnative as a special alias used to indicate that the file system should
  668. not redirect the access. ... Note that 64-bit applications cannot use the
  669. Sysnative alias as it is a virtual directory not a real one.
  670. Note: even though MSDN says 64-bit applications cannot *use* the alias,
  671. it is still useful for them to know it, for example to prepare a path
  672. to pass to a 32-bit application, or to rewrite Sysnative paths read from
  673. an uninstall log created by a 32-bit installer. }
  674. if IsWin64 then
  675. Result := AddBackslash(GetSystemWinDir) + 'Sysnative' { Do not localize }
  676. else
  677. Result := '';
  678. end;
  679. function GetTempDir: String;
  680. { Returns fully qualified path of the temporary directory, with trailing
  681. backslash. }
  682. procedure RestoreDeletedTempDirWithLogonSessionId(const DeletedTempDir: String);
  683. { Restores a deleted temporary directory in the specific scenario described at
  684. https://learn.microsoft.com/en-us/troubleshoot/windows-server/shell-experience/temp-folder-with-logon-session-id-deleted }
  685. begin
  686. const DirWithoutSlash = RemoveBackslashUnlessRoot(DeletedTempDir);
  687. const BaseName = PathExtractName(DirWithoutSlash);
  688. var BaseNameIsNumber := False;
  689. for var I := Low(BaseName) to High(BaseName) do begin
  690. BaseNameIsNumber := CharInSet(BaseName[I], ['0'..'9']);
  691. if not BaseNameIsNumber then
  692. Break;
  693. end;
  694. if BaseNameIsNumber then
  695. CreateDirectory(PChar(DirWithoutSlash), nil);
  696. end;
  697. var
  698. GetTempPathFunc: function(nBufferLength: DWORD; lpBuffer: LPWSTR): DWORD; stdcall;
  699. Buf: array[0..MAX_PATH] of Char;
  700. begin
  701. { When available, GetTempPath2 is preferred as it returns a private
  702. directory (typically C:\Windows\SystemTemp) when running as SYSTEM }
  703. GetTempPathFunc := GetProcAddress(GetModuleHandle(kernel32),
  704. PAnsiChar('GetTempPath2W'));
  705. if not Assigned(GetTempPathFunc) then
  706. GetTempPathFunc := GetTempPathW;
  707. const Res = GetTempPathFunc(SizeOf(Buf) div SizeOf(Buf[0]), Buf);
  708. if (Res > 0) and (Res < SizeOf(Buf) div SizeOf(Buf[0])) then begin
  709. { The docs say the returned path is fully qualified and ends with a
  710. backslash, but let's be really sure! }
  711. Result := AddBackslash(PathExpand(Buf));
  712. if not DirExists(Result) then
  713. RestoreDeletedTempDirWithLogonSessionId(Result);
  714. Exit;
  715. end;
  716. { We don't expect GetTempPath to ever fail or claim a larger buffer is
  717. needed (docs say maximum possible return value is MAX_PATH+1), but if it
  718. does, raise an exception as this function has no return value for failure }
  719. raise Exception.CreateFmt('GetTempDir: GetTempPath failed (%u, %u)',
  720. [Res, GetLastError]);
  721. end;
  722. function StringChangeEx(var S: String; const FromStr, ToStr: String;
  723. const SupportDBCS: Boolean): Integer;
  724. { Changes all occurrences in S of FromStr to ToStr. If SupportDBCS is True
  725. (recommended), double-byte character sequences in S are recognized and
  726. handled properly. Otherwise, the function behaves in a binary-safe manner.
  727. Returns the number of times FromStr was matched and changed. }
  728. var
  729. FromStrLen, I, EndPos, J: Integer;
  730. IsMatch: Boolean;
  731. label 1;
  732. begin
  733. Result := 0;
  734. if FromStr = '' then Exit;
  735. FromStrLen := Length(FromStr);
  736. I := 1;
  737. 1:EndPos := Length(S) - FromStrLen + 1;
  738. while I <= EndPos do begin
  739. IsMatch := True;
  740. J := 0;
  741. while J < FromStrLen do begin
  742. if S[J+I] <> FromStr[J+1] then begin
  743. IsMatch := False;
  744. Break;
  745. end;
  746. Inc(J);
  747. end;
  748. if IsMatch then begin
  749. Inc(Result);
  750. Delete(S, I, FromStrLen);
  751. Insert(ToStr, S, I);
  752. Inc(I, Length(ToStr));
  753. goto 1;
  754. end;
  755. if SupportDBCS then
  756. Inc(I, PathCharLength(S, I))
  757. else
  758. Inc(I);
  759. end;
  760. end;
  761. function StringChange(var S: String; const FromStr, ToStr: String): Integer;
  762. { Same as calling StringChangeEx with SupportDBCS=False }
  763. begin
  764. Result := StringChangeEx(S, FromStr, ToStr, False);
  765. end;
  766. function AdjustLength(var S: String; const Res: Cardinal): Boolean;
  767. { Returns True if successful. Returns False if buffer wasn't large enough,
  768. and called AdjustLength to resize it. }
  769. begin
  770. Result := Integer(Res) < Length(S);
  771. SetLength(S, Res);
  772. end;
  773. function InternalRegQueryStringValue(H: HKEY; Name: PChar; var ResultStr: String;
  774. Type1, Type2, Type3: DWORD): Boolean;
  775. var
  776. Typ, Size: DWORD;
  777. S: String;
  778. ErrorCode: Longint;
  779. label 1;
  780. begin
  781. Result := False;
  782. 1:Size := 0;
  783. if (RegQueryValueEx(H, Name, nil, @Typ, nil, @Size) = ERROR_SUCCESS) and
  784. ((Typ = Type1) or (Typ = Type2) or ((Type3 <> REG_NONE) and (Typ = Type3))) then begin
  785. if Typ = REG_DWORD then begin
  786. var Data: DWORD;
  787. Size := SizeOf(Data);
  788. if (RegQueryValueEx(H, Name, nil, @Typ, PByte(@Data), @Size) = ERROR_SUCCESS) and
  789. (Typ = REG_DWORD) and (Size = Sizeof(Data)) then begin
  790. ResultStr := Data.ToString;
  791. Result := True;
  792. end;
  793. end else if Size = 0 then begin
  794. { It's an empty string with no null terminator.
  795. (Must handle those here since we can't pass a nil lpData pointer on
  796. the second RegQueryValueEx call.) }
  797. ResultStr := '';
  798. Result := True;
  799. end
  800. else begin
  801. { Paranoia: Impose reasonable upper limit on Size to avoid potential
  802. integer overflows below }
  803. if Cardinal(Size) >= Cardinal($70000000) then
  804. OutOfMemoryError;
  805. { Note: If Size isn't a multiple of SizeOf(S[1]), we have to round up
  806. here so that RegQueryValueEx doesn't overflow the buffer }
  807. var Len := (Size + (SizeOf(S[1]) - 1)) div SizeOf(S[1]);
  808. SetString(S, nil, Len);
  809. ErrorCode := RegQueryValueEx(H, Name, nil, @Typ, PByte(@S[1]), @Size);
  810. if ErrorCode = ERROR_MORE_DATA then begin
  811. { The data must've increased in size since the first RegQueryValueEx
  812. call. Start over. }
  813. goto 1;
  814. end;
  815. if (ErrorCode = ERROR_SUCCESS) and
  816. ((Typ = Type1) or (Typ = Type2) or (Typ = Type3)) then begin
  817. { If Size isn't a multiple of SizeOf(S[1]), we disregard the partial
  818. character, like RegGetValue }
  819. Len := Size div SizeOf(S[1]);
  820. { Remove any null terminators from the end and trim the string to the
  821. returned length.
  822. Note: We *should* find 1 null terminator, but it's possible for
  823. there to be more or none if the value was written that way. }
  824. while (Len <> 0) and (S[Len] = #0) do
  825. Dec(Len);
  826. { In a REG_MULTI_SZ value, each individual string is null-terminated,
  827. so add 1 null (back) to the end, unless there are no strings (Len=0) }
  828. if (Typ = REG_MULTI_SZ) and (Len <> 0) then
  829. Inc(Len);
  830. SetLength(S, Len);
  831. if (Typ = REG_MULTI_SZ) and (Len <> 0) then
  832. S[Len] := #0;
  833. ResultStr := S;
  834. Result := True;
  835. end;
  836. end;
  837. end;
  838. end;
  839. function RegQueryStringValue(H: HKEY; Name: PChar; var ResultStr: String; AllowDWord: Boolean): Boolean;
  840. { Queries the specified REG_SZ or REG_EXPAND_SZ registry key/value, and returns
  841. the value in ResultStr. Returns True if successful. When False is returned,
  842. ResultStr is unmodified. Optionally supports REG_DWORD. }
  843. begin
  844. var Type3: DWORD;
  845. if AllowDWord then
  846. Type3 := REG_DWORD
  847. else
  848. Type3 := REG_NONE;
  849. Result := InternalRegQueryStringValue(H, Name, ResultStr, REG_SZ,
  850. REG_EXPAND_SZ, Type3);
  851. end;
  852. function RegQueryMultiStringValue(H: HKEY; Name: PChar; var ResultStr: String): Boolean;
  853. { Queries the specified REG_MULTI_SZ registry key/value, and returns the value
  854. in ResultStr. Returns True if successful. When False is returned, ResultStr
  855. is unmodified. }
  856. begin
  857. Result := InternalRegQueryStringValue(H, Name, ResultStr, REG_MULTI_SZ,
  858. REG_MULTI_SZ, REG_NONE);
  859. end;
  860. function RegValueExists(H: HKEY; Name: PChar): Boolean;
  861. { Returns True if the specified value exists. Requires KEY_QUERY_VALUE access
  862. to the key. }
  863. begin
  864. Result := RegQueryValueEx(H, Name, nil, nil, nil, nil) = ERROR_SUCCESS;
  865. end;
  866. function RegViewToWowKeyFlag(const RegView: TRegView): REGSAM;
  867. begin
  868. case RegView of
  869. rv32Bit: Result := KEY_WOW64_32KEY;
  870. rv64Bit: Result := KEY_WOW64_64KEY;
  871. else
  872. Result := 0;
  873. end;
  874. end;
  875. function RegCreateKeyExView(const RegView: TRegView; hKey: HKEY; lpSubKey: PChar;
  876. Reserved: DWORD; lpClass: PChar; dwOptions: DWORD; samDesired: REGSAM;
  877. lpSecurityAttributes: PSecurityAttributes; var phkResult: HKEY;
  878. lpdwDisposition: PDWORD): Longint;
  879. begin
  880. samDesired := samDesired or RegViewToWowKeyFlag(RegView);
  881. Result := RegCreateKeyEx(hKey, lpSubKey, Reserved, lpClass, dwOptions,
  882. samDesired, lpSecurityAttributes, phkResult, lpdwDisposition);
  883. end;
  884. function RegOpenKeyExView(const RegView: TRegView; hKey: HKEY; lpSubKey: PChar;
  885. ulOptions: DWORD; samDesired: REGSAM; var phkResult: HKEY): Longint;
  886. begin
  887. samDesired := samDesired or RegViewToWowKeyFlag(RegView);
  888. Result := RegOpenKeyEx(hKey, lpSubKey, ulOptions, samDesired, phkResult);
  889. end;
  890. function RegDeleteKeyEx_static(hKey: HKEY; lpSubKey: LPCWSTR;
  891. samDesired: REGSAM; Reserved: DWORD): Longint; stdcall;
  892. external advapi32 name 'RegDeleteKeyExW';
  893. function RegDeleteKeyView(const RegView: TRegView; const Key: HKEY;
  894. const Name: PChar): Longint;
  895. begin
  896. Result := RegDeleteKeyEx_static(Key, Name, RegViewToWowKeyFlag(RegView), 0);
  897. end;
  898. function RegDeleteKeyIncludingSubkeys(const RegView: TRegView; const Key: HKEY;
  899. const Name: PChar): Longint;
  900. { Deletes the specified key and all subkeys.
  901. Returns ERROR_SUCCESS if the key was successful deleted. }
  902. var
  903. H: HKEY;
  904. KeyName: String;
  905. I, KeyNameCount: DWORD;
  906. ErrorCode: Longint;
  907. begin
  908. if (Name = nil) or (Name[0] = #0) then begin
  909. Result := ERROR_INVALID_PARAMETER;
  910. Exit;
  911. end;
  912. if RegOpenKeyExView(RegView, Key, Name, 0, KEY_ENUMERATE_SUB_KEYS, H) = ERROR_SUCCESS then begin
  913. try
  914. SetString(KeyName, nil, 256);
  915. I := 0;
  916. while True do begin
  917. KeyNameCount := ULength(KeyName);
  918. ErrorCode := RegEnumKeyEx(H, I, @KeyName[1], KeyNameCount, nil, nil, nil, nil);
  919. if ErrorCode = ERROR_MORE_DATA then begin
  920. { Double the size of the buffer and try again }
  921. if Length(KeyName) >= 65536 then begin
  922. { Sanity check: If we tried a 64 KB buffer and it's still saying
  923. there's more data, something must be seriously wrong. Bail. }
  924. Break;
  925. end;
  926. SetString(KeyName, nil, Length(KeyName) * 2);
  927. Continue;
  928. end;
  929. if ErrorCode <> ERROR_SUCCESS then
  930. Break;
  931. if RegDeleteKeyIncludingSubkeys(RegView, H, PChar(KeyName)) <> ERROR_SUCCESS then
  932. Inc(I);
  933. end;
  934. finally
  935. RegCloseKey(H);
  936. end;
  937. end;
  938. Result := RegDeleteKeyView(RegView, Key, Name);
  939. end;
  940. function RegDeleteKeyIfEmpty(const RegView: TRegView; const RootKey: HKEY;
  941. const SubkeyName: PChar): Longint;
  942. { Deletes the specified subkey if it has no subkeys or values.
  943. Returns ERROR_SUCCESS if the key was successful deleted, ERROR_DIR_NOT_EMPTY
  944. if it was not deleted because it contained subkeys or values, or possibly
  945. some other Win32 error code. }
  946. var
  947. K: HKEY;
  948. NumSubkeys, NumValues: DWORD;
  949. begin
  950. Result := RegOpenKeyExView(RegView, RootKey, SubkeyName, 0, KEY_QUERY_VALUE, K);
  951. if Result <> ERROR_SUCCESS then
  952. Exit;
  953. Result := RegQueryInfoKey(K, nil, nil, nil, @NumSubkeys, nil, nil,
  954. @NumValues, nil, nil, nil, nil);
  955. RegCloseKey(K);
  956. if Result <> ERROR_SUCCESS then
  957. Exit;
  958. if (NumSubkeys = 0) and (NumValues = 0) then
  959. Result := RegDeleteKeyView(RegView, RootKey, SubkeyName)
  960. else
  961. Result := ERROR_DIR_NOT_EMPTY;
  962. end;
  963. function SHGetFolderPath_shell32(hwnd: HWND; csidl: Integer; hToken: THandle;
  964. dwFlags: DWORD; pszPath: LPWSTR): HResult; stdcall;
  965. external 'shell32.dll' name 'SHGetFolderPathW';
  966. function GetShellFolderPath(const FolderID: Integer): String;
  967. const
  968. SHGFP_TYPE_CURRENT = 0;
  969. var
  970. Buf: array[0..MAX_PATH-1] of Char;
  971. begin
  972. const Res = SHGetFolderPath_shell32(0, FolderID, 0, SHGFP_TYPE_CURRENT, Buf);
  973. if Res = S_OK then
  974. Result := Buf
  975. else
  976. Result := '';
  977. end;
  978. function GetCurrentUserSid: String;
  979. var
  980. Token: THandle;
  981. UserInfoSize: DWORD;
  982. UserInfo: PTokenUser;
  983. StringSid: PWideChar;
  984. begin
  985. Result := '';
  986. if not OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, Token) then
  987. Exit;
  988. UserInfo := nil;
  989. try
  990. UserInfoSize := 0;
  991. if not GetTokenInformation(Token, TokenUser, nil, 0, UserInfoSize) and
  992. (GetLastError <> ERROR_INSUFFICIENT_BUFFER) then
  993. Exit;
  994. GetMem(UserInfo, UserInfoSize);
  995. if not GetTokenInformation(Token, TokenUser, UserInfo,
  996. UserInfoSize, UserInfoSize) then
  997. Exit;
  998. if ConvertSidToStringSidW(UserInfo.User.Sid, StringSid) then begin
  999. Result := StringSid;
  1000. LocalFree(StringSid);
  1001. end;
  1002. finally
  1003. FreeMem(UserInfo);
  1004. CloseHandle(Token);
  1005. end;
  1006. end;
  1007. function IsMemberOfGroup(const DomainAliasRid: DWORD): Boolean;
  1008. { Returns True if the logged-on user is a member of the specified local
  1009. group. }
  1010. const
  1011. SECURITY_NT_AUTHORITY: TSIDIdentifierAuthority =
  1012. (Value: (0, 0, 0, 0, 0, 5));
  1013. SECURITY_BUILTIN_DOMAIN_RID = $00000020;
  1014. SE_GROUP_ENABLED = $00000004;
  1015. SE_GROUP_USE_FOR_DENY_ONLY = $00000010;
  1016. var
  1017. Sid: PSID;
  1018. CheckTokenMembership: function(TokenHandle: THandle; SidToCheck: PSID;
  1019. var IsMember: BOOL): BOOL; stdcall;
  1020. IsMember: BOOL;
  1021. Token: THandle;
  1022. GroupInfoSize: DWORD;
  1023. GroupInfo: PTokenGroups;
  1024. begin
  1025. Result := False;
  1026. if not AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2,
  1027. SECURITY_BUILTIN_DOMAIN_RID, DomainAliasRid,
  1028. 0, 0, 0, 0, 0, 0, Sid) then
  1029. Exit;
  1030. try
  1031. { Use CheckTokenMembership if available. MSDN states:
  1032. "The CheckTokenMembership function should be used with Windows 2000 and
  1033. later to determine whether a specified SID is present and enabled in an
  1034. access token. This function eliminates potential misinterpretations of
  1035. the active group membership if changes to access tokens are made in
  1036. future releases." }
  1037. CheckTokenMembership := GetProcAddress(GetModuleHandle(advapi32),
  1038. 'CheckTokenMembership');
  1039. if Assigned(CheckTokenMembership) then begin
  1040. if CheckTokenMembership(0, Sid, IsMember) then
  1041. Result := IsMember;
  1042. end
  1043. else begin { Should never happen }
  1044. GroupInfo := nil;
  1045. if not OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, Token) then begin
  1046. if GetLastError <> ERROR_NO_TOKEN then
  1047. Exit;
  1048. if not OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, Token) then
  1049. Exit;
  1050. end;
  1051. try
  1052. GroupInfoSize := 0;
  1053. if not GetTokenInformation(Token, TokenGroups, nil, 0, GroupInfoSize) and
  1054. (GetLastError <> ERROR_INSUFFICIENT_BUFFER) then
  1055. Exit;
  1056. GetMem(GroupInfo, GroupInfoSize);
  1057. if not GetTokenInformation(Token, TokenGroups, GroupInfo,
  1058. GroupInfoSize, GroupInfoSize) then
  1059. Exit;
  1060. for var I := 0 to GroupInfo.GroupCount-1 do begin
  1061. if EqualSid(Sid, GroupInfo.Groups[I].Sid) and
  1062. (GroupInfo.Groups[I].Attributes and (SE_GROUP_ENABLED or
  1063. SE_GROUP_USE_FOR_DENY_ONLY) = SE_GROUP_ENABLED) then begin
  1064. Result := True;
  1065. Break;
  1066. end;
  1067. end;
  1068. finally
  1069. FreeMem(GroupInfo);
  1070. CloseHandle(Token);
  1071. end;
  1072. end;
  1073. finally
  1074. FreeSid(Sid);
  1075. end;
  1076. end;
  1077. function IsAdminLoggedOn: Boolean;
  1078. { Returns True if the logged-on user is a member of the Administrators local
  1079. group. }
  1080. const
  1081. DOMAIN_ALIAS_RID_ADMINS = $00000220;
  1082. begin
  1083. Result := IsMemberOfGroup(DOMAIN_ALIAS_RID_ADMINS);
  1084. end;
  1085. function IsPowerUserLoggedOn: Boolean;
  1086. { Returns True if the logged-on user is a member of the Power Users local
  1087. group. }
  1088. const
  1089. DOMAIN_ALIAS_RID_POWER_USERS = $00000223;
  1090. begin
  1091. Result := IsMemberOfGroup(DOMAIN_ALIAS_RID_POWER_USERS);
  1092. end;
  1093. function IsMultiByteString(const S: AnsiString): Boolean;
  1094. var
  1095. I: Integer;
  1096. begin
  1097. Result := False;
  1098. for I := 1 to Length(S) do
  1099. if IsDBCSLeadByte(Ord(S[I])) then begin
  1100. Result := True;
  1101. Break;
  1102. end;
  1103. end;
  1104. function FontExistsCallback(const lplf: TLogFont; const lptm: TTextMetric;
  1105. dwType: DWORD; lpData: LPARAM): Integer; stdcall;
  1106. begin
  1107. Boolean(Pointer(lpData)^) := True;
  1108. Result := 1;
  1109. end;
  1110. function FontExists(const FaceName: String): Boolean;
  1111. var
  1112. DC: HDC;
  1113. begin
  1114. Result := False;
  1115. DC := GetDC(0);
  1116. try
  1117. EnumFonts(DC, PChar(FaceName), @FontExistsCallback, LPARAM(@Result));
  1118. finally
  1119. ReleaseDC(0, DC);
  1120. end;
  1121. end;
  1122. function GetUILanguage: LANGID;
  1123. { Platform-independent version of GetUserDefaultUILanguage. May return 0 in
  1124. case of failure. }
  1125. var
  1126. GetUserDefaultUILanguage: function: LANGID; stdcall;
  1127. K: HKEY;
  1128. S: String;
  1129. E: Integer;
  1130. begin
  1131. GetUserDefaultUILanguage := GetProcAddress(GetModuleHandle(kernel32),
  1132. 'GetUserDefaultUILanguage');
  1133. if Assigned(GetUserDefaultUILanguage) then
  1134. Result := GetUserDefaultUILanguage
  1135. else begin
  1136. { GetUserDefaultUILanguage is available on Windows 2000, Me, and later so
  1137. should never get here }
  1138. if RegOpenKeyExView(rvDefault, HKEY_USERS, '.DEFAULT\Control Panel\International',
  1139. 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  1140. RegQueryStringValue(K, 'Locale', S);
  1141. RegCloseKey(K);
  1142. end;
  1143. Val('$' + S, Result, E);
  1144. if E <> 0 then
  1145. Result := 0;
  1146. end;
  1147. end;
  1148. function RemoveAccelChar(const S: String): String;
  1149. var
  1150. I: Integer;
  1151. begin
  1152. Result := S;
  1153. I := 1;
  1154. while I <= Length(Result) do begin
  1155. if Result[I] = '&' then begin
  1156. System.Delete(Result, I, 1);
  1157. if I > Length(Result) then
  1158. Break;
  1159. end;
  1160. Inc(I, PathCharLength(Result, I));
  1161. end;
  1162. end;
  1163. function GetTextWidth(const DC: HDC; S: String; const Prefix: Boolean): Integer;
  1164. { Returns the width of the specified string using the font currently selected
  1165. into DC. If Prefix is True, it first removes "&" characters as necessary. }
  1166. var
  1167. Size: TSize;
  1168. begin
  1169. { This procedure is 10x faster than using DrawText with the DT_CALCRECT flag }
  1170. if Prefix then
  1171. S := RemoveAccelChar(S);
  1172. GetTextExtentPoint32(DC, PChar(S), Length(S), Size);
  1173. Result := Size.cx;
  1174. end;
  1175. function AddPeriod(const S: String): String;
  1176. begin
  1177. Result := S;
  1178. if (Result <> '') and (PathLastChar(Result)^ > '.') then
  1179. Result := Result + '.';
  1180. end;
  1181. function GetExceptMessage: String;
  1182. var
  1183. E: TObject;
  1184. begin
  1185. E := ExceptObject;
  1186. if E = nil then
  1187. Result := '[ExceptObject=nil]' { should never get here }
  1188. else if E is Exception then
  1189. Result := AddPeriod(Exception(E).Message) { usual case }
  1190. else
  1191. Result := E.ClassName; { shouldn't get here under normal circumstances }
  1192. end;
  1193. function GetPreferredUIFont: String;
  1194. { Gets the preferred UI font. Returns Microsoft Sans Serif, or MS Sans Serif
  1195. if it doesn't exist.
  1196. Microsoft Sans Serif (which is available on Windows 2000 and later) has two
  1197. advantages over MS Sans Serif:
  1198. 1) On Windows XP, it can display password dots in edit boxes.
  1199. 2) In my tests on Japanese XP, Microsoft Sans Serif can display Japanese
  1200. characters (MS Sans Serif cannot). }
  1201. begin
  1202. if FontExists('Microsoft Sans Serif') then
  1203. Result := 'Microsoft Sans Serif'
  1204. else
  1205. Result := 'MS Sans Serif';
  1206. end;
  1207. function IsWildcard(const Pattern: String): Boolean;
  1208. begin
  1209. Result := (Pos('*', Pattern) <> 0) or (Pos('?', Pattern) <> 0);
  1210. end;
  1211. function WildcardMatch(const Text, Pattern: PChar): Boolean;
  1212. { General-purpose wildcard matching function based on the widely used wildcat()
  1213. code by Rich $alz. In this implementation, however, the only supported
  1214. pattern matching characters are ? and *.
  1215. Note that this function uses Unix shell semantics -- e.g. a dot always
  1216. matches a dot (so a pattern of '*.*' won't match 'file'), and ? always
  1217. matches exactly 1 character (so '?????' won't match 'file').
  1218. Also note: The InternalWildcardMatch function can recursively call itself
  1219. for each non-consecutive * character in the pattern. With enough *
  1220. characters, the stack could overflow. So ideally the caller should impose a
  1221. limit on either the length of the pattern string or the number of *
  1222. characters in it. }
  1223. type
  1224. TWildcardMatchResult = (wmFalse, wmTrue, wmAbort);
  1225. function InternalWildcardMatch(T, P: PChar): TWildcardMatchResult;
  1226. begin
  1227. while P^ <> #0 do begin
  1228. if (T^ = #0) and (P^ <> '*') then begin
  1229. Result := wmAbort;
  1230. Exit;
  1231. end;
  1232. case P^ of
  1233. '?': ; { Match any character }
  1234. '*': begin
  1235. Inc(P);
  1236. while P^ = '*' do begin
  1237. { Consecutive stars act just like one }
  1238. Inc(P);
  1239. end;
  1240. if P^ = #0 then begin
  1241. { Trailing star matches everything }
  1242. Result := wmTrue;
  1243. Exit;
  1244. end;
  1245. while T^ <> #0 do begin
  1246. Result := InternalWildcardMatch(T, P);
  1247. if Result <> wmFalse then
  1248. Exit;
  1249. T := PathStrNextChar(T);
  1250. end;
  1251. Result := wmAbort;
  1252. Exit;
  1253. end;
  1254. else
  1255. if not PathCharCompare(T, P) then begin
  1256. Result := wmFalse;
  1257. Exit;
  1258. end;
  1259. end;
  1260. T := PathStrNextChar(T);
  1261. P := PathStrNextChar(P);
  1262. end;
  1263. if T^ = #0 then
  1264. Result := wmTrue
  1265. else
  1266. Result := wmFalse;
  1267. end;
  1268. begin
  1269. Result := (InternalWildcardMatch(Text, Pattern) = wmTrue);
  1270. end;
  1271. function IntMax(const A, B: Integer): Integer;
  1272. begin
  1273. if A > B then
  1274. Result := A
  1275. else
  1276. Result := B;
  1277. end;
  1278. function Win32ErrorString(ErrorCode: DWORD): String;
  1279. { Like SysErrorMessage but also passes the FORMAT_MESSAGE_IGNORE_INSERTS flag
  1280. which allows the function to succeed on errors like 129 }
  1281. var
  1282. Buffer: array[0..1023] of Char;
  1283. begin
  1284. var Len := FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or
  1285. FORMAT_MESSAGE_IGNORE_INSERTS or FORMAT_MESSAGE_ARGUMENT_ARRAY, nil,
  1286. ErrorCode, 0, Buffer, SizeOf(Buffer) div SizeOf(Buffer[0]), nil);
  1287. while (Len > 0) and ((Buffer[Len-1] <= ' ') or (Buffer[Len-1] = '.')) do
  1288. Dec(Len);
  1289. SetString(Result, Buffer, Len);
  1290. end;
  1291. function DeleteDirTree(const Dir: String): Boolean;
  1292. { Removes the specified directory including any files/subdirectories inside
  1293. it. Returns True if successful. }
  1294. var
  1295. H: THandle;
  1296. FindData: TWin32FindData;
  1297. FN: String;
  1298. begin
  1299. if (Dir <> '') and (Pos(#0, Dir) = 0) and { sanity/safety checks }
  1300. IsDirectoryAndNotReparsePoint(Dir) then begin
  1301. H := FindFirstFile(PChar(AddBackslash(Dir) + '*'), FindData);
  1302. if H <> INVALID_HANDLE_VALUE then begin
  1303. try
  1304. repeat
  1305. if (StrComp(FindData.cFileName, '.') <> 0) and
  1306. (StrComp(FindData.cFileName, '..') <> 0) then begin
  1307. FN := AddBackslash(Dir) + FindData.cFileName;
  1308. if FindData.dwFileAttributes and FILE_ATTRIBUTE_READONLY <> 0 then
  1309. SetFileAttributes(PChar(FN), FindData.dwFileAttributes and not FILE_ATTRIBUTE_READONLY);
  1310. if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then
  1311. Windows.DeleteFile(PChar(FN))
  1312. else
  1313. DeleteDirTree(FN);
  1314. end;
  1315. until not FindNextFile(H, FindData);
  1316. finally
  1317. Windows.FindClose(H);
  1318. end;
  1319. end;
  1320. end;
  1321. Result := RemoveDirectory(PChar(Dir));
  1322. end;
  1323. function SetNTFSCompression(const FileOrDir: String; Compress: Boolean): Boolean;
  1324. { Changes the NTFS compression state of a file or directory. If False is
  1325. returned, GetLastError can be called to get extended error information. }
  1326. const
  1327. COMPRESSION_FORMAT_NONE = 0;
  1328. COMPRESSION_FORMAT_DEFAULT = 1;
  1329. FSCTL_SET_COMPRESSION = $9C040;
  1330. Compressions: array[Boolean] of Word = (COMPRESSION_FORMAT_NONE, COMPRESSION_FORMAT_DEFAULT);
  1331. var
  1332. Handle: THandle;
  1333. BytesReturned, LastError: DWORD;
  1334. begin
  1335. Handle := CreateFile(PChar(FileOrDir), GENERIC_READ or GENERIC_WRITE,
  1336. FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
  1337. if Handle <> INVALID_HANDLE_VALUE then begin
  1338. Result := DeviceIoControl(Handle, FSCTL_SET_COMPRESSION, @Compressions[Compress],
  1339. SizeOf(Compressions[Compress]), nil, 0, BytesReturned, nil);
  1340. { Save the error code from DeviceIoControl as CloseHandle may overwrite it
  1341. (Windows 95's CloseHandle always sets it to zero) }
  1342. LastError := GetLastError;
  1343. CloseHandle(Handle);
  1344. SetLastError(LastError);
  1345. end else
  1346. Result := False;
  1347. end;
  1348. var
  1349. ChangeWindowMessageFilterInited: BOOL;
  1350. ChangeWindowMessageFilterFunc: function(msg: UINT; dwFlag: DWORD): BOOL; stdcall;
  1351. ChangeWindowMessageFilterExInited: BOOL;
  1352. ChangeWindowMessageFilterExFunc: function(hWnd: HWND; msg: UINT;
  1353. action: DWORD; pChangeFilterStruct: Pointer): BOOL; stdcall;
  1354. procedure AddToWindowMessageFilter(const Msg: UINT);
  1355. { Adds a single message number to the process-wide message filter. }
  1356. const
  1357. MSGFLT_ADD = 1;
  1358. begin
  1359. if not ChangeWindowMessageFilterInited then begin
  1360. ChangeWindowMessageFilterFunc := GetProcAddress(GetModuleHandle(user32),
  1361. 'ChangeWindowMessageFilter');
  1362. InterlockedExchange(Integer(ChangeWindowMessageFilterInited), Ord(True));
  1363. end;
  1364. if Assigned(ChangeWindowMessageFilterFunc) then
  1365. ChangeWindowMessageFilterFunc(Msg, MSGFLT_ADD);
  1366. end;
  1367. procedure AddToWindowMessageFilterEx(const Wnd: HWND; const Msg: UINT);
  1368. { Adds a single message number to Wnd's window-specific message filter. Falls
  1369. back to modifying the process-wide message filter but in reality that should
  1370. never happen. }
  1371. const
  1372. MSGFLT_ALLOW = 1;
  1373. begin
  1374. if not ChangeWindowMessageFilterExInited then begin
  1375. ChangeWindowMessageFilterExFunc := GetProcAddress(GetModuleHandle(user32),
  1376. 'ChangeWindowMessageFilterEx');
  1377. InterlockedExchange(Integer(ChangeWindowMessageFilterExInited), Ord(True));
  1378. end;
  1379. if Assigned(ChangeWindowMessageFilterExFunc) then
  1380. ChangeWindowMessageFilterExFunc(Wnd, Msg, MSGFLT_ALLOW, nil)
  1381. else
  1382. AddToWindowMessageFilter(Msg);
  1383. end;
  1384. function ShutdownBlockReasonCreate(Wnd: HWND; const Reason: String): Boolean;
  1385. var
  1386. ShutdownBlockReasonCreateFunc: function(Wnd: HWND; pwszReason: LPCWSTR): Bool; stdcall;
  1387. begin
  1388. { MSDN doesn't say whether you must call Destroy before a second Create, but it does say a Destroy
  1389. without a previous Create is a no-op, so call Destroy for safety. }
  1390. ShutdownBlockReasonDestroy(Wnd);
  1391. ShutdownBlockReasonCreateFunc := GetProcAddress(GetModuleHandle(user32), 'ShutdownBlockReasonCreate');
  1392. if Assigned(ShutdownBlockReasonCreateFunc) then
  1393. Result := ShutdownBlockReasonCreateFunc(Wnd, PChar(Reason))
  1394. else
  1395. Result := False;
  1396. end;
  1397. { As MSDN says: if ShutdownBlockReasonCreate was not previously called, this function is a no-op. }
  1398. function ShutdownBlockReasonDestroy(Wnd: HWND): Boolean;
  1399. var
  1400. ShutdownBlockReasonDestroyFunc: function(Wnd: HWND): Bool; stdcall;
  1401. begin
  1402. ShutdownBlockReasonDestroyFunc := GetProcAddress(GetModuleHandle(user32), 'ShutdownBlockReasonDestroy');
  1403. Result := Assigned(ShutdownBlockReasonDestroyFunc) and ShutdownBlockReasonDestroyFunc(Wnd);
  1404. end;
  1405. function TryStrToBoolean(const S: String; var BoolResult: Boolean): Boolean;
  1406. begin
  1407. if (S = '0') or (CompareText(S, 'no') = 0) or (CompareText(S, 'false') = 0) then begin
  1408. BoolResult := False;
  1409. Result := True;
  1410. end
  1411. else if (S = '1') or (CompareText(S, 'yes') = 0) or (CompareText(S, 'true') = 0) then begin
  1412. BoolResult := True;
  1413. Result := True;
  1414. end
  1415. else
  1416. Result := False;
  1417. end;
  1418. procedure WaitMessageWithTimeout(const Milliseconds: DWORD);
  1419. { Like WaitMessage, but times out if a message isn't received before
  1420. Milliseconds ms have elapsed. }
  1421. begin
  1422. MsgWaitForMultipleObjects(0, THandle(nil^), False, Milliseconds, QS_ALLINPUT);
  1423. end;
  1424. function MoveFileReplace(const ExistingFileName, NewFileName: String): Boolean;
  1425. begin
  1426. Result := MoveFileEx(PChar(ExistingFileName), PChar(NewFileName),
  1427. MOVEFILE_REPLACE_EXISTING);
  1428. end;
  1429. procedure CreateMutex(const MutexName: String);
  1430. const
  1431. SECURITY_DESCRIPTOR_REVISION = 1; { Win32 constant not defined in Delphi 3 }
  1432. var
  1433. SecurityDesc: TSecurityDescriptor;
  1434. SecurityAttr: TSecurityAttributes;
  1435. begin
  1436. { By default on Windows NT, created mutexes are accessible only by the user
  1437. running the process. We need our mutexes to be accessible to all users, so
  1438. that the mutex detection can work across user sessions in Windows XP. To
  1439. do this we use a security descriptor with a null DACL. }
  1440. InitializeSecurityDescriptor(@SecurityDesc, SECURITY_DESCRIPTOR_REVISION);
  1441. SetSecurityDescriptorDacl(@SecurityDesc, True, nil, False);
  1442. SecurityAttr.nLength := SizeOf(SecurityAttr);
  1443. SecurityAttr.lpSecurityDescriptor := @SecurityDesc;
  1444. SecurityAttr.bInheritHandle := False;
  1445. Windows.CreateMutex(@SecurityAttr, False, PChar(MutexName));
  1446. end;
  1447. function HighContrastActive: Boolean;
  1448. begin
  1449. var HighContrast: THighContrast;
  1450. HighContrast.cbSize := SizeOf(HighContrast);
  1451. Result := False;
  1452. if SystemParametersInfo(SPI_GETHIGHCONTRAST, HighContrast.cbSize, @HighContrast, 0) then
  1453. Result := (HighContrast.dwFlags and HCF_HIGHCONTRASTON) <> 0;
  1454. end;
  1455. var
  1456. WindowsVersion: Cardinal;
  1457. WindowsVersionRead: Boolean;
  1458. function CurrentWindowsVersionAtLeast(const AMajor, AMinor: Byte; const ABuild: Word = 0): Boolean;
  1459. begin
  1460. if not WindowsVersionRead then begin
  1461. var OSVersionInfo: TOSVersionInfo;
  1462. OSVersionInfo.dwOSVersionInfoSize := SizeOf(OSVersionInfo);
  1463. GetVersionEx(OSVersionInfo);
  1464. WindowsVersion := (Byte(OSVersionInfo.dwMajorVersion) shl 24) or (Byte(OSVersionInfo.dwMinorVersion) shl 16) or Word(OSVersionInfo.dwBuildNumber);
  1465. WindowsVersionRead := True;
  1466. end;
  1467. Result := WindowsVersion >= Cardinal((AMajor shl 24) or (AMinor shl 16) or ABuild);
  1468. end;
  1469. function DarkModeActive: Boolean;
  1470. var
  1471. K: HKEY;
  1472. Size, AppsUseLightTheme: DWORD;
  1473. begin
  1474. Result := False;
  1475. if CurrentWindowsVersionAtLeast(10, 0) and (RegOpenKeyExView(rvDefault, HKEY_CURRENT_USER, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize', 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS) then begin
  1476. Size := SizeOf(AppsUseLightTheme);
  1477. if (RegQueryValueEx(K, 'AppsUseLightTheme', nil, nil, PByte(@AppsUseLightTheme), @Size) = ERROR_SUCCESS) and (AppsUseLightTheme = 0) then
  1478. Result := True;
  1479. RegCloseKey(K);
  1480. end;
  1481. end;
  1482. { FileInformationClass is really an enum type }
  1483. function SetFileInformationByHandle(hFile: THandle; FileInformationClass: DWORD;
  1484. lpFileInformation: LPVOID; dwBufferSize: DWORD): BOOL; stdcall; external kernel32;
  1485. function DeleteFileOrDirByHandle(const H: THandle): Boolean;
  1486. { Deletes a file or directory by handle. DELETE access (Windows._DELETE in
  1487. Delphi) must have been requested when the handle was opened.
  1488. If a directory isn't empty, the function fails.
  1489. When False is returned, call GetLastError to get the error code.
  1490. The directory entry for the file/directory doesn't disappear until all
  1491. handles have been closed. This function does not request "POSIX delete
  1492. semantics" -- which would cause the directory entry to disappear
  1493. immediately, as with DeleteFile -- because it's only supported on Windows 10
  1494. 1607 and later.
  1495. NOTE: This function should generally only be used with handles opened with
  1496. the FILE_FLAG_OPEN_REPARSE_POINT flag. If that flag isn't used, then the
  1497. function will delete the *target* of a symbolic link, not the symbolic link
  1498. itself, which usually isn't the intention. (The DeleteFile and
  1499. RemoveDirectory functions delete symbolic links, not their targets.) }
  1500. const
  1501. FileDispositionInfo = 4;
  1502. type
  1503. TFileDispositionInfo = record
  1504. DeleteFile: Boolean; { actually the Windows BOOLEAN type, also 1-byte }
  1505. end;
  1506. begin
  1507. var Info: TFileDispositionInfo;
  1508. Info.DeleteFile := True;
  1509. Result := SetFileInformationByHandle(H, FileDispositionInfo, @Info,
  1510. SizeOf(Info));
  1511. end;
  1512. function CompareInt64(const N1, N2: Int64): Integer;
  1513. begin
  1514. if N1 = N2 then
  1515. Result := 0
  1516. else if N1 > N2 then
  1517. Result := 1
  1518. else
  1519. Result := -1;
  1520. end;
  1521. function HighLowToInt64(const High, Low: UInt32): Int64;
  1522. begin
  1523. Result := Int64((UInt64(High) shl 32) or Low);
  1524. end;
  1525. function HighLowToUInt64(const High, Low: UInt32): UInt64;
  1526. begin
  1527. Result := (UInt64(High) shl 32) or Low;
  1528. end;
  1529. function FindDataFileSizeToInt64(const FindData: TWin32FindData): Int64;
  1530. begin
  1531. Result := HighLowToInt64(FindData.nFileSizeHigh, FindData.nFileSizeLow);
  1532. end;
  1533. function FileTimeToUInt64(const FileTime: TFileTime): UInt64;
  1534. begin
  1535. Result := HighLowToUInt64(FileTime.dwHighDateTime, FileTime.dwLowDateTime);
  1536. end;
  1537. function StrToWnd(const S: String): HWND;
  1538. begin
  1539. Result := UInt32(StrToUInt64(S));
  1540. end;
  1541. function LastErrorIndicatesPossiblyInUse(const LastError: DWORD; const CheckAlreadyExists: Boolean): Boolean;
  1542. begin
  1543. Result := (LastError = ERROR_ACCESS_DENIED) or
  1544. (LastError = ERROR_SHARING_VIOLATION) or
  1545. (CheckAlreadyExists and (LastError = ERROR_ALREADY_EXISTS));
  1546. end;
  1547. function PerformFileOperationWithRetries(const MaxRetries: Integer; const AlsoRetryOnAlreadyExists: Boolean;
  1548. const Op: TFileOperationFunc; const Failing: TFileOperationFailingProc; const Failed: TFileOperationFailedProc): Boolean;
  1549. { Performs a file operation Op. If it fails then calls Failing up to MaxRetries times. When no
  1550. retries remain, it calls Failed and returns False. Op should ensure LastError is always set on
  1551. failure. It is recommended that Failed throws an exception, rather than expecting the caller to
  1552. inspect the return value. Alternatively, Failed can set TryOnceMore to True to allow an extra retry. }
  1553. begin
  1554. Result := PerformFileOperationWithRetries(MaxRetries, AlsoRetryOnAlreadyExists,
  1555. Op,
  1556. procedure(const LastError: Cardinal; var RetriesLeft: Integer; var NextAction: TFileOperationFailingNextAction)
  1557. begin
  1558. if RetriesLeft > 0 then begin
  1559. Failing(LastError);
  1560. Dec(RetriesLeft);
  1561. NextAction := naRetry;
  1562. end;
  1563. end,
  1564. Failed);
  1565. end;
  1566. function PerformFileOperationWithRetries(const MaxRetries: Integer; const AlsoRetryOnAlreadyExists: Boolean;
  1567. const Op: TFileOperationFunc; const Failing: TFileOperationFailingExProc; const Failed: TFileOperationFailedProc): Boolean;
  1568. { Similar to the other PerformFileOperationWithRetries, but provides fine-grained control to Failing,
  1569. which is now responsible for updating RetriesLeft itself, and can also request an early break.
  1570. Failing's NextAction defaults to *not* retry, but to stop and fail. }
  1571. begin
  1572. var RetriesLeft := MaxRetries;
  1573. var LastError: Cardinal;
  1574. while not Op(LastError) do begin
  1575. { Does the error code indicate that it is possibly in use? }
  1576. if LastErrorIndicatesPossiblyInUse(LastError, AlsoRetryOnAlreadyExists) then begin
  1577. var NextAction := naStopAndFail;
  1578. Failing(LastError, RetriesLeft, NextAction);
  1579. if NextAction = naStopAndSucceed then
  1580. Break
  1581. else if NextAction = naRetry then
  1582. Continue;
  1583. end;
  1584. { Some other error occurred, or we ran out of tries }
  1585. SetLastError(LastError);
  1586. var TryOnceMore := False;
  1587. Failed(LastError, TryOnceMore);
  1588. if not TryOnceMore then
  1589. Exit(False);
  1590. end;
  1591. Result := True;
  1592. end;
  1593. function Is64BitPEImage(const Filename: String): Boolean;
  1594. { Returns True if the specified file is a non-32-bit PE image, False
  1595. otherwise. }
  1596. var
  1597. DosHeader: packed record
  1598. Sig: array[0..1] of AnsiChar;
  1599. Other: array[0..57] of Byte;
  1600. PEHeaderOffset: LongWord;
  1601. end;
  1602. PESigAndHeader: packed record
  1603. Sig: DWORD;
  1604. Header: TImageFileHeader;
  1605. OptHeaderMagic: Word;
  1606. end;
  1607. begin
  1608. Result := False;
  1609. const F = TFile.Create(Filename, fdOpenExisting, faRead, fsRead);
  1610. try
  1611. if F.Read(DosHeader, SizeOf(DosHeader)) = SizeOf(DosHeader) then begin
  1612. if (DosHeader.Sig[0] = 'M') and (DosHeader.Sig[1] = 'Z') and
  1613. (DosHeader.PEHeaderOffset <> 0) then begin
  1614. F.Seek(DosHeader.PEHeaderOffset);
  1615. if F.Read(PESigAndHeader, SizeOf(PESigAndHeader)) = SizeOf(PESigAndHeader) then begin
  1616. if (PESigAndHeader.Sig = IMAGE_NT_SIGNATURE) and
  1617. (PESigAndHeader.OptHeaderMagic <> IMAGE_NT_OPTIONAL_HDR32_MAGIC) then
  1618. Result := True;
  1619. end;
  1620. end;
  1621. end;
  1622. finally
  1623. F.Free;
  1624. end;
  1625. end;
  1626. { TOneShotTimer }
  1627. function TOneShotTimer.Expired: Boolean;
  1628. begin
  1629. Result := (TimeRemaining = 0);
  1630. end;
  1631. procedure TOneShotTimer.SleepUntilExpired;
  1632. var
  1633. Remaining: Cardinal;
  1634. begin
  1635. while True do begin
  1636. Remaining := TimeRemaining;
  1637. if Remaining = 0 then
  1638. Break;
  1639. Sleep(Remaining);
  1640. end;
  1641. end;
  1642. procedure TOneShotTimer.Start(const Timeout: Cardinal);
  1643. begin
  1644. FStartTick := GetTickCount;
  1645. FTimeout := Timeout;
  1646. FLastElapsed := 0;
  1647. end;
  1648. function TOneShotTimer.TimeElapsed: Cardinal;
  1649. var
  1650. Elapsed: Cardinal;
  1651. begin
  1652. Elapsed := GetTickCount - FStartTick;
  1653. if Elapsed > FLastElapsed then
  1654. FLastElapsed := Elapsed;
  1655. Result := FLastElapsed;
  1656. end;
  1657. function TOneShotTimer.TimeRemaining: Cardinal;
  1658. var
  1659. Elapsed: Cardinal;
  1660. begin
  1661. Elapsed := TimeElapsed;
  1662. if Elapsed < FTimeout then
  1663. Result := FTimeout - Elapsed
  1664. else
  1665. Result := 0;
  1666. end;
  1667. { TStrongRandom }
  1668. class procedure TStrongRandom.GenerateBytes(out Buf; const Count: Cardinal);
  1669. const
  1670. BCRYPT_USE_SYSTEM_PREFERRED_RNG = $00000002;
  1671. begin
  1672. InitBCrypt;
  1673. { Zero-fill the buffer first to make it easier to tell if BCryptGenRandom is
  1674. succeeding without (entirely) filling the buffer. We don't actually expect
  1675. that to happen, though. (Not using FillChar here because it takes a signed
  1676. integer for the count.) }
  1677. var I := Count;
  1678. while I > 0 do begin
  1679. Dec(I);
  1680. PByte(@Buf)[I] := 0;
  1681. end;
  1682. const Status = FBCryptGenRandomFunc(0, Buf, Count, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
  1683. if Status <> 0 then
  1684. raise Exception.CreateFmt('TStrongRandom: BCryptGenRandom failed (0x%x)',
  1685. [Status]);
  1686. end;
  1687. class function TStrongRandom.GenerateUInt32: UInt32;
  1688. begin
  1689. GenerateBytes(Result, SizeOf(Result));
  1690. end;
  1691. class function TStrongRandom.GenerateUInt32Range(const ARange: UInt32): UInt32;
  1692. { Like Delphi's Random function, returns a number in the range 0 to ARange-1 }
  1693. begin
  1694. const R = GenerateUInt32;
  1695. Result := UInt32((UInt64(R) * ARange) shr 32);
  1696. end;
  1697. class function TStrongRandom.GenerateUInt64: UInt64;
  1698. begin
  1699. GenerateBytes(Result, SizeOf(Result));
  1700. end;
  1701. class procedure TStrongRandom.InitBCrypt;
  1702. begin
  1703. if Assigned(FBCryptGenRandomFunc) then
  1704. Exit;
  1705. { If this function is entered by multiple threads concurrently, this will
  1706. call LoadLibrary more than once, but that's fine }
  1707. const M = LoadLibrary(PChar(AddBackslash(GetSystemDir) + 'bcrypt.dll'));
  1708. if M = 0 then
  1709. raise Exception.Create('TStrongRandom: Failed to load bcrypt.dll');
  1710. const P = GetProcAddress(M, PAnsiChar('BCryptGenRandom'));
  1711. if P = nil then
  1712. raise Exception.Create('TStrongRandom: Failed to get address of BCryptGenRandom');
  1713. { Make sure the work of LoadLibrary is fully visible before making the
  1714. function pointer visible to other threads }
  1715. MemoryBarrier;
  1716. FBCryptGenRandomFunc := P;
  1717. end;
  1718. { TFileTimeHelper }
  1719. procedure TFileTimeHelper.Clear;
  1720. begin
  1721. { SetFileTime regards a pointer to a FILETIME structure with both members
  1722. set to 0 the same as a NULL pointer and we make use of that. Note that
  1723. 7-Zip may return a value with both members set to 0 as well. }
  1724. dwLowDateTime := 0;
  1725. dwHighDateTime := 0;
  1726. end;
  1727. function TFileTimeHelper.HasTime: Boolean;
  1728. begin
  1729. Result := (dwLowDateTime <> 0) or (dwHighDateTime <> 0);
  1730. end;
  1731. { TCreateProcessOutputReader }
  1732. constructor TCreateProcessOutputReader.Create(const ALogProc: TLogProc;
  1733. const ALogProcData: NativeInt; AMode: TOutputMode = omLog);
  1734. procedure CreatePipeAndSetHandleInformation(var Read, Write: THandle; SecurityAttr: TSecurityAttributes);
  1735. begin
  1736. { CreatePipe docs say no assumptions should be made about the output
  1737. parameter contents (the two handles) when it fails. So specify local
  1738. variables for the output parameters, and only copy the handles into
  1739. the "var" parameters when CreatePipe is successful. That way, if it
  1740. does fail, the "var" parameters will still have their original 0
  1741. values (which is important because the destructor closes all
  1742. non-zero handles). }
  1743. var TempReadPipe, TempWritePipe: THandle;
  1744. if not CreatePipe(TempReadPipe, TempWritePipe, @SecurityAttr, 0) then
  1745. raise Exception.CreateFmt('Output redirection error: CreatePipe failed (%d)', [GetLastError]);
  1746. Read := TempReadPipe;
  1747. Write := TempWritePipe;
  1748. if not SetHandleInformation(TempReadPipe, HANDLE_FLAG_INHERIT, 0) then
  1749. raise Exception.CreateFmt('Output redirection error: SetHandleInformation failed (%d)', [GetLastError]);
  1750. end;
  1751. begin
  1752. if not Assigned(ALogProc) then
  1753. raise Exception.Create('ALogProc is required');
  1754. if AMode = omCapture then begin
  1755. FCaptureOutList := TStringList.Create;
  1756. FCaptureErrList := TStringList.Create;
  1757. end;
  1758. FMode := AMode;
  1759. FLogProc := ALogProc;
  1760. FLogProcData := ALogProcData;
  1761. FNextLineIsFirstLine := True;
  1762. var SecurityAttributes: TSecurityAttributes;
  1763. SecurityAttributes.nLength := SizeOf(SecurityAttributes);
  1764. SecurityAttributes.bInheritHandle := True;
  1765. SecurityAttributes.lpSecurityDescriptor := nil;
  1766. var NulDevice := CreateFile('\\.\NUL', GENERIC_READ,
  1767. FILE_SHARE_READ or FILE_SHARE_WRITE, @SecurityAttributes,
  1768. OPEN_EXISTING, 0, 0);
  1769. { In case the NUL device is missing (which it inexplicably seems to
  1770. be for some users, per web search), don't treat it as a fatal
  1771. error. Just leave FStdInNulDevice at 0. It's not ideal, but the
  1772. child process likely won't even attempt to access stdin anyway. }
  1773. if NulDevice <> INVALID_HANDLE_VALUE then
  1774. FStdInNulDevice := NulDevice;
  1775. CreatePipeAndSetHandleInformation(FStdOut.PipeRead, FStdOut.PipeWrite, SecurityAttributes);
  1776. FStdOut.OkToRead := True;
  1777. FStdOut.CaptureList := FCaptureOutList;
  1778. if FMode = omCapture then begin
  1779. CreatePipeAndSetHandleInformation(FStdErr.PipeRead, FStdErr.PipeWrite, SecurityAttributes);
  1780. FStdErr.OkToRead := True;
  1781. FStdErr.CaptureList := FCaptureErrList;
  1782. end;
  1783. FMaxTotalBytesToRead := 10*1000*1000;
  1784. FMaxTotalLinesToRead := 1000*1000;
  1785. end;
  1786. destructor TCreateProcessOutputReader.Destroy;
  1787. begin
  1788. CloseAndClearHandle(FStdInNulDevice);
  1789. CloseAndClearHandle(FStdOut.PipeRead);
  1790. CloseAndClearHandle(FStdOut.PipeWrite);
  1791. CloseAndClearHandle(FStdErr.PipeRead);
  1792. CloseAndClearHandle(FStdErr.PipeWrite);
  1793. FCaptureOutList.Free;
  1794. FCaptureErrList.Free;
  1795. inherited;
  1796. end;
  1797. procedure TCreateProcessOutputReader.CloseAndClearHandle(var Handle: THandle);
  1798. begin
  1799. if Handle <> 0 then begin
  1800. CloseHandle(Handle);
  1801. Handle := 0;
  1802. end;
  1803. end;
  1804. procedure TCreateProcessOutputReader.HandleAndLogErrorFmt(const S: String; const Args: array of const);
  1805. begin
  1806. FLogProc('OutputReader: ' + Format(S, Args), True, False, FLogProcData);
  1807. if FMode = omCapture then
  1808. FCaptureError := True;
  1809. end;
  1810. procedure TCreateProcessOutputReader.UpdateStartupInfo(var StartupInfo: TStartupInfo);
  1811. begin
  1812. StartupInfo.dwFlags := StartupInfo.dwFlags or STARTF_USESTDHANDLES;
  1813. StartupInfo.hStdInput := FStdInNulDevice;
  1814. StartupInfo.hStdOutput := FStdOut.PipeWrite;
  1815. if FMode = omLog then
  1816. StartupInfo.hStdError := FStdOut.PipeWrite
  1817. else
  1818. StartupInfo.hStdError := FStdErr.PipeWrite;
  1819. end;
  1820. procedure TCreateProcessOutputReader.NotifyCreateProcessDone;
  1821. begin
  1822. CloseAndClearHandle(FStdInNulDevice);
  1823. CloseAndClearHandle(FStdOut.PipeWrite);
  1824. CloseAndClearHandle(FStdErr.PipeWrite);
  1825. end;
  1826. procedure TCreateProcessOutputReader.Read(const LastRead: Boolean);
  1827. function FindNewLine(const S: AnsiString; const LastRead: Boolean): Integer;
  1828. begin
  1829. { This will return the position of the first #13 or #10. If a #13 is at
  1830. the very end of the string it's only accepted if we are certain we can't
  1831. be looking at a split #13#10 because there will be no more reads }
  1832. var N := Length(S);
  1833. for var I := 1 to N do
  1834. if ((S[I] = #13) and ((I < N) or LastRead)) or
  1835. (S[I] = #10) then
  1836. Exit(I);
  1837. Result := 0;
  1838. end;
  1839. procedure LogLine(const CaptureList: TStringList; const S: AnsiString);
  1840. begin
  1841. var UTF8S := UTF8ToString(S);
  1842. if CaptureList <> nil then
  1843. CaptureList.Add(UTF8S)
  1844. else begin
  1845. FLogProc(UTF8S, False, FNextLineIsFirstLine, FLogProcData);
  1846. FNextLineIsFirstLine := False;
  1847. end;
  1848. end;
  1849. function SharedLimitReached: Boolean;
  1850. begin
  1851. Result := (FTotalBytesRead >= FMaxTotalBytesToRead) or
  1852. (FTotalLinesRead >= FMaxTotalLinesToRead);
  1853. end;
  1854. procedure DoRead(var Pipe: TCreateProcessOutputReaderPipe; const LastRead: Boolean);
  1855. begin
  1856. if Pipe.OKToRead then begin
  1857. if SharedLimitReached then begin
  1858. { The other pipe reached the shared limit which was handled and logged.
  1859. So don't read from this pipe but instead close it and exit silently. }
  1860. Pipe.OKToRead := False;
  1861. Pipe.Buffer := '';
  1862. CloseAndClearHandle(Pipe.PipeRead);
  1863. Exit;
  1864. end;
  1865. var TotalBytesAvail: DWORD;
  1866. Pipe.OKToRead := PeekNamedPipe(Pipe.PipeRead, nil, 0, nil, @TotalBytesAvail, nil);
  1867. if not Pipe.OKToRead then begin
  1868. var LastError := GetLastError;
  1869. if LastError <> ERROR_BROKEN_PIPE then begin
  1870. Pipe.Buffer := '';
  1871. HandleAndLogErrorFmt('PeekNamedPipe failed (%d).', [LastError]);
  1872. end;
  1873. end else if TotalBytesAvail > 0 then begin
  1874. { Don't read more than our read limit }
  1875. if TotalBytesAvail > FMaxTotalBytesToRead - FTotalBytesRead then
  1876. TotalBytesAvail := FMaxTotalBytesToRead - FTotalBytesRead;
  1877. { Append newly available data to the incomplete line we might already have }
  1878. var TotalBytesHave := ULength(Pipe.Buffer);
  1879. SetLength(Pipe.Buffer, TotalBytesHave+TotalBytesAvail);
  1880. var BytesRead: DWORD;
  1881. Pipe.OKToRead := ReadFile(Pipe.PipeRead, Pipe.Buffer[TotalBytesHave+1],
  1882. TotalBytesAvail, BytesRead, nil);
  1883. if not Pipe.OKToRead then begin
  1884. var LastError := GetLastError;
  1885. if LastError <> ERROR_BROKEN_PIPE then begin
  1886. Pipe.Buffer := '';
  1887. HandleAndLogErrorFmt('ReadFile failed (%d).', [LastError]);
  1888. end else begin
  1889. { Restore back to original size }
  1890. SetLength(Pipe.Buffer, TotalBytesHave);
  1891. end;
  1892. end else begin
  1893. { Correct length if less bytes were read than requested }
  1894. SetLength(Pipe.Buffer, TotalBytesHave+BytesRead);
  1895. { Check for completed lines thanks to the new data }
  1896. while FTotalLinesRead < FMaxTotalLinesToRead do begin
  1897. var P := FindNewLine(Pipe.Buffer, LastRead);
  1898. if P = 0 then
  1899. Break;
  1900. LogLine(Pipe.CaptureList, Copy(Pipe.Buffer, 1, P-1));
  1901. Inc(FTotalLinesRead);
  1902. if (Pipe.Buffer[P] = #13) and (P < Length(Pipe.Buffer)) and (Pipe.Buffer[P+1] = #10) then
  1903. Inc(P);
  1904. Delete(Pipe.Buffer, 1, P);
  1905. end;
  1906. Inc(FTotalBytesRead, BytesRead);
  1907. if SharedLimitReached then begin
  1908. { Read limit reached: break the pipe, throw away the incomplete line, and log an error }
  1909. Pipe.OKToRead := False;
  1910. Pipe.Buffer := '';
  1911. if FTotalBytesRead >= FMaxTotalBytesToRead then
  1912. HandleAndLogErrorFmt('Maximum output length (%d) reached, ignoring remainder.', [FMaxTotalBytesToRead])
  1913. else
  1914. HandleAndLogErrorFmt('Maximum output lines (%d) reached, ignoring remainder.', [FMaxTotalLinesToRead]);
  1915. end;
  1916. end;
  1917. end;
  1918. { Unblock the child process's write, and cause further writes to fail immediately }
  1919. if not Pipe.OkToRead then
  1920. CloseAndClearHandle(Pipe.PipeRead);
  1921. end;
  1922. if LastRead and (Pipe.Buffer <> '') then begin
  1923. var N := Length(Pipe.Buffer);
  1924. if Pipe.Buffer[N] = #13 then begin
  1925. { See FindNewLine: the buffer could end with a final #13 which needs to
  1926. be stripped still }
  1927. Delete(Pipe.Buffer, N, 1);
  1928. end;
  1929. LogLine(Pipe.CaptureList, Pipe.Buffer);
  1930. end;
  1931. end;
  1932. begin
  1933. DoRead(FStdOut, LastRead);
  1934. if FMode = omCapture then
  1935. DoRead(FStdErr, LastRead);
  1936. end;
  1937. end.