I'm trying to encapsulate a call to create a shell process and capture its stdout/stderr from named pipes for further processing.
I can capture the stdout from a separately started process, but not one spawned from within the application. I suspect (very strongly) that I am missing a flag or setting for CreateProcess that would allow this. That is, if I start the process from a command line with "ipconfig > \\.\pipe\{whatever-the-id-is}" I get the stdout when I read the stream. However, if I start the process with CreateProcess I do not.
Here's the encapsulation code:
unit ShellEncapsulation;
interface
uses Classes, Sysutils, Windows, ShellAPI;
type
TShell = class (TObject)
private
FExecResults: Boolean;
FStdErrName: String;
FCommandLine: String;
FStdOutName: String;
FStderr: TStream;
FStdout: TStream;
hStdErr: THandle;
hStdOut: THandle;
Fbuffersize: Integer;
FTimeOut: Integer;
procedure SetCommandLine(const Value: String);
procedure SetExecResults(const Value: Boolean);
procedure SetStderr(const Value: TStream);
procedure SetStdErrName(const Value: String);
procedure SetStdout(const Value: TStream);
procedure SetStdOutName(const Value: String);
procedure Setbuffersize(const Value: Integer);
procedure SetTimeOut(const Value: Integer);
protected
function GetUniqueName: String;
property CommandLine: String read FCommandLine write SetCommandLine;
property StdErrName: String read FStdErrName write SetStdErrName;
property StdOutName: String read FStdOutName write SetStdOutName;
public
Constructor Create;
Destructor Done;
function Exec (_Commandline: String): Boolean;
property Stdout: TStream read FStdout write SetStdout;
property Stderr: TStream read FStderr write SetStderr;
property ExecResults: Boolean read FExecResults write SetExecResults;
property buffersize: Integer read Fbuffersize write Setbuffersize;
property TimeOut: Integer read FTimeOut write SetTimeOut;
end;
implementation
{ TShell }
constructor TShell.Create;
begin
buffersize := 1024 * 1024; // Allow 1MB Buffer
Timeout := 10000; // Allow 10 seconds time out
end;
destructor TShell.Done;
begin
// StdOut.Close;
StdOut.Free;
StdOut := NIL;
DisconnectNamedPipe (hStdout);
// DeleteFile (StdOutName);
// StdErr.Close
StdErr.Free;
StdErr := Nil;
DisconnectNamedPipe (hStderr);
// DeleteFile (StdErrName);
end;
function TShell.Exec(_Commandline: String): Boolean;
var
StartUpInfo : TStartUpInfo;
ProcessInfo : TProcessInformation;
cmdLine: String;
begin
stdoutname := GetUniquename;
stderrname := GetUniquename;
hStdOut := CreateNamedPipe (PChar(stdOutName),
PIPE_ACCESS_INBOUND,
PIPE_TYPE_BYTE+PIPE_WAIT,
99,
bufferSize,
bufferSize,
TimeOut,
NIL);
hStdErr := CreateNamedPipe (PChar(stdErrName),
PIPE_ACCESS_INBOUND,
PIPE_TYPE_BYTE+PIPE_WAIT,
99,
bufferSize,
bufferSize,
TimeOut,
NIL);
StdOut := THandleStream.Create (hStdOut);
StdErr := THandleStream.Create (hStdErr);
FillChar(StartUpInfo, SizeOf(TStartUpInfo), 0);
with StartUpInfo do begin
cb := SizeOf(TStartUpInfo);
dwFlags := STARTF_USESTDHANDLES;
wShowWindow := SW_SHOWDEFAULT;
hStdOutput := hStdOut;
hStdError := hStdErr;
end; // with
ExecResults := CreateProcess(nil,
PChar(_CommandLine),
nil,
nil,
false,
DETACHED_PROCESS + NORMAL_PRIORITY_CLASS,
nil,
PChar(ExtractFilePath(paramStr(0))),
StartUpInfo,
ProcessInfo);
Result := ExecResults;
end;
function TShell.GetUniqueName: String;
var
aGUID: TGUID;
begin
CreateGUID (aGUID);
// result := '\\.\PIPE\'+Copy (GUIDToString (aGUID),2,8);
result := '\\.\PIPE\'+GUIDToString (aGUID);
end;
procedure TShell.Setbuffersize(const Value: Integer);
begin
Fbuffersize := Value;
end;
procedure TShell.SetCommandLine(const Value: String);
begin
FCommandLine := Value;
end;
procedure TShell.SetExecResults(const Value: Boolean);
begin
FExecResults := Value;
end;
procedure TShell.SetStderr(const Value: TStream);
begin
FStderr := Value;
end;
procedure TShell.SetStdErrName(const Value: String);
begin
FStdErrName := Value;
end;
procedure TShell.SetStdout(const Value: TStream);
begin
FStdout := Value;
end;
procedure TShell.SetStdOutName(const Value: String);
begin
FStdOutName := Value;
end;
procedure TShell.SetTimeOut(const Value: Integer);
begin
FTimeOut := Value;
end;
end.
by: DragonSlayerPosted on 2003-07-18 at 03:41:10ID: 8950631
Hi swift99, this thing took a couple of my hours... :)
tor := nil;
eamHandle) ;
reamHandle ) do );
was going thru your code and logically, nothing could be wrong hehehe... tried it again and again, with security attributes, overlapped I/O... none worked.
then I modified a non-OO version of the above code using standard files (CreateFile thingy)... and it worked! duh!
so I put in pipes... and it stopped working again... double duh!
ok to cut a long (and frustrating) story short, I was biting on my Snickers bar, and suddenly decided to try something crazy... you know, handles are handles, right? so there should be no difference between a pipe handle or a file handle... but I decided to give it a try anyway...
FillChar(SecAttrs, SizeOf(SecAttrs), #0);
SecAtrrs.nLength := SizeOf(SecAttrs);
SecAtrrs.lpSecurityDescrip
SecAtrrs.bInheritHandle := True;
StrPCopy(pOutPutFile, OutStreamName); // <-- OutStreamName was generated via GetUniqueName
if OutStreamHandle <> INVALID_HANDLE_VALUE then
DisconnectNamedPipe(OutStr
OutStreamHandle := CreateNamedPipe(
PChar(pOutputFile),
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE or PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
8192,
8192,
10000,
@SecAttrs);
hOutputFile := CreateFile(
pOutPutFile, { pointer to name of the file }
GENERIC_READ or GENERIC_WRITE, { access (read-write) mode }
FILE_SHARE_READ or FILE_SHARE_WRITE, { share mode }
@SecAttrs, { pointer to security attributes }
CREATE_ALWAYS, { how to create }
FILE_ATTRIBUTE_TEMPORARY, { file attributes }
0 ); { handle to file with attributes to copy }
tried it... still didn't work.
so I created an input pipe...
CreateNamedPipe(
pInputFile,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE or PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
8192,
8192,
10000,
@SecAtrrs);
{ create the appropriate handle for the input file }
hInputFile := CreateFile(
pInputFile, { pointer to name of the file }
GENERIC_READ or GENERIC_WRITE, { access (read-write) mode }
FILE_SHARE_READ or FILE_SHARE_WRITE, { share mode }
@SecAtrrs, { pointer to security attributes }
OPEN_ALWAYS, { how to create }
FILE_ATTRIBUTE_TEMPORARY, { file attributes }
0 ); { handle to file with attributes to copy }
FillChar(StartupInfo, SizeOf(StartupInfo), #0);
StartupInfo.cb := SizeOf(StartupInfo);
StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
StartupInfo.wShowWindow := SW_HIDE;
StartupInfo.hStdOutput := hOutputFile;
StartupInfo.hStdInput := hInputFile; // INVALID_HANDLE_VALUE;
{ create the app }
Result := CreateProcess(nil, { pointer to name of executable module }
pCommandLine, { pointer to command line string }
nil, { pointer to process security attributes }
nil, { pointer to thread security attributes }
True, { handle inheritance flag }
CREATE_NEW_CONSOLE or
REALTIME_PRIORITY_CLASS, { creation flags }
nil, { pointer to new environment block }
nil, { pointer to current directory name }
StartupInfo, { pointer to STARTUPINFO }
ProcessInfo); { pointer to PROCESS_INF }
and it worked like a charm!
if OutStreamHandle <> INVALID_HANDLE_VALUE then
begin
with THandleStream.Create(OutSt
try
Seek(0, soFromBeginning);
Memo1.Lines.Clear;
while Position < Size do
begin
Read(TextStream, SizeOf(TextStream));
Memo1.Lines.Add(TextStream
end;
finally
OutStream.Free;
end;
end;
seems that if I remove the value for StdOut, it doesn't work again.
Now there you go... enjoy yourself :)
DragonSlayer.
PS: Wow, that's a bargain for 50pts :p ... but I sort of like took it as a personal challenge, heh