CmnFunc2.pas 51 KB

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