1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864 |
- unit Shared.CommonFunc;
- {
- Inno Setup
- Copyright (C) 1997-2025 Jordan Russell
- Portions by Martijn Laan
- For conditions of distribution and use, see LICENSE.TXT.
- Common non-VCL functions
- }
- {$B-,R-}
- interface
- uses
- Windows, SysUtils, Classes;
- const
- KEY_WOW64_64KEY = $0100;
- type
- TOneShotTimer = record
- private
- FLastElapsed: Cardinal;
- FStartTick: DWORD;
- FTimeout: Cardinal;
- public
- function Expired: Boolean;
- procedure SleepUntilExpired;
- procedure Start(const Timeout: Cardinal);
- function TimeElapsed: Cardinal;
- function TimeRemaining: Cardinal;
- end;
- TLogProc = procedure(const S: String; const Error, FirstLine: Boolean; const Data: NativeInt);
- TOutputMode = (omLog, omCapture);
- TCreateProcessOutputReaderPipe = record
- OKToRead: Boolean;
- PipeRead, PipeWrite: THandle;
- Buffer: AnsiString;
- CaptureList: TStringList;
- end;
- TCreateProcessOutputReader = class
- private
- FMaxTotalBytesToRead: Cardinal;
- FMaxTotalLinesToRead: Cardinal;
- FTotalBytesRead: Cardinal;
- FTotalLinesRead: Cardinal;
- FStdInNulDevice: THandle;
- FStdOut: TCreateProcessOutputReaderPipe;
- FStdErr: TCreateProcessOutputReaderPipe;
- FLogProc: TLogProc;
- FLogProcData: NativeInt;
- FNextLineIsFirstLine: Boolean;
- FMode: TOutputMode;
- FCaptureOutList: TStringList;
- FCaptureErrList: TStringList;
- FCaptureError: Boolean;
- procedure CloseAndClearHandle(var Handle: THandle);
- procedure HandleAndLogErrorFmt(const S: String; const Args: array of const);
- public
- constructor Create(const ALogProc: TLogProc; const ALogProcData: NativeInt; AMode: TOutputMode = omLog);
- destructor Destroy; override;
- procedure UpdateStartupInfo(var StartupInfo: TStartupInfo);
- procedure NotifyCreateProcessDone;
- procedure Read(const LastRead: Boolean);
- property CaptureOutList: TStringList read FCaptureOutList;
- property CaptureErrList: TStringList read FCaptureErrList;
- property CaptureError: Boolean read FCaptureError;
- end;
- TRegView = (rvDefault, rv32Bit, rv64Bit);
- const
- RegViews64Bit = [rv64Bit];
- function NewFileExists(const Name: String): Boolean;
- function DirExists(const Name: String): Boolean;
- function FileOrDirExists(const Name: String): Boolean;
- function IsDirectoryAndNotReparsePoint(const Name: String): Boolean;
- function GetIniString(const Section, Key: String; Default: String; const Filename: String): String;
- function GetIniInt(const Section, Key: String; const Default, Min, Max: Longint; const Filename: String): Longint;
- function GetIniBool(const Section, Key: String; const Default: Boolean; const Filename: String): Boolean;
- function IniKeyExists(const Section, Key, Filename: String): Boolean;
- function IsIniSectionEmpty(const Section, Filename: String): Boolean;
- function SetIniString(const Section, Key, Value, Filename: String): Boolean;
- function SetIniInt(const Section, Key: String; const Value: Longint; const Filename: String): Boolean;
- function SetIniBool(const Section, Key: String; const Value: Boolean; const Filename: String): Boolean;
- procedure DeleteIniEntry(const Section, Key, Filename: String);
- procedure DeleteIniSection(const Section, Filename: String);
- function GetEnv(const EnvVar: String): String;
- function GetCmdTail: String;
- function GetCmdTailEx(StartIndex: Integer): String;
- function NewParamCount: Integer;
- function NewParamStr(Index: Integer): string;
- function AddQuotes(const S: String): String;
- function RemoveQuotes(const S: String): String;
- function GetShortName(const LongName: String): String;
- function GetWinDir: String;
- function GetSystemWinDir: String;
- function GetSystemDir: String;
- function GetSysWow64Dir: String;
- function GetSysNativeDir(const IsWin64: Boolean): String;
- function GetTempDir: String;
- function StringChange(var S: String; const FromStr, ToStr: String): Integer;
- function StringChangeEx(var S: String; const FromStr, ToStr: String;
- const SupportDBCS: Boolean): Integer;
- function AdjustLength(var S: String; const Res: Cardinal): Boolean;
- function ConvertConstPercentStr(var S: String): Boolean;
- function ConvertPercentStr(var S: String): Boolean;
- function ConstPos(const Ch: Char; const S: String): Integer;
- function SkipPastConst(const S: String; const Start: Integer): Integer;
- function RegQueryStringValue(H: HKEY; Name: PChar; var ResultStr: String; AllowDWord: Boolean = False): Boolean;
- function RegQueryMultiStringValue(H: HKEY; Name: PChar; var ResultStr: String): Boolean;
- function RegValueExists(H: HKEY; Name: PChar): Boolean;
- function RegCreateKeyExView(const RegView: TRegView; hKey: HKEY; lpSubKey: PChar;
- Reserved: DWORD; lpClass: PChar; dwOptions: DWORD; samDesired: REGSAM;
- lpSecurityAttributes: PSecurityAttributes; var phkResult: HKEY;
- lpdwDisposition: PDWORD): Longint;
- function RegOpenKeyExView(const RegView: TRegView; hKey: HKEY; lpSubKey: PChar;
- ulOptions: DWORD; samDesired: REGSAM; var phkResult: HKEY): Longint;
- function RegDeleteKeyView(const RegView: TRegView; const Key: HKEY; const Name: PChar): Longint;
- function RegDeleteKeyIncludingSubkeys(const RegView: TRegView; const Key: HKEY; const Name: PChar): Longint;
- function RegDeleteKeyIfEmpty(const RegView: TRegView; const RootKey: HKEY; const SubkeyName: PChar): Longint;
- function GetShellFolderPath(const FolderID: Integer): String;
- function GetCurrentUserSid: String;
- function IsAdminLoggedOn: Boolean;
- function IsPowerUserLoggedOn: Boolean;
- function IsMultiByteString(const S: AnsiString): Boolean;
- function FontExists(const FaceName: String): Boolean;
- function GetUILanguage: LANGID;
- function RemoveAccelChar(const S: String): String;
- function GetTextWidth(const DC: HDC; S: String; const Prefix: Boolean): Integer;
- function AddPeriod(const S: String): String;
- function GetExceptMessage: String;
- function GetPreferredUIFont: String;
- function IsWildcard(const Pattern: String): Boolean;
- function WildcardMatch(const Text, Pattern: PChar): Boolean;
- function IntMax(const A, B: Integer): Integer;
- function Win32ErrorString(ErrorCode: Integer): String;
- function DeleteDirTree(const Dir: String): Boolean;
- function SetNTFSCompression(const FileOrDir: String; Compress: Boolean): Boolean;
- procedure AddToWindowMessageFilterEx(const Wnd: HWND; const Msg: UINT);
- function ShutdownBlockReasonCreate(Wnd: HWND; const Reason: String): Boolean;
- function ShutdownBlockReasonDestroy(Wnd: HWND): Boolean;
- function TryStrToBoolean(const S: String; var BoolResult: Boolean): Boolean;
- procedure WaitMessageWithTimeout(const Milliseconds: DWORD);
- function MoveFileReplace(const ExistingFileName, NewFileName: String): Boolean;
- procedure TryEnableAutoCompleteFileSystem(Wnd: HWND);
- procedure CreateMutex(const MutexName: String);
- implementation
- uses
- PathFunc;
- { Avoid including Variants (via ActiveX and ShlObj) in SetupLdr (SetupLdr uses CmnFunc2), saving 26 KB. }
- const
- shell32 = 'shell32.dll';
- type
- PSHItemID = ^TSHItemID;
- _SHITEMID = record
- cb: Word; { Size of the ID (including cb itself) }
- abID: array[0..0] of Byte; { The item ID (variable length) }
- end;
- TSHItemID = _SHITEMID;
- SHITEMID = _SHITEMID;
- PItemIDList = ^TItemIDList;
- _ITEMIDLIST = record
- mkid: TSHItemID;
- end;
- TItemIDList = _ITEMIDLIST;
- ITEMIDLIST = _ITEMIDLIST;
- IMalloc = interface(IUnknown)
- ['{00000002-0000-0000-C000-000000000046}']
- function Alloc(cb: Longint): Pointer; stdcall;
- function Realloc(pv: Pointer; cb: Longint): Pointer; stdcall;
- procedure Free(pv: Pointer); stdcall;
- function GetSize(pv: Pointer): Longint; stdcall;
- function DidAlloc(pv: Pointer): Integer; stdcall;
- procedure HeapMinimize; stdcall;
- end;
- function SHGetMalloc(var ppMalloc: IMalloc): HResult; stdcall; external shell32 name 'SHGetMalloc';
- function SHGetSpecialFolderLocation(hwndOwner: HWND; nFolder: Integer;
- var ppidl: PItemIDList): HResult; stdcall; external shell32 name 'SHGetSpecialFolderLocation';
- function SHGetPathFromIDList(pidl: PItemIDList; pszPath: PChar): BOOL; stdcall;
- external shell32 name 'SHGetPathFromIDListW';
- function InternalGetFileAttr(const Name: String): DWORD;
- begin
- Result := GetFileAttributes(PChar(RemoveBackslashUnlessRoot(Name)));
- end;
- function NewFileExists(const Name: String): Boolean;
- { Returns True if the specified file exists.
- This function is better than Delphi's FileExists function because it works
- on files in directories that don't have "list" permission. There is, however,
- one other difference: FileExists allows wildcards, but this function does
- not. }
- begin
- var Attr := GetFileAttributes(PChar(Name));
- Result := (Attr <> INVALID_FILE_ATTRIBUTES) and (Attr and faDirectory = 0);
- end;
- function DirExists(const Name: String): Boolean;
- { Returns True if the specified directory name exists. The specified name
- may include a trailing backslash.
- NOTE: Delphi's FileCtrl unit has a similar function called DirectoryExists.
- However, the implementation is different between Delphi 1 and 2. (Delphi 1
- does not count hidden or system directories as existing.) }
- begin
- var Attr := InternalGetFileAttr(Name);
- Result := (Attr <> INVALID_FILE_ATTRIBUTES) and (Attr and faDirectory <> 0);
- end;
- function FileOrDirExists(const Name: String): Boolean;
- { Returns True if the specified directory or file name exists. The specified
- name may include a trailing backslash. }
- begin
- Result := InternalGetFileAttr(Name) <> INVALID_FILE_ATTRIBUTES;
- end;
- function IsDirectoryAndNotReparsePoint(const Name: String): Boolean;
- { Returns True if the specified directory exists and is NOT a reparse point. }
- const
- FILE_ATTRIBUTE_REPARSE_POINT = $00000400;
- var
- Attr: DWORD;
- begin
- Attr := GetFileAttributes(PChar(Name));
- Result := (Attr <> INVALID_FILE_ATTRIBUTES) and
- (Attr and FILE_ATTRIBUTE_DIRECTORY <> 0) and
- (Attr and FILE_ATTRIBUTE_REPARSE_POINT = 0);
- end;
- function GetIniString(const Section, Key: String; Default: String;
- const Filename: String): String;
- var
- BufSize, Len: Integer;
- begin
- { On Windows 9x, Get*ProfileString can modify the lpDefault parameter, so
- make sure it's unique and not read-only }
- UniqueString(Default);
- BufSize := 256;
- while True do begin
- SetString(Result, nil, BufSize);
- if Filename <> '' then
- Len := GetPrivateProfileString(PChar(Section), PChar(Key), PChar(Default),
- @Result[1], BufSize, PChar(Filename))
- else
- Len := GetProfileString(PChar(Section), PChar(Key), PChar(Default),
- @Result[1], BufSize);
- { Work around bug present on Windows NT/2000 (not 95): When lpDefault is
- too long to fit in the buffer, nSize is returned (null terminator
- counted) instead of nSize-1 (what it's supposed to return). So don't
- trust the returned length; calculate it ourself.
- Note: This also ensures the string can never include embedded nulls. }
- if Len <> 0 then
- Len := StrLen(PChar(Result));
- { Break if the string fits, or if it's apparently 64 KB or longer.
- No point in increasing buffer size past 64 KB because the length
- returned by Windows 2000 seems to be mod 65536. And Windows 95 returns
- 0 on values longer than ~32 KB.
- Note: The docs say the function returns "nSize minus one" if the buffer
- is too small, but I'm willing to bet it can be "minus two" if the last
- character is double-byte. Let's just be extremely paranoid and check for
- BufSize-8. }
- if (Len < BufSize-8) or (BufSize >= 65536) then begin
- SetLength(Result, Len);
- Break;
- end;
- { Otherwise double the buffer size and try again }
- BufSize := BufSize * 2;
- end;
- end;
- function GetIniInt(const Section, Key: String;
- const Default, Min, Max: Longint; const Filename: String): Longint;
- { Reads a Longint from an INI file. If the Longint read is not between Min/Max
- then it returns Default. If Min=Max then Min/Max are ignored }
- var
- S: String;
- E: Integer;
- begin
- S := GetIniString(Section, Key, '', Filename);
- if S = '' then
- Result := Default
- else begin
- Val(S, Result, E);
- if (E <> 0) or ((Min <> Max) and ((Result < Min) or (Result > Max))) then
- Result := Default;
- end;
- end;
- function GetIniBool(const Section, Key: String; const Default: Boolean;
- const Filename: String): Boolean;
- begin
- Result := GetIniInt(Section, Key, Ord(Default), 0, 0, Filename) <> 0;
- end;
- function IniKeyExists(const Section, Key, Filename: String): Boolean;
- function Equals(const Default: PChar): Boolean;
- var
- Test: array[0..7] of Char;
- begin
- Test[0] := #0;
- if Filename <> '' then
- GetPrivateProfileString(PChar(Section), PChar(Key), Default,
- Test, SizeOf(Test) div SizeOf(Test[0]), PChar(Filename))
- else
- GetProfileString(PChar(Section), PChar(Key), Default,
- Test, SizeOf(Test) div SizeOf(Test[0]));
- Result := lstrcmp(Test, Default) = 0;
- end;
- begin
- { If the key does not exist, a default string is returned both times. }
- Result := not Equals('x1234x') or not Equals('x5678x'); { <- don't change }
- end;
- function IsIniSectionEmpty(const Section, Filename: String): Boolean;
- var
- Test: array[0..255] of Char;
- begin
- Test[0] := #0;
- if Filename <> '' then
- GetPrivateProfileString(PChar(Section), nil, '', Test,
- SizeOf(Test) div SizeOf(Test[0]), PChar(Filename))
- else
- GetProfileString(PChar(Section), nil, '', Test,
- SizeOf(Test) div SizeOf(Test[0]));
- Result := Test[0] = #0;
- end;
- function SetIniString(const Section, Key, Value, Filename: String): Boolean;
- begin
- if Filename <> '' then
- Result := WritePrivateProfileString(PChar(Section), PChar(Key),
- PChar(Value), PChar(Filename))
- else
- Result := WriteProfileString(PChar(Section), PChar(Key),
- PChar(Value));
- end;
- function SetIniInt(const Section, Key: String; const Value: Longint;
- const Filename: String): Boolean;
- begin
- Result := SetIniString(Section, Key, IntToStr(Value), Filename);
- end;
- function SetIniBool(const Section, Key: String; const Value: Boolean;
- const Filename: String): Boolean;
- begin
- Result := SetIniInt(Section, Key, Ord(Value), Filename);
- end;
- procedure DeleteIniEntry(const Section, Key, Filename: String);
- begin
- if Filename <> '' then
- WritePrivateProfileString(PChar(Section), PChar(Key),
- nil, PChar(Filename))
- else
- WriteProfileString(PChar(Section), PChar(Key),
- nil);
- end;
- procedure DeleteIniSection(const Section, Filename: String);
- begin
- if Filename <> '' then
- WritePrivateProfileString(PChar(Section), nil, nil,
- PChar(Filename))
- else
- WriteProfileString(PChar(Section), nil, nil);
- end;
- function GetEnv(const EnvVar: String): String;
- { Gets the value of the specified environment variable. (Just like TP's GetEnv) }
- var
- Res: DWORD;
- begin
- SetLength(Result, 255);
- repeat
- Res := GetEnvironmentVariable(PChar(EnvVar), PChar(Result), Length(Result));
- if Res = 0 then begin
- Result := '';
- Break;
- end;
- until AdjustLength(Result, Res);
- end;
- function GetParamStr(const P: PChar; var Param: String): PChar;
- function Extract(P: PChar; const Buffer: PChar; var Len: Integer): PChar;
- var
- InQuote: Boolean;
- begin
- Len := 0;
- InQuote := False;
- while (P^ <> #0) and ((P^ > ' ') or InQuote) do begin
- if P^ = '"' then
- InQuote := not InQuote
- else begin
- if Assigned(Buffer) then
- Buffer[Len] := P^;
- Inc(Len);
- end;
- Inc(P);
- end;
- Result := P;
- end;
- var
- Len: Integer;
- Buffer: String;
- begin
- Extract(P, nil, Len);
- SetString(Buffer, nil, Len);
- Result := Extract(P, @Buffer[1], Len);
- Param := Buffer;
- while (Result^ <> #0) and (Result^ <= ' ') do
- Inc(Result);
- end;
- function GetCmdTail: String;
- { Returns all command line parameters passed to the process as a single
- string. }
- var
- S: String;
- begin
- Result := GetParamStr(GetCommandLine, S);
- end;
- function GetCmdTailEx(StartIndex: Integer): String;
- { Returns all command line parameters passed to the process as a single
- string, starting with StartIndex (one-based). }
- var
- P: PChar;
- S: String;
- begin
- P := GetParamStr(GetCommandLine, S);
- while (StartIndex > 1) and (P^ <> #0) do begin
- P := GetParamStr(P, S);
- Dec(StartIndex);
- end;
- Result := P;
- end;
- function NewParamCount: Integer;
- var
- P: PChar;
- S: String;
- begin
- P := GetParamStr(GetCommandLine, S);
- Result := 0;
- while P^ <> #0 do begin
- Inc(Result);
- P := GetParamStr(P, S);
- end;
- end;
- function NewParamStr(Index: Integer): string;
- { Returns the Indexth command line parameter, or an empty string if Index is
- out of range.
- Differences from Delphi's ParamStr:
- - No limits on parameter length
- - Doesn't ignore empty parameters ("")
- - Handles the empty argv[0] case like MSVC: if GetCommandLine() returns
- " a b" then NewParamStr(1) should return "a", not "b" }
- var
- Buffer: array[0..MAX_PATH-1] of Char;
- S: String;
- P: PChar;
- begin
- if Index = 0 then begin
- SetString(Result, Buffer, GetModuleFileName(0, Buffer, SizeOf(Buffer) div SizeOf(Buffer[0])));
- end
- else begin
- P := GetCommandLine;
- while True do begin
- if P^ = #0 then begin
- S := '';
- Break;
- end;
- P := GetParamStr(P, S);
- if Index = 0 then Break;
- Dec(Index);
- end;
- Result := S;
- end;
- end;
- function AddQuotes(const S: String): String;
- { Adds a quote (") character to the left and right sides of the string if
- the string contains a space and it didn't have quotes already. This is
- primarily used when spawning another process with a long filename as one of
- the parameters. }
- begin
- Result := Trim(S);
- if (PathPos(' ', Result) <> 0) and
- ((Result[1] <> '"') or (PathLastChar(Result)^ <> '"')) then
- Result := '"' + Result + '"';
- end;
- function RemoveQuotes(const S: String): String;
- { Opposite of AddQuotes; removes any quotes around the string. }
- begin
- Result := S;
- while (Result <> '') and (Result[1] = '"') do
- Delete(Result, 1, 1);
- while (Result <> '') and (PathLastChar(Result)^ = '"') do
- SetLength(Result, Length(Result)-1);
- end;
- function ConvertPercentStr(var S: String): Boolean;
- { Expands all %-encoded characters in the string (see RFC 2396). Returns True
- if all were successfully expanded. }
- var
- I, C, E: Integer;
- N: String;
- begin
- Result := True;
- I := 1;
- while I <= Length(S) do begin
- if S[I] = '%' then begin
- N := Copy(S, I, 3);
- if Length(N) <> 3 then begin
- Result := False;
- Break;
- end;
- N[1] := '$';
- Val(N, C, E);
- if E <> 0 then begin
- Result := False;
- Break;
- end;
- { delete the two numbers following '%', and replace '%' with the character }
- Delete(S, I+1, 2);
- S[I] := Chr(C);
- end;
- Inc(I);
- end;
- end;
- function SkipPastConst(const S: String; const Start: Integer): Integer;
- { Returns the character index following the Inno Setup constant embedded
- into the string S at index Start.
- If the constant is not closed (missing a closing brace), it returns zero. }
- var
- L, BraceLevel, LastOpenBrace: Integer;
- begin
- Result := Start;
- L := Length(S);
- if Result < L then begin
- Inc(Result);
- if S[Result] = '{' then begin
- Inc(Result);
- Exit;
- end
- else begin
- BraceLevel := 1;
- LastOpenBrace := -1;
- while Result <= L do begin
- case S[Result] of
- '{': begin
- if LastOpenBrace <> Result-1 then begin
- Inc(BraceLevel);
- LastOpenBrace := Result;
- end
- else
- { Skip over '{{' when in an embedded constant }
- Dec(BraceLevel);
- end;
- '}': begin
- Dec(BraceLevel);
- if BraceLevel = 0 then begin
- Inc(Result);
- Exit;
- end;
- end;
- end;
- Inc(Result);
- end;
- end;
- end;
- Result := 0;
- end;
- function ConvertConstPercentStr(var S: String): Boolean;
- { Same as ConvertPercentStr, but is designed to ignore embedded Inno Setup
- constants. Any '%' characters between braces are not translated. Two
- consecutive braces are ignored. }
- var
- I, C, E: Integer;
- N: String;
- begin
- Result := True;
- I := 1;
- while I <= Length(S) do begin
- case S[I] of
- '{': begin
- I := SkipPastConst(S, I);
- if I = 0 then begin
- Result := False;
- Break;
- end;
- Dec(I); { ...since there's an Inc below }
- end;
- '%': begin
- N := Copy(S, I, 3);
- if Length(N) <> 3 then begin
- Result := False;
- Break;
- end;
- N[1] := '$';
- Val(N, C, E);
- if E <> 0 then begin
- Result := False;
- Break;
- end;
- { delete the two numbers following '%', and replace '%' with the character }
- Delete(S, I+1, 2);
- S[I] := Chr(C);
- end;
- end;
- Inc(I);
- end;
- end;
- function ConstPos(const Ch: Char; const S: String): Integer;
- { Like the standard Pos function, but skips over any Inno Setup constants
- embedded in S }
- var
- I, L: Integer;
- begin
- Result := 0;
- I := 1;
- L := Length(S);
- while I <= L do begin
- if S[I] = Ch then begin
- Result := I;
- Break;
- end
- else if S[I] = '{' then begin
- I := SkipPastConst(S, I);
- if I = 0 then
- Break;
- end
- else
- Inc(I);
- end;
- end;
- function GetShortName(const LongName: String): String;
- { Gets the short version of the specified long filename. If the file does not
- exist, or some other error occurs, it returns LongName. }
- var
- Res: DWORD;
- begin
- SetLength(Result, MAX_PATH);
- repeat
- Res := GetShortPathName(PChar(LongName), PChar(Result), Length(Result));
- if Res = 0 then begin
- Result := LongName;
- Break;
- end;
- until AdjustLength(Result, Res);
- end;
- function GetWinDir: String;
- { Returns fully qualified path of the Windows directory. Only includes a
- trailing backslash if the Windows directory is the root directory. }
- var
- Buf: array[0..MAX_PATH-1] of Char;
- begin
- GetWindowsDirectory(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
- Result := StrPas(Buf);
- end;
- function GetSystemWindowsDirectoryW(lpBuffer: LPWSTR; uSize: UINT): UINT; stdcall; external kernel32;
- function GetSystemWinDir: String;
- { Like get GetWinDir but uses GetSystemWindowsDirectory instead of
- GetWindowsDirectory: With Terminal Services, the GetSystemWindowsDirectory
- function retrieves the path of the system Windows directory, while the
- GetWindowsDirectory function retrieves the path of a Windows directory that is
- private for each user. On a single-user system, GetSystemWindowsDirectory is
- the same as GetWindowsDirectory. }
- var
- Buf: array[0..MAX_PATH-1] of Char;
- begin
- GetSystemWindowsDirectoryW(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
- Result := StrPas(Buf);
- end;
- function GetSystemDir: String;
- { Returns fully qualified path of the Windows System directory. Only includes a
- trailing backslash if the Windows System directory is the root directory. }
- var
- Buf: array[0..MAX_PATH-1] of Char;
- begin
- GetSystemDirectory(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
- Result := StrPas(Buf);
- end;
- function GetSysWow64Dir: String;
- { Returns fully qualified path of the SysWow64 directory on 64-bit Windows.
- Returns '' if there is no SysWow64 directory (e.g. running 32-bit Windows). }
- var
- GetSystemWow64DirectoryFunc: function(
- lpBuffer: PWideChar; uSize: UINT): UINT; stdcall;
- Res: Integer;
- Buf: array[0..MAX_PATH] of Char;
- begin
- Result := '';
- GetSystemWow64DirectoryFunc := GetProcAddress(GetModuleHandle(kernel32),
- 'GetSystemWow64DirectoryW');
- if Assigned(GetSystemWow64DirectoryFunc) then begin
- Res := GetSystemWow64DirectoryFunc(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
- if (Res > 0) and (Res < SizeOf(Buf) div SizeOf(Buf[0])) then
- Result := Buf;
- end;
- end;
- function GetSysNativeDir(const IsWin64: Boolean): String;
- { Returns the special Sysnative alias, without trailing backslash.
- Returns '' if there is no Sysnative alias. }
- begin
- { From MSDN: 32-bit applications can access the native system directory by
- substituting %windir%\Sysnative for %windir%\System32. WOW64 recognizes
- Sysnative as a special alias used to indicate that the file system should
- not redirect the access. }
- if IsWin64 then
- { Note: Avoiding GetWinDir here as that might not return the real Windows
- directory under Terminal Services }
- Result := PathExpand(AddBackslash(GetSystemDir) + '..\Sysnative') { Do not localize }
- else
- Result := '';
- end;
- function GetTempDir: String;
- { Returns fully qualified path of the temporary directory, with trailing
- backslash. }
- var
- GetTempPathFunc: function(nBufferLength: DWORD; lpBuffer: LPWSTR): DWORD; stdcall;
- Buf: array[0..MAX_PATH] of Char;
- begin
- { When available, GetTempPath2 is preferred as it returns a private
- directory (typically C:\Windows\SystemTemp) when running as SYSTEM }
- GetTempPathFunc := GetProcAddress(GetModuleHandle(kernel32),
- PAnsiChar('GetTempPath2W'));
- if not Assigned(GetTempPathFunc) then
- GetTempPathFunc := GetTempPathW;
- const Res = GetTempPathFunc(SizeOf(Buf) div SizeOf(Buf[0]), Buf);
- if (Res > 0) and (Res < SizeOf(Buf) div SizeOf(Buf[0])) then begin
- { The docs say the returned path is fully qualified and ends with a
- backslash, but let's be really sure! }
- Result := AddBackslash(PathExpand(Buf));
- Exit;
- end;
- { We don't expect GetTempPath to ever fail or claim a larger buffer is
- needed (docs say maximum possible return value is MAX_PATH+1), but if it
- does, raise an exception as this function has no return value for failure }
- raise Exception.CreateFmt('GetTempDir: GetTempPath failed (%u, %u)',
- [Res, GetLastError]);
- end;
- function StringChangeEx(var S: String; const FromStr, ToStr: String;
- const SupportDBCS: Boolean): Integer;
- { Changes all occurrences in S of FromStr to ToStr. If SupportDBCS is True
- (recommended), double-byte character sequences in S are recognized and
- handled properly. Otherwise, the function behaves in a binary-safe manner.
- Returns the number of times FromStr was matched and changed. }
- var
- FromStrLen, I, EndPos, J: Integer;
- IsMatch: Boolean;
- label 1;
- begin
- Result := 0;
- if FromStr = '' then Exit;
- FromStrLen := Length(FromStr);
- I := 1;
- 1:EndPos := Length(S) - FromStrLen + 1;
- while I <= EndPos do begin
- IsMatch := True;
- J := 0;
- while J < FromStrLen do begin
- if S[J+I] <> FromStr[J+1] then begin
- IsMatch := False;
- Break;
- end;
- Inc(J);
- end;
- if IsMatch then begin
- Inc(Result);
- Delete(S, I, FromStrLen);
- Insert(ToStr, S, I);
- Inc(I, Length(ToStr));
- goto 1;
- end;
- if SupportDBCS then
- Inc(I, PathCharLength(S, I))
- else
- Inc(I);
- end;
- end;
- function StringChange(var S: String; const FromStr, ToStr: String): Integer;
- { Same as calling StringChangeEx with SupportDBCS=False }
- begin
- Result := StringChangeEx(S, FromStr, ToStr, False);
- end;
- function AdjustLength(var S: String; const Res: Cardinal): Boolean;
- { Returns True if successful. Returns False if buffer wasn't large enough,
- and called AdjustLength to resize it. }
- begin
- Result := Integer(Res) < Length(S);
- SetLength(S, Res);
- end;
- function InternalRegQueryStringValue(H: HKEY; Name: PChar; var ResultStr: String;
- Type1, Type2, Type3: DWORD): Boolean;
- var
- Typ, Size: DWORD;
- Len: Integer;
- S: String;
- ErrorCode: Longint;
- label 1;
- begin
- Result := False;
- 1:Size := 0;
- if (RegQueryValueEx(H, Name, nil, @Typ, nil, @Size) = ERROR_SUCCESS) and
- ((Typ = Type1) or (Typ = Type2) or ((Type3 <> REG_NONE) and (Typ = Type3))) then begin
- if Typ = REG_DWORD then begin
- var Data: DWORD;
- Size := SizeOf(Data);
- if (RegQueryValueEx(H, Name, nil, @Typ, @Data, @Size) = ERROR_SUCCESS) and
- (Typ = REG_DWORD) and (Size = Sizeof(Data)) then begin
- ResultStr := Data.ToString;
- Result := True;
- end;
- end else if Size = 0 then begin
- { It's an empty string with no null terminator.
- (Must handle those here since we can't pass a nil lpData pointer on
- the second RegQueryValueEx call.) }
- ResultStr := '';
- Result := True;
- end
- else begin
- { Paranoia: Impose reasonable upper limit on Size to avoid potential
- integer overflows below }
- if Cardinal(Size) >= Cardinal($70000000) then
- OutOfMemoryError;
- { Note: If Size isn't a multiple of SizeOf(S[1]), we have to round up
- here so that RegQueryValueEx doesn't overflow the buffer }
- Len := (Size + (SizeOf(S[1]) - 1)) div SizeOf(S[1]);
- SetString(S, nil, Len);
- ErrorCode := RegQueryValueEx(H, Name, nil, @Typ, @S[1], @Size);
- if ErrorCode = ERROR_MORE_DATA then begin
- { The data must've increased in size since the first RegQueryValueEx
- call. Start over. }
- goto 1;
- end;
- if (ErrorCode = ERROR_SUCCESS) and
- ((Typ = Type1) or (Typ = Type2) or (Typ = Type3)) then begin
- { If Size isn't a multiple of SizeOf(S[1]), we disregard the partial
- character, like RegGetValue }
- Len := Size div SizeOf(S[1]);
- { Remove any null terminators from the end and trim the string to the
- returned length.
- Note: We *should* find 1 null terminator, but it's possible for
- there to be more or none if the value was written that way. }
- while (Len <> 0) and (S[Len] = #0) do
- Dec(Len);
- { In a REG_MULTI_SZ value, each individual string is null-terminated,
- so add 1 null (back) to the end, unless there are no strings (Len=0) }
- if (Typ = REG_MULTI_SZ) and (Len <> 0) then
- Inc(Len);
- SetLength(S, Len);
- if (Typ = REG_MULTI_SZ) and (Len <> 0) then
- S[Len] := #0;
- ResultStr := S;
- Result := True;
- end;
- end;
- end;
- end;
- function RegQueryStringValue(H: HKEY; Name: PChar; var ResultStr: String; AllowDWord: Boolean): Boolean;
- { Queries the specified REG_SZ or REG_EXPAND_SZ registry key/value, and returns
- the value in ResultStr. Returns True if successful. When False is returned,
- ResultStr is unmodified. Optionally supports REG_DWORD. }
- begin
- var Type3: DWORD;
- if AllowDWord then
- Type3 := REG_DWORD
- else
- Type3 := REG_NONE;
- Result := InternalRegQueryStringValue(H, Name, ResultStr, REG_SZ,
- REG_EXPAND_SZ, Type3);
- end;
- function RegQueryMultiStringValue(H: HKEY; Name: PChar; var ResultStr: String): Boolean;
- { Queries the specified REG_MULTI_SZ registry key/value, and returns the value
- in ResultStr. Returns True if successful. When False is returned, ResultStr
- is unmodified. }
- begin
- Result := InternalRegQueryStringValue(H, Name, ResultStr, REG_MULTI_SZ,
- REG_MULTI_SZ, REG_NONE);
- end;
- function RegValueExists(H: HKEY; Name: PChar): Boolean;
- { Returns True if the specified value exists. Requires KEY_QUERY_VALUE access
- to the key. }
- begin
- Result := RegQueryValueEx(H, Name, nil, nil, nil, nil) = ERROR_SUCCESS;
- end;
- function RegCreateKeyExView(const RegView: TRegView; hKey: HKEY; lpSubKey: PChar;
- Reserved: DWORD; lpClass: PChar; dwOptions: DWORD; samDesired: REGSAM;
- lpSecurityAttributes: PSecurityAttributes; var phkResult: HKEY;
- lpdwDisposition: PDWORD): Longint;
- begin
- if RegView = rv64Bit then
- samDesired := samDesired or KEY_WOW64_64KEY;
- Result := RegCreateKeyEx(hKey, lpSubKey, Reserved, lpClass, dwOptions,
- samDesired, lpSecurityAttributes, phkResult, lpdwDisposition);
- end;
- function RegOpenKeyExView(const RegView: TRegView; hKey: HKEY; lpSubKey: PChar;
- ulOptions: DWORD; samDesired: REGSAM; var phkResult: HKEY): Longint;
- begin
- if RegView = rv64Bit then
- samDesired := samDesired or KEY_WOW64_64KEY;
- Result := RegOpenKeyEx(hKey, lpSubKey, ulOptions, samDesired, phkResult);
- end;
- var
- RegDeleteKeyExFunc: function(hKey: HKEY;
- lpSubKey: PWideChar; samDesired: REGSAM; Reserved: DWORD): Longint; stdcall;
- function RegDeleteKeyView(const RegView: TRegView; const Key: HKEY;
- const Name: PChar): Longint;
- begin
- if RegView <> rv64Bit then
- Result := RegDeleteKey(Key, Name)
- else begin
- if @RegDeleteKeyExFunc = nil then
- RegDeleteKeyExFunc := GetProcAddress(GetModuleHandle(advapi32),
- 'RegDeleteKeyExW');
- if Assigned(RegDeleteKeyExFunc) then
- Result := RegDeleteKeyExFunc(Key, Name, KEY_WOW64_64KEY, 0)
- else
- Result := ERROR_PROC_NOT_FOUND;
- end;
- end;
- function RegDeleteKeyIncludingSubkeys(const RegView: TRegView; const Key: HKEY;
- const Name: PChar): Longint;
- { Deletes the specified key and all subkeys.
- Returns ERROR_SUCCESS if the key was successful deleted. }
- var
- H: HKEY;
- KeyName: String;
- I, KeyNameCount: DWORD;
- ErrorCode: Longint;
- begin
- if (Name = nil) or (Name[0] = #0) then begin
- Result := ERROR_INVALID_PARAMETER;
- Exit;
- end;
- if RegOpenKeyExView(RegView, Key, Name, 0, KEY_ENUMERATE_SUB_KEYS, H) = ERROR_SUCCESS then begin
- try
- SetString(KeyName, nil, 256);
- I := 0;
- while True do begin
- KeyNameCount := Length(KeyName);
- ErrorCode := RegEnumKeyEx(H, I, @KeyName[1], KeyNameCount, nil, nil, nil, nil);
- if ErrorCode = ERROR_MORE_DATA then begin
- { Double the size of the buffer and try again }
- if Length(KeyName) >= 65536 then begin
- { Sanity check: If we tried a 64 KB buffer and it's still saying
- there's more data, something must be seriously wrong. Bail. }
- Break;
- end;
- SetString(KeyName, nil, Length(KeyName) * 2);
- Continue;
- end;
- if ErrorCode <> ERROR_SUCCESS then
- Break;
- if RegDeleteKeyIncludingSubkeys(RegView, H, PChar(KeyName)) <> ERROR_SUCCESS then
- Inc(I);
- end;
- finally
- RegCloseKey(H);
- end;
- end;
- Result := RegDeleteKeyView(RegView, Key, Name);
- end;
- function RegDeleteKeyIfEmpty(const RegView: TRegView; const RootKey: HKEY;
- const SubkeyName: PChar): Longint;
- { Deletes the specified subkey if it has no subkeys or values.
- Returns ERROR_SUCCESS if the key was successful deleted, ERROR_DIR_NOT_EMPTY
- if it was not deleted because it contained subkeys or values, or possibly
- some other Win32 error code. }
- var
- K: HKEY;
- NumSubkeys, NumValues: DWORD;
- begin
- Result := RegOpenKeyExView(RegView, RootKey, SubkeyName, 0, KEY_QUERY_VALUE, K);
- if Result <> ERROR_SUCCESS then
- Exit;
- Result := RegQueryInfoKey(K, nil, nil, nil, @NumSubkeys, nil, nil,
- @NumValues, nil, nil, nil, nil);
- RegCloseKey(K);
- if Result <> ERROR_SUCCESS then
- Exit;
- if (NumSubkeys = 0) and (NumValues = 0) then
- Result := RegDeleteKeyView(RegView, RootKey, SubkeyName)
- else
- Result := ERROR_DIR_NOT_EMPTY;
- end;
- function GetShellFolderPath(const FolderID: Integer): String;
- var
- pidl: PItemIDList;
- Buffer: array[0..MAX_PATH-1] of Char;
- Malloc: IMalloc;
- begin
- Result := '';
- if FAILED(SHGetMalloc(Malloc)) then
- Malloc := nil;
- if SUCCEEDED(SHGetSpecialFolderLocation(0, FolderID, pidl)) then begin
- if SHGetPathFromIDList(pidl, Buffer) then
- Result := Buffer;
- if Assigned(Malloc) then
- Malloc.Free(pidl);
- end;
- end;
- function GetCurrentUserSid: String;
- var
- Token: THandle;
- UserInfoSize: DWORD;
- UserInfo: PTokenUser;
- StringSid: PWideChar;
- begin
- Result := '';
- if not OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, Token) then
- Exit;
- UserInfo := nil;
- try
- UserInfoSize := 0;
- if not GetTokenInformation(Token, TokenUser, nil, 0, UserInfoSize) and
- (GetLastError <> ERROR_INSUFFICIENT_BUFFER) then
- Exit;
- GetMem(UserInfo, UserInfoSize);
- if not GetTokenInformation(Token, TokenUser, UserInfo,
- UserInfoSize, UserInfoSize) then
- Exit;
- if ConvertSidToStringSidW(UserInfo.User.Sid, StringSid) then begin
- Result := StringSid;
- LocalFree(StringSid);
- end;
- finally
- FreeMem(UserInfo);
- CloseHandle(Token);
- end;
- end;
- function IsMemberOfGroup(const DomainAliasRid: DWORD): Boolean;
- { Returns True if the logged-on user is a member of the specified local
- group. }
- const
- SECURITY_NT_AUTHORITY: TSIDIdentifierAuthority =
- (Value: (0, 0, 0, 0, 0, 5));
- SECURITY_BUILTIN_DOMAIN_RID = $00000020;
- SE_GROUP_ENABLED = $00000004;
- SE_GROUP_USE_FOR_DENY_ONLY = $00000010;
- var
- Sid: PSID;
- CheckTokenMembership: function(TokenHandle: THandle; SidToCheck: PSID;
- var IsMember: BOOL): BOOL; stdcall;
- IsMember: BOOL;
- Token: THandle;
- GroupInfoSize: DWORD;
- GroupInfo: PTokenGroups;
- I: Integer;
- begin
- Result := False;
- if not AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2,
- SECURITY_BUILTIN_DOMAIN_RID, DomainAliasRid,
- 0, 0, 0, 0, 0, 0, Sid) then
- Exit;
- try
- { Use CheckTokenMembership if available. MSDN states:
- "The CheckTokenMembership function should be used with Windows 2000 and
- later to determine whether a specified SID is present and enabled in an
- access token. This function eliminates potential misinterpretations of
- the active group membership if changes to access tokens are made in
- future releases." }
- CheckTokenMembership := GetProcAddress(GetModuleHandle(advapi32),
- 'CheckTokenMembership');
- if Assigned(CheckTokenMembership) then begin
- if CheckTokenMembership(0, Sid, IsMember) then
- Result := IsMember;
- end
- else begin { Should never happen }
- GroupInfo := nil;
- if not OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, Token) then begin
- if GetLastError <> ERROR_NO_TOKEN then
- Exit;
- if not OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, Token) then
- Exit;
- end;
- try
- GroupInfoSize := 0;
- if not GetTokenInformation(Token, TokenGroups, nil, 0, GroupInfoSize) and
- (GetLastError <> ERROR_INSUFFICIENT_BUFFER) then
- Exit;
- GetMem(GroupInfo, GroupInfoSize);
- if not GetTokenInformation(Token, TokenGroups, GroupInfo,
- GroupInfoSize, GroupInfoSize) then
- Exit;
- for I := 0 to GroupInfo.GroupCount-1 do begin
- if EqualSid(Sid, GroupInfo.Groups[I].Sid) and
- (GroupInfo.Groups[I].Attributes and (SE_GROUP_ENABLED or
- SE_GROUP_USE_FOR_DENY_ONLY) = SE_GROUP_ENABLED) then begin
- Result := True;
- Break;
- end;
- end;
- finally
- FreeMem(GroupInfo);
- CloseHandle(Token);
- end;
- end;
- finally
- FreeSid(Sid);
- end;
- end;
- function IsAdminLoggedOn: Boolean;
- { Returns True if the logged-on user is a member of the Administrators local
- group. }
- const
- DOMAIN_ALIAS_RID_ADMINS = $00000220;
- begin
- Result := IsMemberOfGroup(DOMAIN_ALIAS_RID_ADMINS);
- end;
- function IsPowerUserLoggedOn: Boolean;
- { Returns True if the logged-on user is a member of the Power Users local
- group. }
- const
- DOMAIN_ALIAS_RID_POWER_USERS = $00000223;
- begin
- Result := IsMemberOfGroup(DOMAIN_ALIAS_RID_POWER_USERS);
- end;
- function IsMultiByteString(const S: AnsiString): Boolean;
- var
- I: Integer;
- begin
- Result := False;
- for I := 1 to Length(S) do
- if IsDBCSLeadByte(Ord(S[I])) then begin
- Result := True;
- Break;
- end;
- end;
- function FontExistsCallback(const lplf: TLogFont; const lptm: TTextMetric;
- dwType: DWORD; lpData: LPARAM): Integer; stdcall;
- begin
- Boolean(Pointer(lpData)^) := True;
- Result := 1;
- end;
- function FontExists(const FaceName: String): Boolean;
- var
- DC: HDC;
- begin
- Result := False;
- DC := GetDC(0);
- try
- EnumFonts(DC, PChar(FaceName), @FontExistsCallback, @Result);
- finally
- ReleaseDC(0, DC);
- end;
- end;
- function GetUILanguage: LANGID;
- { Platform-independent version of GetUserDefaultUILanguage. May return 0 in
- case of failure. }
- var
- GetUserDefaultUILanguage: function: LANGID; stdcall;
- K: HKEY;
- S: String;
- E: Integer;
- begin
- GetUserDefaultUILanguage := GetProcAddress(GetModuleHandle(kernel32),
- 'GetUserDefaultUILanguage');
- if Assigned(GetUserDefaultUILanguage) then
- Result := GetUserDefaultUILanguage
- else begin
- { GetUserDefaultUILanguage is available on Windows 2000, Me, and later so
- should never get here }
- if RegOpenKeyExView(rvDefault, HKEY_USERS, '.DEFAULT\Control Panel\International',
- 0, KEY_QUERY_VALUE, K) = ERROR_SUCCESS then begin
- RegQueryStringValue(K, 'Locale', S);
- RegCloseKey(K);
- end;
- Val('$' + S, Result, E);
- if E <> 0 then
- Result := 0;
- end;
- end;
- function RemoveAccelChar(const S: String): String;
- var
- I: Integer;
- begin
- Result := S;
- I := 1;
- while I <= Length(Result) do begin
- if Result[I] = '&' then begin
- System.Delete(Result, I, 1);
- if I > Length(Result) then
- Break;
- end;
- Inc(I, PathCharLength(Result, I));
- end;
- end;
- function GetTextWidth(const DC: HDC; S: String; const Prefix: Boolean): Integer;
- { Returns the width of the specified string using the font currently selected
- into DC. If Prefix is True, it first removes "&" characters as necessary. }
- var
- Size: TSize;
- begin
- { This procedure is 10x faster than using DrawText with the DT_CALCRECT flag }
- if Prefix then
- S := RemoveAccelChar(S);
- GetTextExtentPoint32(DC, PChar(S), Length(S), Size);
- Result := Size.cx;
- end;
- function AddPeriod(const S: String): String;
- begin
- Result := S;
- if (Result <> '') and (PathLastChar(Result)^ > '.') then
- Result := Result + '.';
- end;
- function GetExceptMessage: String;
- var
- E: TObject;
- begin
- E := ExceptObject;
- if E = nil then
- Result := '[ExceptObject=nil]' { should never get here }
- else if E is Exception then
- Result := AddPeriod(Exception(E).Message) { usual case }
- else
- Result := E.ClassName; { shouldn't get here under normal circumstances }
- end;
- function GetPreferredUIFont: String;
- { Gets the preferred UI font. Returns Microsoft Sans Serif, or MS Sans Serif
- if it doesn't exist.
- Microsoft Sans Serif (which is available on Windows 2000 and later) has two
- advantages over MS Sans Serif:
- 1) On Windows XP, it can display password dots in edit boxes.
- 2) In my tests on Japanese XP, Microsoft Sans Serif can display Japanese
- characters (MS Sans Serif cannot). }
- begin
- if FontExists('Microsoft Sans Serif') then
- Result := 'Microsoft Sans Serif'
- else
- Result := 'MS Sans Serif';
- end;
- function IsWildcard(const Pattern: String): Boolean;
- begin
- Result := (Pos('*', Pattern) <> 0) or (Pos('?', Pattern) <> 0);
- end;
- function WildcardMatch(const Text, Pattern: PChar): Boolean;
- { General-purpose wildcard matching function based on the widely used wildcat()
- code by Rich $alz. In this implementation, however, the only supported
- pattern matching characters are ? and *.
- Note that this function uses Unix shell semantics -- e.g. a dot always
- matches a dot (so a pattern of '*.*' won't match 'file'), and ? always
- matches exactly 1 character (so '?????' won't match 'file').
- Also note: The InternalWildcardMatch function can recursively call itself
- for each non-consecutive * character in the pattern. With enough *
- characters, the stack could overflow. So ideally the caller should impose a
- limit on either the length of the pattern string or the number of *
- characters in it. }
- type
- TWildcardMatchResult = (wmFalse, wmTrue, wmAbort);
- function InternalWildcardMatch(T, P: PChar): TWildcardMatchResult;
- begin
- while P^ <> #0 do begin
- if (T^ = #0) and (P^ <> '*') then begin
- Result := wmAbort;
- Exit;
- end;
- case P^ of
- '?': ; { Match any character }
- '*': begin
- Inc(P);
- while P^ = '*' do begin
- { Consecutive stars act just like one }
- Inc(P);
- end;
- if P^ = #0 then begin
- { Trailing star matches everything }
- Result := wmTrue;
- Exit;
- end;
- while T^ <> #0 do begin
- Result := InternalWildcardMatch(T, P);
- if Result <> wmFalse then
- Exit;
- T := PathStrNextChar(T);
- end;
- Result := wmAbort;
- Exit;
- end;
- else
- if not PathCharCompare(T, P) then begin
- Result := wmFalse;
- Exit;
- end;
- end;
- T := PathStrNextChar(T);
- P := PathStrNextChar(P);
- end;
- if T^ = #0 then
- Result := wmTrue
- else
- Result := wmFalse;
- end;
- begin
- Result := (InternalWildcardMatch(Text, Pattern) = wmTrue);
- end;
- function IntMax(const A, B: Integer): Integer;
- begin
- if A > B then
- Result := A
- else
- Result := B;
- end;
- function Win32ErrorString(ErrorCode: Integer): String;
- { Like SysErrorMessage but also passes the FORMAT_MESSAGE_IGNORE_INSERTS flag
- which allows the function to succeed on errors like 129 }
- var
- Len: Integer;
- Buffer: array[0..1023] of Char;
- begin
- Len := FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or
- FORMAT_MESSAGE_IGNORE_INSERTS or FORMAT_MESSAGE_ARGUMENT_ARRAY, nil,
- ErrorCode, 0, Buffer, SizeOf(Buffer) div SizeOf(Buffer[0]), nil);
- while (Len > 0) and ((Buffer[Len-1] <= ' ') or (Buffer[Len-1] = '.')) do
- Dec(Len);
- SetString(Result, Buffer, Len);
- end;
- function DeleteDirTree(const Dir: String): Boolean;
- { Removes the specified directory including any files/subdirectories inside
- it. Returns True if successful. }
- var
- H: THandle;
- FindData: TWin32FindData;
- FN: String;
- begin
- if (Dir <> '') and (Pos(#0, Dir) = 0) and { sanity/safety checks }
- IsDirectoryAndNotReparsePoint(Dir) then begin
- H := FindFirstFile(PChar(AddBackslash(Dir) + '*'), FindData);
- if H <> INVALID_HANDLE_VALUE then begin
- try
- repeat
- if (StrComp(FindData.cFileName, '.') <> 0) and
- (StrComp(FindData.cFileName, '..') <> 0) then begin
- FN := AddBackslash(Dir) + FindData.cFileName;
- if FindData.dwFileAttributes and FILE_ATTRIBUTE_READONLY <> 0 then
- SetFileAttributes(PChar(FN), FindData.dwFileAttributes and not FILE_ATTRIBUTE_READONLY);
- if FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY = 0 then
- Windows.DeleteFile(PChar(FN))
- else
- DeleteDirTree(FN);
- end;
- until not FindNextFile(H, FindData);
- finally
- Windows.FindClose(H);
- end;
- end;
- end;
- Result := RemoveDirectory(PChar(Dir));
- end;
- function SetNTFSCompression(const FileOrDir: String; Compress: Boolean): Boolean;
- { Changes the NTFS compression state of a file or directory. If False is
- returned, GetLastError can be called to get extended error information. }
- const
- COMPRESSION_FORMAT_NONE = 0;
- COMPRESSION_FORMAT_DEFAULT = 1;
- FSCTL_SET_COMPRESSION = $9C040;
- Compressions: array[Boolean] of Word = (COMPRESSION_FORMAT_NONE, COMPRESSION_FORMAT_DEFAULT);
- var
- Handle: THandle;
- BytesReturned, LastError: DWORD;
- begin
- Handle := CreateFile(PChar(FileOrDir), GENERIC_READ or GENERIC_WRITE,
- FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
- if Handle <> INVALID_HANDLE_VALUE then begin
- Result := DeviceIoControl(Handle, FSCTL_SET_COMPRESSION, @Compressions[Compress],
- SizeOf(Compressions[Compress]), nil, 0, BytesReturned, nil);
- { Save the error code from DeviceIoControl as CloseHandle may overwrite it
- (Windows 95's CloseHandle always sets it to zero) }
- LastError := GetLastError;
- CloseHandle(Handle);
- SetLastError(LastError);
- end else
- Result := False;
- end;
- var
- ChangeWindowMessageFilterInited: BOOL;
- ChangeWindowMessageFilterFunc: function(msg: UINT; dwFlag: DWORD): BOOL; stdcall;
- ChangeWindowMessageFilterExInited: BOOL;
- ChangeWindowMessageFilterExFunc: function(hWnd: HWND; msg: UINT;
- action: DWORD; pChangeFilterStruct: Pointer): BOOL; stdcall;
- procedure AddToWindowMessageFilter(const Msg: UINT);
- { Adds a single message number to the process-wide message filter. }
- const
- MSGFLT_ADD = 1;
- begin
- if not ChangeWindowMessageFilterInited then begin
- ChangeWindowMessageFilterFunc := GetProcAddress(GetModuleHandle(user32),
- 'ChangeWindowMessageFilter');
- InterlockedExchange(Integer(ChangeWindowMessageFilterInited), Ord(True));
- end;
- if Assigned(ChangeWindowMessageFilterFunc) then
- ChangeWindowMessageFilterFunc(Msg, MSGFLT_ADD);
- end;
- procedure AddToWindowMessageFilterEx(const Wnd: HWND; const Msg: UINT);
- { Adds a single message number to Wnd's window-specific message filter. Falls
- back to modifying the process-wide message filter but in reality that should
- never happen. }
- const
- MSGFLT_ALLOW = 1;
- begin
- if not ChangeWindowMessageFilterExInited then begin
- ChangeWindowMessageFilterExFunc := GetProcAddress(GetModuleHandle(user32),
- 'ChangeWindowMessageFilterEx');
- InterlockedExchange(Integer(ChangeWindowMessageFilterExInited), Ord(True));
- end;
- if Assigned(ChangeWindowMessageFilterExFunc) then
- ChangeWindowMessageFilterExFunc(Wnd, Msg, MSGFLT_ALLOW, nil)
- else
- AddToWindowMessageFilter(Msg);
- end;
- function ShutdownBlockReasonCreate(Wnd: HWND; const Reason: String): Boolean;
- var
- ShutdownBlockReasonCreateFunc: function(Wnd: HWND; pwszReason: LPCWSTR): Bool; stdcall;
- begin
- { MSDN doesn't say whether you must call Destroy before a second Create, but it does say a Destroy
- without a previous Create is a no-op, so call Destroy for safety. }
- ShutdownBlockReasonDestroy(Wnd);
- ShutdownBlockReasonCreateFunc := GetProcAddress(GetModuleHandle(user32), 'ShutdownBlockReasonCreate');
- if Assigned(ShutdownBlockReasonCreateFunc) then
- Result := ShutdownBlockReasonCreateFunc(Wnd, PChar(Reason))
- else
- Result := False;
- end;
- { As MSDN says: if ShutdownBlockReasonCreate was not previously called, this function is a no-op. }
- function ShutdownBlockReasonDestroy(Wnd: HWND): Boolean;
- var
- ShutdownBlockReasonDestroyFunc: function(Wnd: HWND): Bool; stdcall;
- begin
- ShutdownBlockReasonDestroyFunc := GetProcAddress(GetModuleHandle(user32), 'ShutdownBlockReasonDestroy');
- Result := Assigned(ShutdownBlockReasonDestroyFunc) and ShutdownBlockReasonDestroyFunc(Wnd);
- end;
- function TryStrToBoolean(const S: String; var BoolResult: Boolean): Boolean;
- begin
- if (S = '0') or (CompareText(S, 'no') = 0) or (CompareText(S, 'false') = 0) then begin
- BoolResult := False;
- Result := True;
- end
- else if (S = '1') or (CompareText(S, 'yes') = 0) or (CompareText(S, 'true') = 0) then begin
- BoolResult := True;
- Result := True;
- end
- else
- Result := False;
- end;
- procedure WaitMessageWithTimeout(const Milliseconds: DWORD);
- { Like WaitMessage, but times out if a message isn't received before
- Milliseconds ms have elapsed. }
- begin
- MsgWaitForMultipleObjects(0, THandle(nil^), False, Milliseconds, QS_ALLINPUT);
- end;
- function MoveFileReplace(const ExistingFileName, NewFileName: String): Boolean;
- begin
- Result := MoveFileEx(PChar(ExistingFileName), PChar(NewFileName),
- MOVEFILE_REPLACE_EXISTING);
- end;
- var
- SHAutoCompleteInitialized: Boolean;
- SHAutoCompleteFunc: function(hwndEdit: HWND; dwFlags: dWord): LongInt; stdcall;
- procedure TryEnableAutoCompleteFileSystem(Wnd: HWND);
- const
- SHACF_FILESYSTEM = $1;
- var
- M: HMODULE;
- begin
- if not SHAutoCompleteInitialized then begin
- M := SafeLoadLibrary(AddBackslash(GetSystemDir) + 'shlwapi.dll',
- SEM_NOOPENFILEERRORBOX);
- if M <> 0 then
- SHAutoCompleteFunc := GetProcAddress(M, 'SHAutoComplete');
- SHAutoCompleteInitialized := True;
- end;
- if Assigned(SHAutoCompleteFunc) then
- SHAutoCompleteFunc(Wnd, SHACF_FILESYSTEM);
- end;
- procedure CreateMutex(const MutexName: String);
- const
- SECURITY_DESCRIPTOR_REVISION = 1; { Win32 constant not defined in Delphi 3 }
- var
- SecurityDesc: TSecurityDescriptor;
- SecurityAttr: TSecurityAttributes;
- begin
- { By default on Windows NT, created mutexes are accessible only by the user
- running the process. We need our mutexes to be accessible to all users, so
- that the mutex detection can work across user sessions in Windows XP. To
- do this we use a security descriptor with a null DACL. }
- InitializeSecurityDescriptor(@SecurityDesc, SECURITY_DESCRIPTOR_REVISION);
- SetSecurityDescriptorDacl(@SecurityDesc, True, nil, False);
- SecurityAttr.nLength := SizeOf(SecurityAttr);
- SecurityAttr.lpSecurityDescriptor := @SecurityDesc;
- SecurityAttr.bInheritHandle := False;
- Windows.CreateMutex(@SecurityAttr, False, PChar(MutexName));
- end;
- { TOneShotTimer }
- function TOneShotTimer.Expired: Boolean;
- begin
- Result := (TimeRemaining = 0);
- end;
- procedure TOneShotTimer.SleepUntilExpired;
- var
- Remaining: Cardinal;
- begin
- while True do begin
- Remaining := TimeRemaining;
- if Remaining = 0 then
- Break;
- Sleep(Remaining);
- end;
- end;
- procedure TOneShotTimer.Start(const Timeout: Cardinal);
- begin
- FStartTick := GetTickCount;
- FTimeout := Timeout;
- FLastElapsed := 0;
- end;
- function TOneShotTimer.TimeElapsed: Cardinal;
- var
- Elapsed: Cardinal;
- begin
- Elapsed := GetTickCount - FStartTick;
- if Elapsed > FLastElapsed then
- FLastElapsed := Elapsed;
- Result := FLastElapsed;
- end;
- function TOneShotTimer.TimeRemaining: Cardinal;
- var
- Elapsed: Cardinal;
- begin
- Elapsed := TimeElapsed;
- if Elapsed < FTimeout then
- Result := FTimeout - Elapsed
- else
- Result := 0;
- end;
- { TCreateProcessOutputReader }
- constructor TCreateProcessOutputReader.Create(const ALogProc: TLogProc;
- const ALogProcData: NativeInt; AMode: TOutputMode = omLog);
- procedure CreatePipeAndSetHandleInformation(var Read, Write: THandle; SecurityAttr: TSecurityAttributes);
- begin
- { CreatePipe docs say no assumptions should be made about the output
- parameter contents (the two handles) when it fails. So specify local
- variables for the output parameters, and only copy the handles into
- the "var" parameters when CreatePipe is successful. That way, if it
- does fail, the "var" parameters will still have their original 0
- values (which is important because the destructor closes all
- non-zero handles). }
- var TempReadPipe, TempWritePipe: THandle;
- if not CreatePipe(TempReadPipe, TempWritePipe, @SecurityAttr, 0) then
- raise Exception.CreateFmt('Output redirection error: CreatePipe failed (%d)', [GetLastError]);
- Read := TempReadPipe;
- Write := TempWritePipe;
- if not SetHandleInformation(TempReadPipe, HANDLE_FLAG_INHERIT, 0) then
- raise Exception.CreateFmt('Output redirection error: SetHandleInformation failed (%d)', [GetLastError]);
- end;
- begin
- if not Assigned(ALogProc) then
- raise Exception.Create('ALogProc is required');
- if AMode = omCapture then begin
- FCaptureOutList := TStringList.Create;
- FCaptureErrList := TStringList.Create;
- end;
- FMode := AMode;
- FLogProc := ALogProc;
- FLogProcData := ALogProcData;
- FNextLineIsFirstLine := True;
- var SecurityAttributes: TSecurityAttributes;
- SecurityAttributes.nLength := SizeOf(SecurityAttributes);
- SecurityAttributes.bInheritHandle := True;
- SecurityAttributes.lpSecurityDescriptor := nil;
- var NulDevice := CreateFile('\\.\NUL', GENERIC_READ,
- FILE_SHARE_READ or FILE_SHARE_WRITE, @SecurityAttributes,
- OPEN_EXISTING, 0, 0);
- { In case the NUL device is missing (which it inexplicably seems to
- be for some users, per web search), don't treat it as a fatal
- error. Just leave FStdInNulDevice at 0. It's not ideal, but the
- child process likely won't even attempt to access stdin anyway. }
- if NulDevice <> INVALID_HANDLE_VALUE then
- FStdInNulDevice := NulDevice;
- CreatePipeAndSetHandleInformation(FStdOut.PipeRead, FStdOut.PipeWrite, SecurityAttributes);
- FStdOut.OkToRead := True;
- FStdOut.CaptureList := FCaptureOutList;
- if FMode = omCapture then begin
- CreatePipeAndSetHandleInformation(FStdErr.PipeRead, FStdErr.PipeWrite, SecurityAttributes);
- FStdErr.OkToRead := True;
- FStdErr.CaptureList := FCaptureErrList;
- end;
- FMaxTotalBytesToRead := 10*1000*1000;
- FMaxTotalLinesToRead := 1000*1000;
- end;
- destructor TCreateProcessOutputReader.Destroy;
- begin
- CloseAndClearHandle(FStdInNulDevice);
- CloseAndClearHandle(FStdOut.PipeRead);
- CloseAndClearHandle(FStdOut.PipeWrite);
- CloseAndClearHandle(FStdErr.PipeRead);
- CloseAndClearHandle(FStdErr.PipeWrite);
- FCaptureOutList.Free;
- FCaptureErrList.Free;
- inherited;
- end;
- procedure TCreateProcessOutputReader.CloseAndClearHandle(var Handle: THandle);
- begin
- if Handle <> 0 then begin
- CloseHandle(Handle);
- Handle := 0;
- end;
- end;
- procedure TCreateProcessOutputReader.HandleAndLogErrorFmt(const S: String; const Args: array of const);
- begin
- FLogProc('OutputReader: ' + Format(S, Args), True, False, FLogProcData);
- if FMode = omCapture then
- FCaptureError := True;
- end;
- procedure TCreateProcessOutputReader.UpdateStartupInfo(var StartupInfo: TStartupInfo);
- begin
- StartupInfo.dwFlags := StartupInfo.dwFlags or STARTF_USESTDHANDLES;
- StartupInfo.hStdInput := FStdInNulDevice;
- StartupInfo.hStdOutput := FStdOut.PipeWrite;
- if FMode = omLog then
- StartupInfo.hStdError := FStdOut.PipeWrite
- else
- StartupInfo.hStdError := FStdErr.PipeWrite;
- end;
- procedure TCreateProcessOutputReader.NotifyCreateProcessDone;
- begin
- CloseAndClearHandle(FStdInNulDevice);
- CloseAndClearHandle(FStdOut.PipeWrite);
- CloseAndClearHandle(FStdErr.PipeWrite);
- end;
- procedure TCreateProcessOutputReader.Read(const LastRead: Boolean);
- function FindNewLine(const S: AnsiString; const LastRead: Boolean): Integer;
- begin
- { This will return the position of the first #13 or #10. If a #13 is at
- the very end of the string it's only accepted if we are certain we can't
- be looking at a split #13#10 because there will be no more reads }
- var N := Length(S);
- for var I := 1 to N do
- if ((S[I] = #13) and ((I < N) or LastRead)) or
- (S[I] = #10) then
- Exit(I);
- Result := 0;
- end;
- procedure LogLine(const CaptureList: TStringList; const S: AnsiString);
- begin
- var UTF8S := UTF8ToString(S);
- if CaptureList <> nil then
- CaptureList.Add(UTF8S)
- else begin
- FLogProc(UTF8S, False, FNextLineIsFirstLine, FLogProcData);
- FNextLineIsFirstLine := False;
- end;
- end;
- function SharedLimitReached: Boolean;
- begin
- Result := (FTotalBytesRead >= FMaxTotalBytesToRead) or
- (FTotalLinesRead >= FMaxTotalLinesToRead);
- end;
- procedure DoRead(var Pipe: TCreateProcessOutputReaderPipe; const LastRead: Boolean);
- begin
- if Pipe.OKToRead then begin
- if SharedLimitReached then begin
- { The other pipe reached the shared limit which was handled and logged.
- So don't read from this pipe but instead close it and exit silently. }
- Pipe.OKToRead := False;
- Pipe.Buffer := '';
- CloseAndClearHandle(Pipe.PipeRead);
- Exit;
- end;
- var TotalBytesAvail: DWORD;
- Pipe.OKToRead := PeekNamedPipe(Pipe.PipeRead, nil, 0, nil, @TotalBytesAvail, nil);
- if not Pipe.OKToRead then begin
- var LastError := GetLastError;
- if LastError <> ERROR_BROKEN_PIPE then begin
- Pipe.Buffer := '';
- HandleAndLogErrorFmt('PeekNamedPipe failed (%d).', [LastError]);
- end;
- end else if TotalBytesAvail > 0 then begin
- { Don't read more than our read limit }
- if TotalBytesAvail > FMaxTotalBytesToRead - FTotalBytesRead then
- TotalBytesAvail := FMaxTotalBytesToRead - FTotalBytesRead;
- { Append newly available data to the incomplete line we might already have }
- var TotalBytesHave: DWORD := Length(Pipe.Buffer);
- SetLength(Pipe.Buffer, TotalBytesHave+TotalBytesAvail);
- var BytesRead: DWORD;
- Pipe.OKToRead := ReadFile(Pipe.PipeRead, Pipe.Buffer[TotalBytesHave+1],
- TotalBytesAvail, BytesRead, nil);
- if not Pipe.OKToRead then begin
- var LastError := GetLastError;
- if LastError <> ERROR_BROKEN_PIPE then begin
- Pipe.Buffer := '';
- HandleAndLogErrorFmt('ReadFile failed (%d).', [LastError]);
- end else begin
- { Restore back to original size }
- SetLength(Pipe.Buffer, TotalBytesHave);
- end;
- end else begin
- { Correct length if less bytes were read than requested }
- SetLength(Pipe.Buffer, TotalBytesHave+BytesRead);
- { Check for completed lines thanks to the new data }
- while FTotalLinesRead < FMaxTotalLinesToRead do begin
- var P := FindNewLine(Pipe.Buffer, LastRead);
- if P = 0 then
- Break;
- LogLine(Pipe.CaptureList, Copy(Pipe.Buffer, 1, P-1));
- Inc(FTotalLinesRead);
- if (Pipe.Buffer[P] = #13) and (P < Length(Pipe.Buffer)) and (Pipe.Buffer[P+1] = #10) then
- Inc(P);
- Delete(Pipe.Buffer, 1, P);
- end;
- Inc(FTotalBytesRead, BytesRead);
- if SharedLimitReached then begin
- { Read limit reached: break the pipe, throw away the incomplete line, and log an error }
- Pipe.OKToRead := False;
- Pipe.Buffer := '';
- if FTotalBytesRead >= FMaxTotalBytesToRead then
- HandleAndLogErrorFmt('Maximum output length (%d) reached, ignoring remainder.', [FMaxTotalBytesToRead])
- else
- HandleAndLogErrorFmt('Maximum output lines (%d) reached, ignoring remainder.', [FMaxTotalLinesToRead]);
- end;
- end;
- end;
- { Unblock the child process's write, and cause further writes to fail immediately }
- if not Pipe.OkToRead then
- CloseAndClearHandle(Pipe.PipeRead);
- end;
- if LastRead and (Pipe.Buffer <> '') then begin
- var N := Length(Pipe.Buffer);
- if Pipe.Buffer[N] = #13 then begin
- { See FindNewLine: the buffer could end with a final #13 which needs to
- be stripped still }
- Delete(Pipe.Buffer, N, 1);
- end;
- LogLine(Pipe.CaptureList, Pipe.Buffer);
- end;
- end;
-
- begin
- DoRead(FStdOut, LastRead);
- if FMode = omCapture then
- DoRead(FStdErr, LastRead);
- end;
- end.
|