Shared.CommonFunc.pas 70 KB

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