Shared.CommonFunc.pas 75 KB

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