Shared.CommonFunc.pas 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864
  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. TLogProc = procedure(const S: String; const Error, FirstLine: Boolean; const Data: NativeInt);
  29. TOutputMode = (omLog, omCapture);
  30. TCreateProcessOutputReaderPipe = record
  31. OKToRead: Boolean;
  32. PipeRead, PipeWrite: THandle;
  33. Buffer: AnsiString;
  34. CaptureList: TStringList;
  35. end;
  36. TCreateProcessOutputReader = class
  37. private
  38. FMaxTotalBytesToRead: Cardinal;
  39. FMaxTotalLinesToRead: Cardinal;
  40. FTotalBytesRead: Cardinal;
  41. FTotalLinesRead: Cardinal;
  42. FStdInNulDevice: THandle;
  43. FStdOut: TCreateProcessOutputReaderPipe;
  44. FStdErr: TCreateProcessOutputReaderPipe;
  45. FLogProc: TLogProc;
  46. FLogProcData: NativeInt;
  47. FNextLineIsFirstLine: Boolean;
  48. FMode: TOutputMode;
  49. FCaptureOutList: TStringList;
  50. FCaptureErrList: TStringList;
  51. FCaptureError: Boolean;
  52. procedure CloseAndClearHandle(var Handle: THandle);
  53. procedure HandleAndLogErrorFmt(const S: String; const Args: array of const);
  54. public
  55. constructor Create(const ALogProc: TLogProc; const ALogProcData: NativeInt; AMode: TOutputMode = omLog);
  56. destructor Destroy; override;
  57. procedure UpdateStartupInfo(var StartupInfo: TStartupInfo);
  58. procedure NotifyCreateProcessDone;
  59. procedure Read(const LastRead: Boolean);
  60. property CaptureOutList: TStringList read FCaptureOutList;
  61. property CaptureErrList: TStringList read FCaptureErrList;
  62. property CaptureError: Boolean read FCaptureError;
  63. end;
  64. TRegView = (rvDefault, rv32Bit, rv64Bit);
  65. const
  66. RegViews64Bit = [rv64Bit];
  67. function NewFileExists(const Name: String): Boolean;
  68. function DirExists(const Name: String): Boolean;
  69. function FileOrDirExists(const Name: String): Boolean;
  70. function IsDirectoryAndNotReparsePoint(const Name: String): Boolean;
  71. function GetIniString(const Section, Key: String; Default: String; const Filename: String): String;
  72. function GetIniInt(const Section, Key: String; const Default, Min, Max: Longint; const Filename: String): Longint;
  73. function GetIniBool(const Section, Key: String; const Default: Boolean; const Filename: String): Boolean;
  74. function IniKeyExists(const Section, Key, Filename: String): Boolean;
  75. function IsIniSectionEmpty(const Section, Filename: String): Boolean;
  76. function SetIniString(const Section, Key, Value, Filename: String): Boolean;
  77. function SetIniInt(const Section, Key: String; const Value: Longint; const Filename: String): Boolean;
  78. function SetIniBool(const Section, Key: String; const Value: Boolean; const Filename: String): Boolean;
  79. procedure DeleteIniEntry(const Section, Key, Filename: String);
  80. procedure DeleteIniSection(const Section, Filename: String);
  81. function GetEnv(const EnvVar: String): String;
  82. function GetCmdTail: String;
  83. function GetCmdTailEx(StartIndex: Integer): String;
  84. function NewParamCount: Integer;
  85. function NewParamStr(Index: Integer): string;
  86. function AddQuotes(const S: String): String;
  87. function RemoveQuotes(const S: String): String;
  88. function GetShortName(const LongName: String): String;
  89. function GetWinDir: String;
  90. function GetSystemWinDir: String;
  91. function GetSystemDir: String;
  92. function GetSysWow64Dir: String;
  93. function GetSysNativeDir(const IsWin64: Boolean): String;
  94. function GetTempDir: String;
  95. function StringChange(var S: String; const FromStr, ToStr: String): Integer;
  96. function StringChangeEx(var S: String; const FromStr, ToStr: String;
  97. const SupportDBCS: Boolean): Integer;
  98. function AdjustLength(var S: String; const Res: Cardinal): Boolean;
  99. function ConvertConstPercentStr(var S: String): Boolean;
  100. function ConvertPercentStr(var S: String): Boolean;
  101. function ConstPos(const Ch: Char; const S: String): Integer;
  102. function SkipPastConst(const S: String; const Start: Integer): Integer;
  103. function RegQueryStringValue(H: HKEY; Name: PChar; var ResultStr: String; AllowDWord: Boolean = False): Boolean;
  104. function RegQueryMultiStringValue(H: HKEY; Name: PChar; var ResultStr: String): Boolean;
  105. function RegValueExists(H: HKEY; Name: PChar): Boolean;
  106. function RegCreateKeyExView(const RegView: TRegView; hKey: HKEY; lpSubKey: PChar;
  107. Reserved: DWORD; lpClass: PChar; dwOptions: DWORD; samDesired: REGSAM;
  108. lpSecurityAttributes: PSecurityAttributes; var phkResult: HKEY;
  109. lpdwDisposition: PDWORD): Longint;
  110. function RegOpenKeyExView(const RegView: TRegView; hKey: HKEY; lpSubKey: PChar;
  111. ulOptions: DWORD; samDesired: REGSAM; var phkResult: HKEY): Longint;
  112. function RegDeleteKeyView(const RegView: TRegView; const Key: HKEY; const Name: PChar): Longint;
  113. function RegDeleteKeyIncludingSubkeys(const RegView: TRegView; const Key: HKEY; const Name: PChar): Longint;
  114. function RegDeleteKeyIfEmpty(const RegView: TRegView; const RootKey: HKEY; const SubkeyName: PChar): Longint;
  115. function GetShellFolderPath(const FolderID: Integer): String;
  116. function GetCurrentUserSid: String;
  117. function IsAdminLoggedOn: Boolean;
  118. function IsPowerUserLoggedOn: Boolean;
  119. function IsMultiByteString(const S: AnsiString): Boolean;
  120. function FontExists(const FaceName: String): Boolean;
  121. function GetUILanguage: LANGID;
  122. function RemoveAccelChar(const S: String): String;
  123. function GetTextWidth(const DC: HDC; S: String; const Prefix: Boolean): Integer;
  124. function AddPeriod(const S: String): String;
  125. function GetExceptMessage: String;
  126. function GetPreferredUIFont: String;
  127. function IsWildcard(const Pattern: String): Boolean;
  128. function WildcardMatch(const Text, Pattern: PChar): Boolean;
  129. function IntMax(const A, B: Integer): Integer;
  130. function Win32ErrorString(ErrorCode: Integer): String;
  131. function DeleteDirTree(const Dir: String): Boolean;
  132. function SetNTFSCompression(const FileOrDir: String; Compress: Boolean): Boolean;
  133. procedure AddToWindowMessageFilterEx(const Wnd: HWND; const Msg: UINT);
  134. function ShutdownBlockReasonCreate(Wnd: HWND; const Reason: String): Boolean;
  135. function ShutdownBlockReasonDestroy(Wnd: HWND): Boolean;
  136. function TryStrToBoolean(const S: String; var BoolResult: Boolean): Boolean;
  137. procedure WaitMessageWithTimeout(const Milliseconds: DWORD);
  138. function MoveFileReplace(const ExistingFileName, NewFileName: String): Boolean;
  139. procedure TryEnableAutoCompleteFileSystem(Wnd: HWND);
  140. procedure CreateMutex(const MutexName: String);
  141. implementation
  142. uses
  143. PathFunc;
  144. { Avoid including Variants (via ActiveX and ShlObj) in SetupLdr (SetupLdr uses CmnFunc2), saving 26 KB. }
  145. const
  146. shell32 = 'shell32.dll';
  147. type
  148. PSHItemID = ^TSHItemID;
  149. _SHITEMID = record
  150. cb: Word; { Size of the ID (including cb itself) }
  151. abID: array[0..0] of Byte; { The item ID (variable length) }
  152. end;
  153. TSHItemID = _SHITEMID;
  154. SHITEMID = _SHITEMID;
  155. PItemIDList = ^TItemIDList;
  156. _ITEMIDLIST = record
  157. mkid: TSHItemID;
  158. end;
  159. TItemIDList = _ITEMIDLIST;
  160. ITEMIDLIST = _ITEMIDLIST;
  161. IMalloc = interface(IUnknown)
  162. ['{00000002-0000-0000-C000-000000000046}']
  163. function Alloc(cb: Longint): Pointer; stdcall;
  164. function Realloc(pv: Pointer; cb: Longint): Pointer; stdcall;
  165. procedure Free(pv: Pointer); stdcall;
  166. function GetSize(pv: Pointer): Longint; stdcall;
  167. function DidAlloc(pv: Pointer): Integer; stdcall;
  168. procedure HeapMinimize; stdcall;
  169. end;
  170. function SHGetMalloc(var ppMalloc: IMalloc): HResult; stdcall; external shell32 name 'SHGetMalloc';
  171. function SHGetSpecialFolderLocation(hwndOwner: HWND; nFolder: Integer;
  172. var ppidl: PItemIDList): HResult; stdcall; external shell32 name 'SHGetSpecialFolderLocation';
  173. function SHGetPathFromIDList(pidl: PItemIDList; pszPath: PChar): BOOL; stdcall;
  174. external shell32 name 'SHGetPathFromIDListW';
  175. function InternalGetFileAttr(const Name: String): DWORD;
  176. begin
  177. Result := GetFileAttributes(PChar(RemoveBackslashUnlessRoot(Name)));
  178. end;
  179. function NewFileExists(const Name: String): Boolean;
  180. { Returns True if the specified file exists.
  181. This function is better than Delphi's FileExists function because it works
  182. on files in directories that don't have "list" permission. There is, however,
  183. one other difference: FileExists allows wildcards, but this function does
  184. not. }
  185. begin
  186. var Attr := GetFileAttributes(PChar(Name));
  187. Result := (Attr <> INVALID_FILE_ATTRIBUTES) and (Attr and faDirectory = 0);
  188. end;
  189. function DirExists(const Name: String): Boolean;
  190. { Returns True if the specified directory name exists. The specified name
  191. may include a trailing backslash.
  192. NOTE: Delphi's FileCtrl unit has a similar function called DirectoryExists.
  193. However, the implementation is different between Delphi 1 and 2. (Delphi 1
  194. does not count hidden or system directories as existing.) }
  195. begin
  196. var Attr := InternalGetFileAttr(Name);
  197. Result := (Attr <> INVALID_FILE_ATTRIBUTES) and (Attr and faDirectory <> 0);
  198. end;
  199. function FileOrDirExists(const Name: String): Boolean;
  200. { Returns True if the specified directory or file name exists. The specified
  201. name may include a trailing backslash. }
  202. begin
  203. Result := InternalGetFileAttr(Name) <> INVALID_FILE_ATTRIBUTES;
  204. end;
  205. function IsDirectoryAndNotReparsePoint(const Name: String): Boolean;
  206. { Returns True if the specified directory exists and is NOT a reparse point. }
  207. const
  208. FILE_ATTRIBUTE_REPARSE_POINT = $00000400;
  209. var
  210. Attr: DWORD;
  211. begin
  212. Attr := GetFileAttributes(PChar(Name));
  213. Result := (Attr <> INVALID_FILE_ATTRIBUTES) and
  214. (Attr and FILE_ATTRIBUTE_DIRECTORY <> 0) and
  215. (Attr and FILE_ATTRIBUTE_REPARSE_POINT = 0);
  216. end;
  217. function GetIniString(const Section, Key: String; Default: String;
  218. const Filename: String): String;
  219. var
  220. BufSize, Len: Integer;
  221. begin
  222. { On Windows 9x, Get*ProfileString can modify the lpDefault parameter, so
  223. make sure it's unique and not read-only }
  224. UniqueString(Default);
  225. BufSize := 256;
  226. while True do begin
  227. SetString(Result, nil, BufSize);
  228. if Filename <> '' then
  229. Len := GetPrivateProfileString(PChar(Section), PChar(Key), PChar(Default),
  230. @Result[1], BufSize, PChar(Filename))
  231. else
  232. Len := GetProfileString(PChar(Section), PChar(Key), PChar(Default),
  233. @Result[1], BufSize);
  234. { Work around bug present on Windows NT/2000 (not 95): When lpDefault is
  235. too long to fit in the buffer, nSize is returned (null terminator
  236. counted) instead of nSize-1 (what it's supposed to return). So don't
  237. trust the returned length; calculate it ourself.
  238. Note: This also ensures the string can never include embedded nulls. }
  239. if Len <> 0 then
  240. Len := StrLen(PChar(Result));
  241. { Break if the string fits, or if it's apparently 64 KB or longer.
  242. No point in increasing buffer size past 64 KB because the length
  243. returned by Windows 2000 seems to be mod 65536. And Windows 95 returns
  244. 0 on values longer than ~32 KB.
  245. Note: The docs say the function returns "nSize minus one" if the buffer
  246. is too small, but I'm willing to bet it can be "minus two" if the last
  247. character is double-byte. Let's just be extremely paranoid and check for
  248. BufSize-8. }
  249. if (Len < BufSize-8) or (BufSize >= 65536) then begin
  250. SetLength(Result, Len);
  251. Break;
  252. end;
  253. { Otherwise double the buffer size and try again }
  254. BufSize := BufSize * 2;
  255. end;
  256. end;
  257. function GetIniInt(const Section, Key: String;
  258. const Default, Min, Max: Longint; const Filename: String): Longint;
  259. { Reads a Longint from an INI file. If the Longint read is not between Min/Max
  260. then it returns Default. If Min=Max then Min/Max are ignored }
  261. var
  262. S: String;
  263. E: Integer;
  264. begin
  265. S := GetIniString(Section, Key, '', Filename);
  266. if S = '' then
  267. Result := Default
  268. else begin
  269. Val(S, Result, E);
  270. if (E <> 0) or ((Min <> Max) and ((Result < Min) or (Result > Max))) then
  271. Result := Default;
  272. end;
  273. end;
  274. function GetIniBool(const Section, Key: String; const Default: Boolean;
  275. const Filename: String): Boolean;
  276. begin
  277. Result := GetIniInt(Section, Key, Ord(Default), 0, 0, Filename) <> 0;
  278. end;
  279. function IniKeyExists(const Section, Key, Filename: String): Boolean;
  280. function Equals(const Default: PChar): Boolean;
  281. var
  282. Test: array[0..7] of Char;
  283. begin
  284. Test[0] := #0;
  285. if Filename <> '' then
  286. GetPrivateProfileString(PChar(Section), PChar(Key), Default,
  287. Test, SizeOf(Test) div SizeOf(Test[0]), PChar(Filename))
  288. else
  289. GetProfileString(PChar(Section), PChar(Key), Default,
  290. Test, SizeOf(Test) div SizeOf(Test[0]));
  291. Result := lstrcmp(Test, Default) = 0;
  292. end;
  293. begin
  294. { If the key does not exist, a default string is returned both times. }
  295. Result := not Equals('x1234x') or not Equals('x5678x'); { <- don't change }
  296. end;
  297. function IsIniSectionEmpty(const Section, Filename: String): Boolean;
  298. var
  299. Test: array[0..255] of Char;
  300. begin
  301. Test[0] := #0;
  302. if Filename <> '' then
  303. GetPrivateProfileString(PChar(Section), nil, '', Test,
  304. SizeOf(Test) div SizeOf(Test[0]), PChar(Filename))
  305. else
  306. GetProfileString(PChar(Section), nil, '', Test,
  307. SizeOf(Test) div SizeOf(Test[0]));
  308. Result := Test[0] = #0;
  309. end;
  310. function SetIniString(const Section, Key, Value, Filename: String): Boolean;
  311. begin
  312. if Filename <> '' then
  313. Result := WritePrivateProfileString(PChar(Section), PChar(Key),
  314. PChar(Value), PChar(Filename))
  315. else
  316. Result := WriteProfileString(PChar(Section), PChar(Key),
  317. PChar(Value));
  318. end;
  319. function SetIniInt(const Section, Key: String; const Value: Longint;
  320. const Filename: String): Boolean;
  321. begin
  322. Result := SetIniString(Section, Key, IntToStr(Value), Filename);
  323. end;
  324. function SetIniBool(const Section, Key: String; const Value: Boolean;
  325. const Filename: String): Boolean;
  326. begin
  327. Result := SetIniInt(Section, Key, Ord(Value), Filename);
  328. end;
  329. procedure DeleteIniEntry(const Section, Key, Filename: String);
  330. begin
  331. if Filename <> '' then
  332. WritePrivateProfileString(PChar(Section), PChar(Key),
  333. nil, PChar(Filename))
  334. else
  335. WriteProfileString(PChar(Section), PChar(Key),
  336. nil);
  337. end;
  338. procedure DeleteIniSection(const Section, Filename: String);
  339. begin
  340. if Filename <> '' then
  341. WritePrivateProfileString(PChar(Section), nil, nil,
  342. PChar(Filename))
  343. else
  344. WriteProfileString(PChar(Section), nil, nil);
  345. end;
  346. function GetEnv(const EnvVar: String): String;
  347. { Gets the value of the specified environment variable. (Just like TP's GetEnv) }
  348. var
  349. Res: DWORD;
  350. begin
  351. SetLength(Result, 255);
  352. repeat
  353. Res := GetEnvironmentVariable(PChar(EnvVar), PChar(Result), Length(Result));
  354. if Res = 0 then begin
  355. Result := '';
  356. Break;
  357. end;
  358. until AdjustLength(Result, Res);
  359. end;
  360. function GetParamStr(const P: PChar; var Param: String): PChar;
  361. function Extract(P: PChar; const Buffer: PChar; var Len: Integer): PChar;
  362. var
  363. InQuote: Boolean;
  364. begin
  365. Len := 0;
  366. InQuote := False;
  367. while (P^ <> #0) and ((P^ > ' ') or InQuote) do begin
  368. if P^ = '"' then
  369. InQuote := not InQuote
  370. else begin
  371. if Assigned(Buffer) then
  372. Buffer[Len] := P^;
  373. Inc(Len);
  374. end;
  375. Inc(P);
  376. end;
  377. Result := P;
  378. end;
  379. var
  380. Len: Integer;
  381. Buffer: String;
  382. begin
  383. Extract(P, nil, Len);
  384. SetString(Buffer, nil, Len);
  385. Result := Extract(P, @Buffer[1], Len);
  386. Param := Buffer;
  387. while (Result^ <> #0) and (Result^ <= ' ') do
  388. Inc(Result);
  389. end;
  390. function GetCmdTail: String;
  391. { Returns all command line parameters passed to the process as a single
  392. string. }
  393. var
  394. S: String;
  395. begin
  396. Result := GetParamStr(GetCommandLine, S);
  397. end;
  398. function GetCmdTailEx(StartIndex: Integer): String;
  399. { Returns all command line parameters passed to the process as a single
  400. string, starting with StartIndex (one-based). }
  401. var
  402. P: PChar;
  403. S: String;
  404. begin
  405. P := GetParamStr(GetCommandLine, S);
  406. while (StartIndex > 1) and (P^ <> #0) do begin
  407. P := GetParamStr(P, S);
  408. Dec(StartIndex);
  409. end;
  410. Result := P;
  411. end;
  412. function NewParamCount: Integer;
  413. var
  414. P: PChar;
  415. S: String;
  416. begin
  417. P := GetParamStr(GetCommandLine, S);
  418. Result := 0;
  419. while P^ <> #0 do begin
  420. Inc(Result);
  421. P := GetParamStr(P, S);
  422. end;
  423. end;
  424. function NewParamStr(Index: Integer): string;
  425. { Returns the Indexth command line parameter, or an empty string if Index is
  426. out of range.
  427. Differences from Delphi's ParamStr:
  428. - No limits on parameter length
  429. - Doesn't ignore empty parameters ("")
  430. - Handles the empty argv[0] case like MSVC: if GetCommandLine() returns
  431. " a b" then NewParamStr(1) should return "a", not "b" }
  432. var
  433. Buffer: array[0..MAX_PATH-1] of Char;
  434. S: String;
  435. P: PChar;
  436. begin
  437. if Index = 0 then begin
  438. SetString(Result, Buffer, GetModuleFileName(0, Buffer, SizeOf(Buffer) div SizeOf(Buffer[0])));
  439. end
  440. else begin
  441. P := GetCommandLine;
  442. while True do begin
  443. if P^ = #0 then begin
  444. S := '';
  445. Break;
  446. end;
  447. P := GetParamStr(P, S);
  448. if Index = 0 then Break;
  449. Dec(Index);
  450. end;
  451. Result := S;
  452. end;
  453. end;
  454. function AddQuotes(const S: String): String;
  455. { Adds a quote (") character to the left and right sides of the string if
  456. the string contains a space and it didn't have quotes already. This is
  457. primarily used when spawning another process with a long filename as one of
  458. the parameters. }
  459. begin
  460. Result := Trim(S);
  461. if (PathPos(' ', Result) <> 0) and
  462. ((Result[1] <> '"') or (PathLastChar(Result)^ <> '"')) then
  463. Result := '"' + Result + '"';
  464. end;
  465. function RemoveQuotes(const S: String): String;
  466. { Opposite of AddQuotes; removes any quotes around the string. }
  467. begin
  468. Result := S;
  469. while (Result <> '') and (Result[1] = '"') do
  470. Delete(Result, 1, 1);
  471. while (Result <> '') and (PathLastChar(Result)^ = '"') do
  472. SetLength(Result, Length(Result)-1);
  473. end;
  474. function ConvertPercentStr(var S: String): Boolean;
  475. { Expands all %-encoded characters in the string (see RFC 2396). Returns True
  476. if all were successfully expanded. }
  477. var
  478. I, C, E: Integer;
  479. N: String;
  480. begin
  481. Result := True;
  482. I := 1;
  483. while I <= Length(S) do begin
  484. if S[I] = '%' then begin
  485. N := Copy(S, I, 3);
  486. if Length(N) <> 3 then begin
  487. Result := False;
  488. Break;
  489. end;
  490. N[1] := '$';
  491. Val(N, C, E);
  492. if E <> 0 then begin
  493. Result := False;
  494. Break;
  495. end;
  496. { delete the two numbers following '%', and replace '%' with the character }
  497. Delete(S, I+1, 2);
  498. S[I] := Chr(C);
  499. end;
  500. Inc(I);
  501. end;
  502. end;
  503. function SkipPastConst(const S: String; const Start: Integer): Integer;
  504. { Returns the character index following the Inno Setup constant embedded
  505. into the string S at index Start.
  506. If the constant is not closed (missing a closing brace), it returns zero. }
  507. var
  508. L, BraceLevel, LastOpenBrace: Integer;
  509. begin
  510. Result := Start;
  511. L := Length(S);
  512. if Result < L then begin
  513. Inc(Result);
  514. if S[Result] = '{' then begin
  515. Inc(Result);
  516. Exit;
  517. end
  518. else begin
  519. BraceLevel := 1;
  520. LastOpenBrace := -1;
  521. while Result <= L do begin
  522. case S[Result] of
  523. '{': begin
  524. if LastOpenBrace <> Result-1 then begin
  525. Inc(BraceLevel);
  526. LastOpenBrace := Result;
  527. end
  528. else
  529. { Skip over '{{' when in an embedded constant }
  530. Dec(BraceLevel);
  531. end;
  532. '}': begin
  533. Dec(BraceLevel);
  534. if BraceLevel = 0 then begin
  535. Inc(Result);
  536. Exit;
  537. end;
  538. end;
  539. end;
  540. Inc(Result);
  541. end;
  542. end;
  543. end;
  544. Result := 0;
  545. end;
  546. function ConvertConstPercentStr(var S: String): Boolean;
  547. { Same as ConvertPercentStr, but is designed to ignore embedded Inno Setup
  548. constants. Any '%' characters between braces are not translated. Two
  549. consecutive braces are ignored. }
  550. var
  551. I, C, E: Integer;
  552. N: String;
  553. begin
  554. Result := True;
  555. I := 1;
  556. while I <= Length(S) do begin
  557. case S[I] of
  558. '{': begin
  559. I := SkipPastConst(S, I);
  560. if I = 0 then begin
  561. Result := False;
  562. Break;
  563. end;
  564. Dec(I); { ...since there's an Inc below }
  565. end;
  566. '%': begin
  567. N := Copy(S, I, 3);
  568. if Length(N) <> 3 then begin
  569. Result := False;
  570. Break;
  571. end;
  572. N[1] := '$';
  573. Val(N, C, E);
  574. if E <> 0 then begin
  575. Result := False;
  576. Break;
  577. end;
  578. { delete the two numbers following '%', and replace '%' with the character }
  579. Delete(S, I+1, 2);
  580. S[I] := Chr(C);
  581. end;
  582. end;
  583. Inc(I);
  584. end;
  585. end;
  586. function ConstPos(const Ch: Char; const S: String): Integer;
  587. { Like the standard Pos function, but skips over any Inno Setup constants
  588. embedded in S }
  589. var
  590. I, L: Integer;
  591. begin
  592. Result := 0;
  593. I := 1;
  594. L := Length(S);
  595. while I <= L do begin
  596. if S[I] = Ch then begin
  597. Result := I;
  598. Break;
  599. end
  600. else if S[I] = '{' then begin
  601. I := SkipPastConst(S, I);
  602. if I = 0 then
  603. Break;
  604. end
  605. else
  606. Inc(I);
  607. end;
  608. end;
  609. function GetShortName(const LongName: String): String;
  610. { Gets the short version of the specified long filename. If the file does not
  611. exist, or some other error occurs, it returns LongName. }
  612. var
  613. Res: DWORD;
  614. begin
  615. SetLength(Result, MAX_PATH);
  616. repeat
  617. Res := GetShortPathName(PChar(LongName), PChar(Result), Length(Result));
  618. if Res = 0 then begin
  619. Result := LongName;
  620. Break;
  621. end;
  622. until AdjustLength(Result, Res);
  623. end;
  624. function GetWinDir: String;
  625. { Returns fully qualified path of the Windows directory. Only includes a
  626. trailing backslash if the Windows directory is the root directory. }
  627. var
  628. Buf: array[0..MAX_PATH-1] of Char;
  629. begin
  630. GetWindowsDirectory(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
  631. Result := StrPas(Buf);
  632. end;
  633. function GetSystemWindowsDirectoryW(lpBuffer: LPWSTR; uSize: UINT): UINT; stdcall; external kernel32;
  634. function GetSystemWinDir: String;
  635. { Like get GetWinDir but uses GetSystemWindowsDirectory instead of
  636. GetWindowsDirectory: With Terminal Services, the GetSystemWindowsDirectory
  637. function retrieves the path of the system Windows directory, while the
  638. GetWindowsDirectory function retrieves the path of a Windows directory that is
  639. private for each user. On a single-user system, GetSystemWindowsDirectory is
  640. the same as GetWindowsDirectory. }
  641. var
  642. Buf: array[0..MAX_PATH-1] of Char;
  643. begin
  644. GetSystemWindowsDirectoryW(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
  645. Result := StrPas(Buf);
  646. end;
  647. function GetSystemDir: String;
  648. { Returns fully qualified path of the Windows System directory. Only includes a
  649. trailing backslash if the Windows System directory is the root directory. }
  650. var
  651. Buf: array[0..MAX_PATH-1] of Char;
  652. begin
  653. GetSystemDirectory(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
  654. Result := StrPas(Buf);
  655. end;
  656. function GetSysWow64Dir: String;
  657. { Returns fully qualified path of the SysWow64 directory on 64-bit Windows.
  658. Returns '' if there is no SysWow64 directory (e.g. running 32-bit Windows). }
  659. var
  660. GetSystemWow64DirectoryFunc: function(
  661. lpBuffer: PWideChar; uSize: UINT): UINT; stdcall;
  662. Res: Integer;
  663. Buf: array[0..MAX_PATH] of Char;
  664. begin
  665. Result := '';
  666. GetSystemWow64DirectoryFunc := GetProcAddress(GetModuleHandle(kernel32),
  667. 'GetSystemWow64DirectoryW');
  668. if Assigned(GetSystemWow64DirectoryFunc) then begin
  669. Res := GetSystemWow64DirectoryFunc(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
  670. if (Res > 0) and (Res < SizeOf(Buf) div SizeOf(Buf[0])) then
  671. Result := Buf;
  672. end;
  673. end;
  674. function GetSysNativeDir(const IsWin64: Boolean): String;
  675. { Returns the special Sysnative alias, without trailing backslash.
  676. Returns '' if there is no Sysnative alias. }
  677. begin
  678. { From MSDN: 32-bit applications can access the native system directory by
  679. substituting %windir%\Sysnative for %windir%\System32. WOW64 recognizes
  680. Sysnative as a special alias used to indicate that the file system should
  681. not redirect the access. }
  682. if IsWin64 then
  683. { Note: Avoiding GetWinDir here as that might not return the real Windows
  684. directory under Terminal Services }
  685. Result := PathExpand(AddBackslash(GetSystemDir) + '..\Sysnative') { Do not localize }
  686. else
  687. Result := '';
  688. end;
  689. function GetTempDir: String;
  690. { Returns fully qualified path of the temporary directory, with trailing
  691. backslash. }
  692. var
  693. GetTempPathFunc: function(nBufferLength: DWORD; lpBuffer: LPWSTR): DWORD; stdcall;
  694. Buf: array[0..MAX_PATH] of Char;
  695. begin
  696. { When available, GetTempPath2 is preferred as it returns a private
  697. directory (typically C:\Windows\SystemTemp) when running as SYSTEM }
  698. GetTempPathFunc := GetProcAddress(GetModuleHandle(kernel32),
  699. PAnsiChar('GetTempPath2W'));
  700. if not Assigned(GetTempPathFunc) then
  701. GetTempPathFunc := GetTempPathW;
  702. const Res = GetTempPathFunc(SizeOf(Buf) div SizeOf(Buf[0]), Buf);
  703. if (Res > 0) and (Res < SizeOf(Buf) div SizeOf(Buf[0])) then begin
  704. { The docs say the returned path is fully qualified and ends with a
  705. backslash, but let's be really sure! }
  706. Result := AddBackslash(PathExpand(Buf));
  707. Exit;
  708. end;
  709. { We don't expect GetTempPath to ever fail or claim a larger buffer is
  710. needed (docs say maximum possible return value is MAX_PATH+1), but if it
  711. does, raise an exception as this function has no return value for failure }
  712. raise Exception.CreateFmt('GetTempDir: GetTempPath failed (%u, %u)',
  713. [Res, GetLastError]);
  714. end;
  715. function StringChangeEx(var S: String; const FromStr, ToStr: String;
  716. const SupportDBCS: Boolean): Integer;
  717. { Changes all occurrences in S of FromStr to ToStr. If SupportDBCS is True
  718. (recommended), double-byte character sequences in S are recognized and
  719. handled properly. Otherwise, the function behaves in a binary-safe manner.
  720. Returns the number of times FromStr was matched and changed. }
  721. var
  722. FromStrLen, I, EndPos, J: Integer;
  723. IsMatch: Boolean;
  724. label 1;
  725. begin
  726. Result := 0;
  727. if FromStr = '' then Exit;
  728. FromStrLen := Length(FromStr);
  729. I := 1;
  730. 1:EndPos := Length(S) - FromStrLen + 1;
  731. while I <= EndPos do begin
  732. IsMatch := True;
  733. J := 0;
  734. while J < FromStrLen do begin
  735. if S[J+I] <> FromStr[J+1] then begin
  736. IsMatch := False;
  737. Break;
  738. end;
  739. Inc(J);
  740. end;
  741. if IsMatch then begin
  742. Inc(Result);
  743. Delete(S, I, FromStrLen);
  744. Insert(ToStr, S, I);
  745. Inc(I, Length(ToStr));
  746. goto 1;
  747. end;
  748. if SupportDBCS then
  749. Inc(I, PathCharLength(S, I))
  750. else
  751. Inc(I);
  752. end;
  753. end;
  754. function StringChange(var S: String; const FromStr, ToStr: String): Integer;
  755. { Same as calling StringChangeEx with SupportDBCS=False }
  756. begin
  757. Result := StringChangeEx(S, FromStr, ToStr, False);
  758. end;
  759. function AdjustLength(var S: String; const Res: Cardinal): Boolean;
  760. { Returns True if successful. Returns False if buffer wasn't large enough,
  761. and called AdjustLength to resize it. }
  762. begin
  763. Result := Integer(Res) < Length(S);
  764. SetLength(S, Res);
  765. end;
  766. function InternalRegQueryStringValue(H: HKEY; Name: PChar; var ResultStr: String;
  767. Type1, Type2, Type3: DWORD): Boolean;
  768. var
  769. Typ, Size: DWORD;
  770. Len: Integer;
  771. S: String;
  772. ErrorCode: Longint;
  773. label 1;
  774. begin
  775. Result := False;
  776. 1:Size := 0;
  777. if (RegQueryValueEx(H, Name, nil, @Typ, nil, @Size) = ERROR_SUCCESS) and
  778. ((Typ = Type1) or (Typ = Type2) or ((Type3 <> REG_NONE) and (Typ = Type3))) then begin
  779. if Typ = REG_DWORD then begin
  780. var Data: DWORD;
  781. Size := SizeOf(Data);
  782. if (RegQueryValueEx(H, Name, nil, @Typ, @Data, @Size) = ERROR_SUCCESS) and
  783. (Typ = REG_DWORD) and (Size = Sizeof(Data)) then begin
  784. ResultStr := Data.ToString;
  785. Result := True;
  786. end;
  787. end else if Size = 0 then begin
  788. { It's an empty string with no null terminator.
  789. (Must handle those here since we can't pass a nil lpData pointer on
  790. the second RegQueryValueEx call.) }
  791. ResultStr := '';
  792. Result := True;
  793. end
  794. else begin
  795. { Paranoia: Impose reasonable upper limit on Size to avoid potential
  796. integer overflows below }
  797. if Cardinal(Size) >= Cardinal($70000000) then
  798. OutOfMemoryError;
  799. { Note: If Size isn't a multiple of SizeOf(S[1]), we have to round up
  800. here so that RegQueryValueEx doesn't overflow the buffer }
  801. Len := (Size + (SizeOf(S[1]) - 1)) div SizeOf(S[1]);
  802. SetString(S, nil, Len);
  803. ErrorCode := RegQueryValueEx(H, Name, nil, @Typ, @S[1], @Size);
  804. if ErrorCode = ERROR_MORE_DATA then begin
  805. { The data must've increased in size since the first RegQueryValueEx
  806. call. Start over. }
  807. goto 1;
  808. end;
  809. if (ErrorCode = ERROR_SUCCESS) and
  810. ((Typ = Type1) or (Typ = Type2) or (Typ = Type3)) then begin
  811. { If Size isn't a multiple of SizeOf(S[1]), we disregard the partial
  812. character, like RegGetValue }
  813. Len := Size div SizeOf(S[1]);
  814. { Remove any null terminators from the end and trim the string to the
  815. returned length.
  816. Note: We *should* find 1 null terminator, but it's possible for
  817. there to be more or none if the value was written that way. }
  818. while (Len <> 0) and (S[Len] = #0) do
  819. Dec(Len);
  820. { In a REG_MULTI_SZ value, each individual string is null-terminated,
  821. so add 1 null (back) to the end, unless there are no strings (Len=0) }
  822. if (Typ = REG_MULTI_SZ) and (Len <> 0) then
  823. Inc(Len);
  824. SetLength(S, Len);
  825. if (Typ = REG_MULTI_SZ) and (Len <> 0) then
  826. S[Len] := #0;
  827. ResultStr := S;
  828. Result := True;
  829. end;
  830. end;
  831. end;
  832. end;
  833. function RegQueryStringValue(H: HKEY; Name: PChar; var ResultStr: String; AllowDWord: Boolean): Boolean;
  834. { Queries the specified REG_SZ or REG_EXPAND_SZ registry key/value, and returns
  835. the value in ResultStr. Returns True if successful. When False is returned,
  836. ResultStr is unmodified. Optionally supports REG_DWORD. }
  837. begin
  838. var Type3: DWORD;
  839. if AllowDWord then
  840. Type3 := REG_DWORD
  841. else
  842. Type3 := REG_NONE;
  843. Result := InternalRegQueryStringValue(H, Name, ResultStr, REG_SZ,
  844. REG_EXPAND_SZ, Type3);
  845. end;
  846. function RegQueryMultiStringValue(H: HKEY; Name: PChar; var ResultStr: String): Boolean;
  847. { Queries the specified REG_MULTI_SZ registry key/value, and returns the value
  848. in ResultStr. Returns True if successful. When False is returned, ResultStr
  849. is unmodified. }
  850. begin
  851. Result := InternalRegQueryStringValue(H, Name, ResultStr, REG_MULTI_SZ,
  852. REG_MULTI_SZ, REG_NONE);
  853. end;
  854. function RegValueExists(H: HKEY; Name: PChar): Boolean;
  855. { Returns True if the specified value exists. Requires KEY_QUERY_VALUE access
  856. to the key. }
  857. begin
  858. Result := RegQueryValueEx(H, Name, nil, nil, nil, nil) = ERROR_SUCCESS;
  859. end;
  860. function RegCreateKeyExView(const RegView: TRegView; hKey: HKEY; lpSubKey: PChar;
  861. Reserved: DWORD; lpClass: PChar; dwOptions: DWORD; samDesired: REGSAM;
  862. lpSecurityAttributes: PSecurityAttributes; var phkResult: HKEY;
  863. lpdwDisposition: PDWORD): Longint;
  864. begin
  865. if RegView = rv64Bit then
  866. samDesired := samDesired or KEY_WOW64_64KEY;
  867. Result := RegCreateKeyEx(hKey, lpSubKey, Reserved, lpClass, dwOptions,
  868. samDesired, lpSecurityAttributes, phkResult, lpdwDisposition);
  869. end;
  870. function RegOpenKeyExView(const RegView: TRegView; hKey: HKEY; lpSubKey: PChar;
  871. ulOptions: DWORD; samDesired: REGSAM; var phkResult: HKEY): Longint;
  872. begin
  873. if RegView = rv64Bit then
  874. samDesired := samDesired or KEY_WOW64_64KEY;
  875. Result := RegOpenKeyEx(hKey, lpSubKey, ulOptions, samDesired, phkResult);
  876. end;
  877. var
  878. RegDeleteKeyExFunc: function(hKey: HKEY;
  879. lpSubKey: PWideChar; samDesired: REGSAM; Reserved: DWORD): Longint; stdcall;
  880. function RegDeleteKeyView(const RegView: TRegView; const Key: HKEY;
  881. const Name: PChar): Longint;
  882. begin
  883. if RegView <> rv64Bit then
  884. Result := RegDeleteKey(Key, Name)
  885. else begin
  886. if @RegDeleteKeyExFunc = nil then
  887. RegDeleteKeyExFunc := GetProcAddress(GetModuleHandle(advapi32),
  888. 'RegDeleteKeyExW');
  889. if Assigned(RegDeleteKeyExFunc) then
  890. Result := RegDeleteKeyExFunc(Key, Name, KEY_WOW64_64KEY, 0)
  891. else
  892. Result := ERROR_PROC_NOT_FOUND;
  893. end;
  894. end;
  895. function RegDeleteKeyIncludingSubkeys(const RegView: TRegView; const Key: HKEY;
  896. const Name: PChar): Longint;
  897. { Deletes the specified key and all subkeys.
  898. Returns ERROR_SUCCESS if the key was successful deleted. }
  899. var
  900. H: HKEY;
  901. KeyName: String;
  902. I, KeyNameCount: DWORD;
  903. ErrorCode: Longint;
  904. begin
  905. if (Name = nil) or (Name[0] = #0) then begin
  906. Result := ERROR_INVALID_PARAMETER;
  907. Exit;
  908. end;
  909. if RegOpenKeyExView(RegView, Key, Name, 0, KEY_ENUMERATE_SUB_KEYS, H) = ERROR_SUCCESS then begin
  910. try
  911. SetString(KeyName, nil, 256);
  912. I := 0;
  913. while True do begin
  914. KeyNameCount := Length(KeyName);
  915. ErrorCode := RegEnumKeyEx(H, I, @KeyName[1], KeyNameCount, nil, nil, nil, nil);
  916. if ErrorCode = ERROR_MORE_DATA then begin
  917. { Double the size of the buffer and try again }
  918. if Length(KeyName) >= 65536 then begin
  919. { Sanity check: If we tried a 64 KB buffer and it's still saying
  920. there's more data, something must be seriously wrong. Bail. }
  921. Break;
  922. end;
  923. SetString(KeyName, nil, Length(KeyName) * 2);
  924. Continue;
  925. end;
  926. if ErrorCode <> ERROR_SUCCESS then
  927. Break;
  928. if RegDeleteKeyIncludingSubkeys(RegView, H, PChar(KeyName)) <> ERROR_SUCCESS then
  929. Inc(I);
  930. end;
  931. finally
  932. RegCloseKey(H);
  933. end;
  934. end;
  935. Result := RegDeleteKeyView(RegView, Key, Name);
  936. end;
  937. function RegDeleteKeyIfEmpty(const RegView: TRegView; const RootKey: HKEY;
  938. const SubkeyName: PChar): Longint;
  939. { Deletes the specified subkey if it has no subkeys or values.
  940. Returns ERROR_SUCCESS if the key was successful deleted, ERROR_DIR_NOT_EMPTY
  941. if it was not deleted because it contained subkeys or values, or possibly
  942. some other Win32 error code. }
  943. var
  944. K: HKEY;
  945. NumSubkeys, NumValues: DWORD;
  946. begin
  947. Result := RegOpenKeyExView(RegView, RootKey, SubkeyName, 0, KEY_QUERY_VALUE, K);
  948. if Result <> ERROR_SUCCESS then
  949. Exit;
  950. Result := RegQueryInfoKey(K, nil, nil, nil, @NumSubkeys, nil, nil,
  951. @NumValues, nil, nil, nil, nil);
  952. RegCloseKey(K);
  953. if Result <> ERROR_SUCCESS then
  954. Exit;
  955. if (NumSubkeys = 0) and (NumValues = 0) then
  956. Result := RegDeleteKeyView(RegView, RootKey, SubkeyName)
  957. else
  958. Result := ERROR_DIR_NOT_EMPTY;
  959. end;
  960. function GetShellFolderPath(const FolderID: Integer): String;
  961. var
  962. pidl: PItemIDList;
  963. Buffer: array[0..MAX_PATH-1] of Char;
  964. Malloc: IMalloc;
  965. begin
  966. Result := '';
  967. if FAILED(SHGetMalloc(Malloc)) then
  968. Malloc := nil;
  969. if SUCCEEDED(SHGetSpecialFolderLocation(0, FolderID, pidl)) then begin
  970. if SHGetPathFromIDList(pidl, Buffer) then
  971. Result := Buffer;
  972. if Assigned(Malloc) then
  973. Malloc.Free(pidl);
  974. end;
  975. end;
  976. function GetCurrentUserSid: String;
  977. var
  978. Token: THandle;
  979. UserInfoSize: DWORD;
  980. UserInfo: PTokenUser;
  981. StringSid: PWideChar;
  982. begin
  983. Result := '';
  984. if not OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, Token) then
  985. Exit;
  986. UserInfo := nil;
  987. try
  988. UserInfoSize := 0;
  989. if not GetTokenInformation(Token, TokenUser, nil, 0, UserInfoSize) and
  990. (GetLastError <> ERROR_INSUFFICIENT_BUFFER) then
  991. Exit;
  992. GetMem(UserInfo, UserInfoSize);
  993. if not GetTokenInformation(Token, TokenUser, UserInfo,
  994. UserInfoSize, UserInfoSize) then
  995. Exit;
  996. if ConvertSidToStringSidW(UserInfo.User.Sid, StringSid) then begin
  997. Result := StringSid;
  998. LocalFree(StringSid);
  999. end;
  1000. finally
  1001. FreeMem(UserInfo);
  1002. CloseHandle(Token);
  1003. end;
  1004. end;
  1005. function IsMemberOfGroup(const DomainAliasRid: DWORD): Boolean;
  1006. { Returns True if the logged-on user is a member of the specified local
  1007. group. }
  1008. const
  1009. SECURITY_NT_AUTHORITY: TSIDIdentifierAuthority =
  1010. (Value: (0, 0, 0, 0, 0, 5));
  1011. SECURITY_BUILTIN_DOMAIN_RID = $00000020;
  1012. SE_GROUP_ENABLED = $00000004;
  1013. SE_GROUP_USE_FOR_DENY_ONLY = $00000010;
  1014. var
  1015. Sid: PSID;
  1016. CheckTokenMembership: function(TokenHandle: THandle; SidToCheck: PSID;
  1017. var IsMember: BOOL): BOOL; stdcall;
  1018. IsMember: BOOL;
  1019. Token: THandle;
  1020. GroupInfoSize: DWORD;
  1021. GroupInfo: PTokenGroups;
  1022. I: Integer;
  1023. begin
  1024. Result := False;
  1025. if not AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2,
  1026. SECURITY_BUILTIN_DOMAIN_RID, DomainAliasRid,
  1027. 0, 0, 0, 0, 0, 0, Sid) then
  1028. Exit;
  1029. try
  1030. { Use CheckTokenMembership if available. MSDN states:
  1031. "The CheckTokenMembership function should be used with Windows 2000 and
  1032. later to determine whether a specified SID is present and enabled in an
  1033. access token. This function eliminates potential misinterpretations of
  1034. the active group membership if changes to access tokens are made in
  1035. future releases." }
  1036. CheckTokenMembership := GetProcAddress(GetModuleHandle(advapi32),
  1037. 'CheckTokenMembership');
  1038. if Assigned(CheckTokenMembership) then begin
  1039. if CheckTokenMembership(0, Sid, IsMember) then
  1040. Result := IsMember;
  1041. end
  1042. else begin { Should never happen }
  1043. GroupInfo := nil;
  1044. if not OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, Token) then begin
  1045. if GetLastError <> ERROR_NO_TOKEN then
  1046. Exit;
  1047. if not OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, Token) then
  1048. Exit;
  1049. end;
  1050. try
  1051. GroupInfoSize := 0;
  1052. if not GetTokenInformation(Token, TokenGroups, nil, 0, GroupInfoSize) and
  1053. (GetLastError <> ERROR_INSUFFICIENT_BUFFER) then
  1054. Exit;
  1055. GetMem(GroupInfo, GroupInfoSize);
  1056. if not GetTokenInformation(Token, TokenGroups, GroupInfo,
  1057. GroupInfoSize, GroupInfoSize) then
  1058. Exit;
  1059. for I := 0 to GroupInfo.GroupCount-1 do begin
  1060. if EqualSid(Sid, GroupInfo.Groups[I].Sid) and
  1061. (GroupInfo.Groups[I].Attributes and (SE_GROUP_ENABLED or
  1062. SE_GROUP_USE_FOR_DENY_ONLY) = SE_GROUP_ENABLED) then begin
  1063. Result := True;
  1064. Break;
  1065. end;
  1066. end;
  1067. finally
  1068. FreeMem(GroupInfo);
  1069. CloseHandle(Token);
  1070. end;
  1071. end;
  1072. finally
  1073. FreeSid(Sid);
  1074. end;
  1075. end;
  1076. function IsAdminLoggedOn: Boolean;
  1077. { Returns True if the logged-on user is a member of the Administrators local
  1078. group. }
  1079. const
  1080. DOMAIN_ALIAS_RID_ADMINS = $00000220;
  1081. begin
  1082. Result := IsMemberOfGroup(DOMAIN_ALIAS_RID_ADMINS);
  1083. end;
  1084. function IsPowerUserLoggedOn: Boolean;
  1085. { Returns True if the logged-on user is a member of the Power Users local
  1086. group. }
  1087. const
  1088. DOMAIN_ALIAS_RID_POWER_USERS = $00000223;
  1089. begin
  1090. Result := IsMemberOfGroup(DOMAIN_ALIAS_RID_POWER_USERS);
  1091. end;
  1092. function IsMultiByteString(const S: AnsiString): Boolean;
  1093. var
  1094. I: Integer;
  1095. begin
  1096. Result := False;
  1097. for I := 1 to Length(S) do
  1098. if IsDBCSLeadByte(Ord(S[I])) then begin
  1099. Result := True;
  1100. Break;
  1101. end;
  1102. end;
  1103. function FontExistsCallback(const lplf: TLogFont; const lptm: TTextMetric;
  1104. dwType: DWORD; lpData: LPARAM): Integer; stdcall;
  1105. begin
  1106. Boolean(Pointer(lpData)^) := True;
  1107. Result := 1;
  1108. end;
  1109. function FontExists(const FaceName: String): Boolean;
  1110. var
  1111. DC: HDC;
  1112. begin
  1113. Result := False;
  1114. DC := GetDC(0);
  1115. try
  1116. EnumFonts(DC, PChar(FaceName), @FontExistsCallback, @Result);
  1117. finally
  1118. ReleaseDC(0, DC);
  1119. end;
  1120. end;
  1121. function GetUILanguage: LANGID;
  1122. { Platform-independent version of GetUserDefaultUILanguage. May return 0 in
  1123. case of failure. }
  1124. var
  1125. GetUserDefaultUILanguage: function: LANGID; stdcall;
  1126. K: HKEY;
  1127. S: String;
  1128. E: Integer;
  1129. begin
  1130. GetUserDefaultUILanguage := GetProcAddress(GetModuleHandle(kernel32),
  1131. 'GetUserDefaultUILanguage');
  1132. if Assigned(GetUserDefaultUILanguage) then
  1133. Result := GetUserDefaultUILanguage
  1134. else begin
  1135. { GetUserDefaultUILanguage is available on Windows 2000, Me, and later so
  1136. should never get here }
  1137. if RegOpenKeyExView(rvDefault, HKEY_USERS, '.DEFAULT\Control Panel\International',
  1138. 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
  1139. RegQueryStringValue(K, 'Locale', S);
  1140. RegCloseKey(K);
  1141. end;
  1142. Val('$' + S, Result, E);
  1143. if E <> 0 then
  1144. Result := 0;
  1145. end;
  1146. end;
  1147. function RemoveAccelChar(const S: String): String;
  1148. var
  1149. I: Integer;
  1150. begin
  1151. Result := S;
  1152. I := 1;
  1153. while I <= Length(Result) do begin
  1154. if Result[I] = '&' then begin
  1155. System.Delete(Result, I, 1);
  1156. if I > Length(Result) then
  1157. Break;
  1158. end;
  1159. Inc(I, PathCharLength(Result, I));
  1160. end;
  1161. end;
  1162. function GetTextWidth(const DC: HDC; S: String; const Prefix: Boolean): Integer;
  1163. { Returns the width of the specified string using the font currently selected
  1164. into DC. If Prefix is True, it first removes "&" characters as necessary. }
  1165. var
  1166. Size: TSize;
  1167. begin
  1168. { This procedure is 10x faster than using DrawText with the DT_CALCRECT flag }
  1169. if Prefix then
  1170. S := RemoveAccelChar(S);
  1171. GetTextExtentPoint32(DC, PChar(S), Length(S), Size);
  1172. Result := Size.cx;
  1173. end;
  1174. function AddPeriod(const S: String): String;
  1175. begin
  1176. Result := S;
  1177. if (Result <> '') and (PathLastChar(Result)^ > '.') then
  1178. Result := Result + '.';
  1179. end;
  1180. function GetExceptMessage: String;
  1181. var
  1182. E: TObject;
  1183. begin
  1184. E := ExceptObject;
  1185. if E = nil then
  1186. Result := '[ExceptObject=nil]' { should never get here }
  1187. else if E is Exception then
  1188. Result := AddPeriod(Exception(E).Message) { usual case }
  1189. else
  1190. Result := E.ClassName; { shouldn't get here under normal circumstances }
  1191. end;
  1192. function GetPreferredUIFont: String;
  1193. { Gets the preferred UI font. Returns Microsoft Sans Serif, or MS Sans Serif
  1194. if it doesn't exist.
  1195. Microsoft Sans Serif (which is available on Windows 2000 and later) has two
  1196. advantages over MS Sans Serif:
  1197. 1) On Windows XP, it can display password dots in edit boxes.
  1198. 2) In my tests on Japanese XP, Microsoft Sans Serif can display Japanese
  1199. characters (MS Sans Serif cannot). }
  1200. begin
  1201. if FontExists('Microsoft Sans Serif') then
  1202. Result := 'Microsoft Sans Serif'
  1203. else
  1204. Result := 'MS Sans Serif';
  1205. end;
  1206. function IsWildcard(const Pattern: String): Boolean;
  1207. begin
  1208. Result := (Pos('*', Pattern) <> 0) or (Pos('?', Pattern) <> 0);
  1209. end;
  1210. function WildcardMatch(const Text, Pattern: PChar): Boolean;
  1211. { General-purpose wildcard matching function based on the widely used wildcat()
  1212. code by Rich $alz. In this implementation, however, the only supported
  1213. pattern matching characters are ? and *.
  1214. Note that this function uses Unix shell semantics -- e.g. a dot always
  1215. matches a dot (so a pattern of '*.*' won't match 'file'), and ? always
  1216. matches exactly 1 character (so '?????' won't match 'file').
  1217. Also note: The InternalWildcardMatch function can recursively call itself
  1218. for each non-consecutive * character in the pattern. With enough *
  1219. characters, the stack could overflow. So ideally the caller should impose a
  1220. limit on either the length of the pattern string or the number of *
  1221. characters in it. }
  1222. type
  1223. TWildcardMatchResult = (wmFalse, wmTrue, wmAbort);
  1224. function InternalWildcardMatch(T, P: PChar): TWildcardMatchResult;
  1225. begin
  1226. while P^ <> #0 do begin
  1227. if (T^ = #0) and (P^ <> '*') then begin
  1228. Result := wmAbort;
  1229. Exit;
  1230. end;
  1231. case P^ of
  1232. '?': ; { Match any character }
  1233. '*': begin
  1234. Inc(P);
  1235. while P^ = '*' do begin
  1236. { Consecutive stars act just like one }
  1237. Inc(P);
  1238. end;
  1239. if P^ = #0 then begin
  1240. { Trailing star matches everything }
  1241. Result := wmTrue;
  1242. Exit;
  1243. end;
  1244. while T^ <> #0 do begin
  1245. Result := InternalWildcardMatch(T, P);
  1246. if Result <> wmFalse then
  1247. Exit;
  1248. T := PathStrNextChar(T);
  1249. end;
  1250. Result := wmAbort;
  1251. Exit;
  1252. end;
  1253. else
  1254. if not PathCharCompare(T, P) then begin
  1255. Result := wmFalse;
  1256. Exit;
  1257. end;
  1258. end;
  1259. T := PathStrNextChar(T);
  1260. P := PathStrNextChar(P);
  1261. end;
  1262. if T^ = #0 then
  1263. Result := wmTrue
  1264. else
  1265. Result := wmFalse;
  1266. end;
  1267. begin
  1268. Result := (InternalWildcardMatch(Text, Pattern) = wmTrue);
  1269. end;
  1270. function IntMax(const A, B: Integer): Integer;
  1271. begin
  1272. if A > B then
  1273. Result := A
  1274. else
  1275. Result := B;
  1276. end;
  1277. function Win32ErrorString(ErrorCode: Integer): String;
  1278. { Like SysErrorMessage but also passes the FORMAT_MESSAGE_IGNORE_INSERTS flag
  1279. which allows the function to succeed on errors like 129 }
  1280. var
  1281. Len: Integer;
  1282. Buffer: array[0..1023] of Char;
  1283. begin
  1284. 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. var
  1430. SHAutoCompleteInitialized: Boolean;
  1431. SHAutoCompleteFunc: function(hwndEdit: HWND; dwFlags: dWord): LongInt; stdcall;
  1432. procedure TryEnableAutoCompleteFileSystem(Wnd: HWND);
  1433. const
  1434. SHACF_FILESYSTEM = $1;
  1435. var
  1436. M: HMODULE;
  1437. begin
  1438. if not SHAutoCompleteInitialized then begin
  1439. M := SafeLoadLibrary(AddBackslash(GetSystemDir) + 'shlwapi.dll',
  1440. SEM_NOOPENFILEERRORBOX);
  1441. if M <> 0 then
  1442. SHAutoCompleteFunc := GetProcAddress(M, 'SHAutoComplete');
  1443. SHAutoCompleteInitialized := True;
  1444. end;
  1445. if Assigned(SHAutoCompleteFunc) then
  1446. SHAutoCompleteFunc(Wnd, SHACF_FILESYSTEM);
  1447. end;
  1448. procedure CreateMutex(const MutexName: String);
  1449. const
  1450. SECURITY_DESCRIPTOR_REVISION = 1; { Win32 constant not defined in Delphi 3 }
  1451. var
  1452. SecurityDesc: TSecurityDescriptor;
  1453. SecurityAttr: TSecurityAttributes;
  1454. begin
  1455. { By default on Windows NT, created mutexes are accessible only by the user
  1456. running the process. We need our mutexes to be accessible to all users, so
  1457. that the mutex detection can work across user sessions in Windows XP. To
  1458. do this we use a security descriptor with a null DACL. }
  1459. InitializeSecurityDescriptor(@SecurityDesc, SECURITY_DESCRIPTOR_REVISION);
  1460. SetSecurityDescriptorDacl(@SecurityDesc, True, nil, False);
  1461. SecurityAttr.nLength := SizeOf(SecurityAttr);
  1462. SecurityAttr.lpSecurityDescriptor := @SecurityDesc;
  1463. SecurityAttr.bInheritHandle := False;
  1464. Windows.CreateMutex(@SecurityAttr, False, PChar(MutexName));
  1465. end;
  1466. { TOneShotTimer }
  1467. function TOneShotTimer.Expired: Boolean;
  1468. begin
  1469. Result := (TimeRemaining = 0);
  1470. end;
  1471. procedure TOneShotTimer.SleepUntilExpired;
  1472. var
  1473. Remaining: Cardinal;
  1474. begin
  1475. while True do begin
  1476. Remaining := TimeRemaining;
  1477. if Remaining = 0 then
  1478. Break;
  1479. Sleep(Remaining);
  1480. end;
  1481. end;
  1482. procedure TOneShotTimer.Start(const Timeout: Cardinal);
  1483. begin
  1484. FStartTick := GetTickCount;
  1485. FTimeout := Timeout;
  1486. FLastElapsed := 0;
  1487. end;
  1488. function TOneShotTimer.TimeElapsed: Cardinal;
  1489. var
  1490. Elapsed: Cardinal;
  1491. begin
  1492. Elapsed := GetTickCount - FStartTick;
  1493. if Elapsed > FLastElapsed then
  1494. FLastElapsed := Elapsed;
  1495. Result := FLastElapsed;
  1496. end;
  1497. function TOneShotTimer.TimeRemaining: Cardinal;
  1498. var
  1499. Elapsed: Cardinal;
  1500. begin
  1501. Elapsed := TimeElapsed;
  1502. if Elapsed < FTimeout then
  1503. Result := FTimeout - Elapsed
  1504. else
  1505. Result := 0;
  1506. end;
  1507. { TCreateProcessOutputReader }
  1508. constructor TCreateProcessOutputReader.Create(const ALogProc: TLogProc;
  1509. const ALogProcData: NativeInt; AMode: TOutputMode = omLog);
  1510. procedure CreatePipeAndSetHandleInformation(var Read, Write: THandle; SecurityAttr: TSecurityAttributes);
  1511. begin
  1512. { CreatePipe docs say no assumptions should be made about the output
  1513. parameter contents (the two handles) when it fails. So specify local
  1514. variables for the output parameters, and only copy the handles into
  1515. the "var" parameters when CreatePipe is successful. That way, if it
  1516. does fail, the "var" parameters will still have their original 0
  1517. values (which is important because the destructor closes all
  1518. non-zero handles). }
  1519. var TempReadPipe, TempWritePipe: THandle;
  1520. if not CreatePipe(TempReadPipe, TempWritePipe, @SecurityAttr, 0) then
  1521. raise Exception.CreateFmt('Output redirection error: CreatePipe failed (%d)', [GetLastError]);
  1522. Read := TempReadPipe;
  1523. Write := TempWritePipe;
  1524. if not SetHandleInformation(TempReadPipe, HANDLE_FLAG_INHERIT, 0) then
  1525. raise Exception.CreateFmt('Output redirection error: SetHandleInformation failed (%d)', [GetLastError]);
  1526. end;
  1527. begin
  1528. if not Assigned(ALogProc) then
  1529. raise Exception.Create('ALogProc is required');
  1530. if AMode = omCapture then begin
  1531. FCaptureOutList := TStringList.Create;
  1532. FCaptureErrList := TStringList.Create;
  1533. end;
  1534. FMode := AMode;
  1535. FLogProc := ALogProc;
  1536. FLogProcData := ALogProcData;
  1537. FNextLineIsFirstLine := True;
  1538. var SecurityAttributes: TSecurityAttributes;
  1539. SecurityAttributes.nLength := SizeOf(SecurityAttributes);
  1540. SecurityAttributes.bInheritHandle := True;
  1541. SecurityAttributes.lpSecurityDescriptor := nil;
  1542. var NulDevice := CreateFile('\\.\NUL', GENERIC_READ,
  1543. FILE_SHARE_READ or FILE_SHARE_WRITE, @SecurityAttributes,
  1544. OPEN_EXISTING, 0, 0);
  1545. { In case the NUL device is missing (which it inexplicably seems to
  1546. be for some users, per web search), don't treat it as a fatal
  1547. error. Just leave FStdInNulDevice at 0. It's not ideal, but the
  1548. child process likely won't even attempt to access stdin anyway. }
  1549. if NulDevice <> INVALID_HANDLE_VALUE then
  1550. FStdInNulDevice := NulDevice;
  1551. CreatePipeAndSetHandleInformation(FStdOut.PipeRead, FStdOut.PipeWrite, SecurityAttributes);
  1552. FStdOut.OkToRead := True;
  1553. FStdOut.CaptureList := FCaptureOutList;
  1554. if FMode = omCapture then begin
  1555. CreatePipeAndSetHandleInformation(FStdErr.PipeRead, FStdErr.PipeWrite, SecurityAttributes);
  1556. FStdErr.OkToRead := True;
  1557. FStdErr.CaptureList := FCaptureErrList;
  1558. end;
  1559. FMaxTotalBytesToRead := 10*1000*1000;
  1560. FMaxTotalLinesToRead := 1000*1000;
  1561. end;
  1562. destructor TCreateProcessOutputReader.Destroy;
  1563. begin
  1564. CloseAndClearHandle(FStdInNulDevice);
  1565. CloseAndClearHandle(FStdOut.PipeRead);
  1566. CloseAndClearHandle(FStdOut.PipeWrite);
  1567. CloseAndClearHandle(FStdErr.PipeRead);
  1568. CloseAndClearHandle(FStdErr.PipeWrite);
  1569. FCaptureOutList.Free;
  1570. FCaptureErrList.Free;
  1571. inherited;
  1572. end;
  1573. procedure TCreateProcessOutputReader.CloseAndClearHandle(var Handle: THandle);
  1574. begin
  1575. if Handle <> 0 then begin
  1576. CloseHandle(Handle);
  1577. Handle := 0;
  1578. end;
  1579. end;
  1580. procedure TCreateProcessOutputReader.HandleAndLogErrorFmt(const S: String; const Args: array of const);
  1581. begin
  1582. FLogProc('OutputReader: ' + Format(S, Args), True, False, FLogProcData);
  1583. if FMode = omCapture then
  1584. FCaptureError := True;
  1585. end;
  1586. procedure TCreateProcessOutputReader.UpdateStartupInfo(var StartupInfo: TStartupInfo);
  1587. begin
  1588. StartupInfo.dwFlags := StartupInfo.dwFlags or STARTF_USESTDHANDLES;
  1589. StartupInfo.hStdInput := FStdInNulDevice;
  1590. StartupInfo.hStdOutput := FStdOut.PipeWrite;
  1591. if FMode = omLog then
  1592. StartupInfo.hStdError := FStdOut.PipeWrite
  1593. else
  1594. StartupInfo.hStdError := FStdErr.PipeWrite;
  1595. end;
  1596. procedure TCreateProcessOutputReader.NotifyCreateProcessDone;
  1597. begin
  1598. CloseAndClearHandle(FStdInNulDevice);
  1599. CloseAndClearHandle(FStdOut.PipeWrite);
  1600. CloseAndClearHandle(FStdErr.PipeWrite);
  1601. end;
  1602. procedure TCreateProcessOutputReader.Read(const LastRead: Boolean);
  1603. function FindNewLine(const S: AnsiString; const LastRead: Boolean): Integer;
  1604. begin
  1605. { This will return the position of the first #13 or #10. If a #13 is at
  1606. the very end of the string it's only accepted if we are certain we can't
  1607. be looking at a split #13#10 because there will be no more reads }
  1608. var N := Length(S);
  1609. for var I := 1 to N do
  1610. if ((S[I] = #13) and ((I < N) or LastRead)) or
  1611. (S[I] = #10) then
  1612. Exit(I);
  1613. Result := 0;
  1614. end;
  1615. procedure LogLine(const CaptureList: TStringList; const S: AnsiString);
  1616. begin
  1617. var UTF8S := UTF8ToString(S);
  1618. if CaptureList <> nil then
  1619. CaptureList.Add(UTF8S)
  1620. else begin
  1621. FLogProc(UTF8S, False, FNextLineIsFirstLine, FLogProcData);
  1622. FNextLineIsFirstLine := False;
  1623. end;
  1624. end;
  1625. function SharedLimitReached: Boolean;
  1626. begin
  1627. Result := (FTotalBytesRead >= FMaxTotalBytesToRead) or
  1628. (FTotalLinesRead >= FMaxTotalLinesToRead);
  1629. end;
  1630. procedure DoRead(var Pipe: TCreateProcessOutputReaderPipe; const LastRead: Boolean);
  1631. begin
  1632. if Pipe.OKToRead then begin
  1633. if SharedLimitReached then begin
  1634. { The other pipe reached the shared limit which was handled and logged.
  1635. So don't read from this pipe but instead close it and exit silently. }
  1636. Pipe.OKToRead := False;
  1637. Pipe.Buffer := '';
  1638. CloseAndClearHandle(Pipe.PipeRead);
  1639. Exit;
  1640. end;
  1641. var TotalBytesAvail: DWORD;
  1642. Pipe.OKToRead := PeekNamedPipe(Pipe.PipeRead, nil, 0, nil, @TotalBytesAvail, nil);
  1643. if not Pipe.OKToRead then begin
  1644. var LastError := GetLastError;
  1645. if LastError <> ERROR_BROKEN_PIPE then begin
  1646. Pipe.Buffer := '';
  1647. HandleAndLogErrorFmt('PeekNamedPipe failed (%d).', [LastError]);
  1648. end;
  1649. end else if TotalBytesAvail > 0 then begin
  1650. { Don't read more than our read limit }
  1651. if TotalBytesAvail > FMaxTotalBytesToRead - FTotalBytesRead then
  1652. TotalBytesAvail := FMaxTotalBytesToRead - FTotalBytesRead;
  1653. { Append newly available data to the incomplete line we might already have }
  1654. var TotalBytesHave: DWORD := Length(Pipe.Buffer);
  1655. SetLength(Pipe.Buffer, TotalBytesHave+TotalBytesAvail);
  1656. var BytesRead: DWORD;
  1657. Pipe.OKToRead := ReadFile(Pipe.PipeRead, Pipe.Buffer[TotalBytesHave+1],
  1658. TotalBytesAvail, BytesRead, nil);
  1659. if not Pipe.OKToRead then begin
  1660. var LastError := GetLastError;
  1661. if LastError <> ERROR_BROKEN_PIPE then begin
  1662. Pipe.Buffer := '';
  1663. HandleAndLogErrorFmt('ReadFile failed (%d).', [LastError]);
  1664. end else begin
  1665. { Restore back to original size }
  1666. SetLength(Pipe.Buffer, TotalBytesHave);
  1667. end;
  1668. end else begin
  1669. { Correct length if less bytes were read than requested }
  1670. SetLength(Pipe.Buffer, TotalBytesHave+BytesRead);
  1671. { Check for completed lines thanks to the new data }
  1672. while FTotalLinesRead < FMaxTotalLinesToRead do begin
  1673. var P := FindNewLine(Pipe.Buffer, LastRead);
  1674. if P = 0 then
  1675. Break;
  1676. LogLine(Pipe.CaptureList, Copy(Pipe.Buffer, 1, P-1));
  1677. Inc(FTotalLinesRead);
  1678. if (Pipe.Buffer[P] = #13) and (P < Length(Pipe.Buffer)) and (Pipe.Buffer[P+1] = #10) then
  1679. Inc(P);
  1680. Delete(Pipe.Buffer, 1, P);
  1681. end;
  1682. Inc(FTotalBytesRead, BytesRead);
  1683. if SharedLimitReached then begin
  1684. { Read limit reached: break the pipe, throw away the incomplete line, and log an error }
  1685. Pipe.OKToRead := False;
  1686. Pipe.Buffer := '';
  1687. if FTotalBytesRead >= FMaxTotalBytesToRead then
  1688. HandleAndLogErrorFmt('Maximum output length (%d) reached, ignoring remainder.', [FMaxTotalBytesToRead])
  1689. else
  1690. HandleAndLogErrorFmt('Maximum output lines (%d) reached, ignoring remainder.', [FMaxTotalLinesToRead]);
  1691. end;
  1692. end;
  1693. end;
  1694. { Unblock the child process's write, and cause further writes to fail immediately }
  1695. if not Pipe.OkToRead then
  1696. CloseAndClearHandle(Pipe.PipeRead);
  1697. end;
  1698. if LastRead and (Pipe.Buffer <> '') then begin
  1699. var N := Length(Pipe.Buffer);
  1700. if Pipe.Buffer[N] = #13 then begin
  1701. { See FindNewLine: the buffer could end with a final #13 which needs to
  1702. be stripped still }
  1703. Delete(Pipe.Buffer, N, 1);
  1704. end;
  1705. LogLine(Pipe.CaptureList, Pipe.Buffer);
  1706. end;
  1707. end;
  1708. begin
  1709. DoRead(FStdOut, LastRead);
  1710. if FMode = omCapture then
  1711. DoRead(FStdErr, LastRead);
  1712. end;
  1713. end.