| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856 |
- 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. This does not use the Win32 function GetTempPath, due to platform
- differences. }
- label 1;
- begin
- Result := GetEnv('TMP');
- if (Result <> '') and DirExists(Result) then
- goto 1;
- Result := GetEnv('TEMP');
- if (Result <> '') and DirExists(Result) then
- goto 1;
- { Like Windows 2000's GetTempPath, return USERPROFILE when TMP and TEMP
- are not set }
- Result := GetEnv('USERPROFILE');
- if (Result <> '') and DirExists(Result) then
- goto 1;
- Result := GetWinDir;
- 1:Result := AddBackslash(PathExpand(Result));
- 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.
|