2
0

Shared.CommonFunc.pas 63 KB

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