Jump to content
Tuts 4 You

How to read from CLI till its finished


LCF-AT

Recommended Posts

LCF-AT

Hi guys,

I have again a small question about piping a CLI output to my buffer.So I just wanna start a CLI app with paramters and reading the output (content I also see in CMD window) so long till its finished.So the only problem I have so far is how to wait till its finished.Lets say the CLI app needs a while (a minute etc).My question is what function/s I need to use for that?I see I cant use WaitForSingleObject with INFINITE paramter so this dosent work because the CLI app dosent exit itself so long I didnt had reading the bytes.If I just read the bytes using PeekNamedPipe / ReadFile function then I dont get the whole content = it quits to early and the CLI app is still working.

My goal is it to read the bytes on fly and checking whether the pipe / process is still working / activ or not.

Below some example code:

_CreatePipe PROC

Local  RetVal:DWORD

        invoke RtlZeroMemory,addr SecuAttr,sizeof SecuAttr
        invoke RtlZeroMemory,addr StartInfo,sizeof StartInfo
        invoke RtlZeroMemory,addr PI,sizeof PI
        mov RetVal,0
        mov SecuAttr.nLength,sizeof SECURITY_ATTRIBUTES
        mov SecuAttr.bInheritHandle,TRUE
        invoke CreatePipe,addr hRead,addr hWrite,addr SecuAttr,NULL
        .if eax == FALSE
            invoke MessageBox,0,chr$("CreatePipe failed!"),chr$("Problem!"),MB_ICONWARNING
            mov eax,FALSE
            ret
        .endif
	    invoke SetHandleInformation, hRead, HANDLE_FLAG_INHERIT, 0
        .if eax == FALSE
            invoke CloseHandle,hRead
            invoke CloseHandle,hWrite
            invoke MessageBox,0,chr$("SetHandleInformation failed!"),chr$("Problem!"),MB_ICONWARNING
            mov eax,FALSE
            ret
        .endif	
        mov RetVal,eax
        mov StartInfo.cb,sizeof StartInfo
        invoke GetStartupInfo,addr StartInfo
        xor eax,eax
        mov StartInfo.lpReserved,eax     
        mov eax,hWrite
        mov StartInfo.hStdOutput,eax
        mov StartInfo.hStdError,eax
	    mov StartInfo.dwFlags, STARTF_USESTDHANDLES
        mov StartInfo.wShowWindow,SW_SHOW
        mov eax,RetVal
        ret
_CreatePipe endp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
		.elseif ax == IDB_TEST

		    invoke VirtualAlloc,0,50000h,MEM_COMMIT,PAGE_EXECUTE_READWRITE
		    mov esi, eax
		    push esi
		    invoke _CreatePipe
		    .if eax != FALSE
		        lea edi, chr$("cmd.exe /c C:\tool.exe -t -r")
		        invoke CreateProcess, NULL, edi, NULL,NULL,TRUE,CREATE_NO_WINDOW,NULL,NULL, addr StartInfo, addr PI 
		        .if eax != FALSE

		            @@:
		            invoke Sleep,300
		            invoke PeekNamedPipe,hRead,offset PIPEBUFFER,sizeof PIPEBUFFER,offset PIPEREADBYTES,addr AVAL,NULL
		            .if eax != FALSE
		                .if AVAL != 0h
		                    invoke ReadFile,hRead,esi,PIPEREADBYTES,addr BYTESREAD,0
		                    .if eax != FALSE
		                        add esi, BYTESREAD
		                        jmp @B
		                    .else
		                        invoke MessageBox,0,chr$("ReadFile failed!"),chr$("Problem!"),MB_ICONWARNING
		                        jmp @F
		                    .endif
		                .else
		                    pop esi
		                    push esi
		                    invoke MessageBox,0,esi,chr$("Info!"),MB_ICONINFORMATION
		                .endif
		            .else
		                invoke MessageBox,0,chr$("PeekNamedPipe failed!"),chr$("Problem!"),MB_ICONWARNING
		            .endif

		                @@:
		                invoke CloseHandle, [PI.hThread]
		                invoke CloseHandle, [PI.hProcess]
		                invoke CloseHandle, hRead	
		                invoke CloseHandle, hWrite	            
		        .else
		        invoke CloseHandle, hRead
		        invoke CloseHandle, hWrite
		        .endif
		    .endif
		    pop esi
		    invoke VirtualFree,esi,0,MEM_RELEASE

How to handle the wait operation correctly and to read all bytes / content so long the CLI app is activ / working?Maybe anyone could help a little to fix that problem.

Thank you

Link to post
evlncrn8

https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess

that at least lets you check 100% if the process has exited, the rest is just polling, to check if there's input and so on

also, 

        mov StartInfo.hStdOutput,eax
        mov StartInfo.hStdError,eax

was that intentional ?

theres stdout, stdin, and stderr.. also you didnt set hread from what i saw but i didnt go through the code in detail

  • Like 2
Link to post

When you use CreateProcess the PROCESS_INFORMATION class gives back the main process handle and the main thread handle. You can use those to pull the exit codes of either.

 - GetExitCodeProcess and GetExitCodeThread

Keep in mind if the application is multi-threaded, it is not guaranteed that the main thread handle will remain alive for the entire duration of the app.

  • Like 1
Link to post
LCF-AT

Hi guys,

thanks for your answers.Ok I am just checking whether the process is still active or not using GetExitCodeProcess function.Ok,so this seems to work  so far and I can read while pipe content.Now I found another small problem.So if I start the CLI app and it does run for a longer while then I wanna also stop the process similar like using CRTL+C key press in CMD window.First I just tried to use TermniateProcess function with the hprocess ID of CLI app but this dosent work and CLI app keeps working in background / taskmanager and I need to wait till its finished by itself.Now I checked again the internet and found something about using GenerateConsoleCtrlEvent function to send CTRL_C_EVENT.I also found some example code like this...

https://codetitans.pl/blog/post/sending-ctrl-c-signal-to-another-application-on-windows

[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool FreeConsole();

[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add);

// Delegate type to be used as the Handler Routine for SCCH
delegate Boolean ConsoleCtrlDelegate(CtrlTypes type);

// Enumerated type for the control messages sent to the handler routine
enum CtrlTypes : uint
{
    CTRL_C_EVENT = 0,
    CTRL_BREAK_EVENT,
    CTRL_CLOSE_EVENT,
    CTRL_LOGOFF_EVENT = 5,
    CTRL_SHUTDOWN_EVENT
}

[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);

public static void StopProgram(uint pid)
{
    // It's impossible to be attached to 2 consoles at the same time,
    // so release the current one.
    FreeConsole();

    // This does not require the console window to be visible.
    if (AttachConsole(pid))
    {
        // Disable Ctrl-C handling for our program
        SetConsoleCtrlHandler(null, true);
        GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);

        // Must wait here. If we don't and re-enable Ctrl-C
        // handling below too fast, we might terminate ourselves.
        Thread.Sleep(2000);

        FreeConsole();

        // Re-enable Ctrl-C handling or any subsequently started
        // programs will inherit the disabled state.
        SetConsoleCtrlHandler(null, false);
    }
}

...I tried this like this for testing...

	                             invoke CreateProcess, NULL,addr commandline,NULL,NULL,TRUE,CREATE_NO_WINDOW,NULL,NULL,addr StartInfo,addr PI
	                             .if eax != FALSE
	                                 invoke Sleep,2000

	                                 invoke AttachConsole,PI.dwProcessId
	                                 .if eax != FALSE
	                                     invoke SetConsoleCtrlHandler,NULL,TRUE
	                                     .if eax != FALSE
	                                         invoke GenerateConsoleCtrlEvent,CTRL_C_EVENT,NULL
	                                         .if eax != FALSE
	                                             invoke Sleep,2000
	                                             invoke FreeConsole
	                                             invoke SetConsoleCtrlHandler,NULL,FALSE
	                                         .endif
	                                     .endif
	                                 .endif

...so it seems to work and the CLI app gets closed but I am not so sure whether this code example is good idea to use it on that way etc.In Olly it does crash after I did traced over this code after (not in outside of Olly).So is it needed to use FreeConsole & SetConsoleCtrlHandler at the end if the CLI is no more present?I dont think so or?I am using already cmd.exe /c paramter in CreateProcess function.

Other issue I see is that I dont use the flag CREATE_NEW_PROCESS_GROUP (in the link above they told to use it but then CTRL_C_EVENT isnt working).Maybe you have some another little clue how I should do it correctly to send CTRL_C_EVENT & wait without to get any trouble later and I also dont wanna exit the CLI process rough you know.

greetz

Link to post
LCF-AT

Hi again,

ok I used it so now with freeconsole & Setconsolehandler at the end.So if I dont do it then it dosent work anymore after first try.My thread code look so now...

local WASATTACHED:DWORD

    mov WASATTACHED, 0
    @@:
    .if STOP == 1  ; <--- stop CLI
        invoke GetExitCodeProcess,[PI.hProcess],addr ExitCode
        .if ExitCode == STILL_ACTIVE && WASATTACHED == NULL
            invoke AttachConsole,PI.dwProcessId
            mov WASATTACHED, 1
            invoke SetConsoleCtrlHandler,NULL,TRUE
            invoke GenerateConsoleCtrlEvent,CTRL_C_EVENT,NULL
        .endif       
    .endif
    invoke RtlZeroMemory,addr PIPEBUFFER,sizeof PIPEBUFFER
    invoke Sleep,30
    invoke PeekNamedPipe,hRead,offset PIPEBUFFER,sizeof PIPEBUFFER-10h,offset PIPEREADBYTES,addr AVAL,NULL
    .if eax != FALSE
        .if AVAL != 0h
            invoke RtlZeroMemory,addr PIPEBUFFER,sizeof PIPEBUFFER
            invoke ReadFile,hRead,addr PIPEBUFFER,PIPEREADBYTES,addr BYTESREAD,0
            .if eax != FALSE
                invoke SendMessage,RICHEDITHANDLE,EM_REPLACESEL,FALSE,addr PIPEBUFFER
                invoke RichSetPos, RICHEDITHANDLE, -1h
                jmp @B
            .else
                invoke MessageBox,0,chr$("ReadFile failed!"),chr$("Problem!"),MB_ICONWARNING
                jmp @F
            .endif
        .else
            invoke GetExitCodeProcess,[PI.hProcess],addr ExitCode
            .if ExitCode == STILL_ACTIVE
                jmp @B
            .endif
        .endif
    .else
        invoke MessageBox,0,chr$("PeekNamedPipe failed!"),chr$("Problem!"),MB_ICONWARNING
    .endif
    @@:
    invoke CloseHandle, hRead
    invoke CloseHandle, hWrite     
    invoke CloseHandle, [PI.hThread]
    invoke CloseHandle, [PI.hProcess] 
    .if STOP == 1 && WASATTACHED == 1
        invoke FreeConsole
        invoke SetConsoleCtrlHandler,NULL,FALSE
    .endif  
 
    mov STOP, 0
    xor eax,eax
	Ret

...seems to work fine so far. :)

Thanks again for the help.

greetz

Link to post
  • 2 years later...
LCF-AT

Hi again,

so I have another addon question about starting console windows,So how to manage to start more than one console instances?Lets say I wanna run a CLI app few times via CreateProcess function and I wanna also check whether they are still running and I wanna also force to exit them if I want by sending CTRL_C_EVENT (same code as above).Is that doable?In example above I have to use that functions...

CreateProcess
----------------
AttachConsole
SetConsoleCtrlHandler
GenerateConsoleCtrlEvent
GetExitCodeProcess
CloseHandle
FreeConsole
SetConsoleCtrlHandler

...which are working for one instance without problems but now I wanna start more than one instances at same time or one by one you know.So above in code example I saw the info...

    // It's impossible to be attached to 2 consoles at the same time,
    // so release the current one.

...which tells me that there is any limit and I only can attach only one  CLI.Is that right?

CreateProcess
----------------
invoke AttachConsole,PI.dwProcessId                      <-- PID
invoke SetConsoleCtrlHandler,NULL,TRUE                   <-- No PID
invoke GenerateConsoleCtrlEvent,CTRL_C_EVENT,NULL        <-- No PID
invoke GetExitCodeProcess,[PI.hProcess],addr ExitCode    <-- PID
invoke CloseHandle, [PI.hThread]                         <-- PID
invoke CloseHandle, [PI.hProcess]                        <-- PID
invoke FreeConsole                                       <-- No PID
invoke SetConsoleCtrlHandler,NULL,FALSE                  <-- No PID

Above you can see the functions I can use a PID with and some of them I can not use a specific PID.When I start more than one CLI apps at same time then I can not control all at the samt time?How to manage that problem to get access to all running CLI instances I did started to send Events into the right CLI window?

Or have I just to set a lock on that attach / FreeConsole code to handle each PID seperated one by one?Some kind of "right of way" like in traffic etc?Maybe you can tell me or show some code lines how to manage that better to run more than one instances at same time.Thank you.

greetz

Link to post
LCF-AT

Hi fearless,

thanks for this info but I'am not sure about that whether I could use it (as I think) and its also some C stuff (no clue about that) you know.So if I read it right then it wants to create some child processes for another CLI window/s but in my case I want to run diffrent instances seperated from each other.Otherwise lets say I do start 4 CLI windows (anyhow with that C code) then it will build child processes from first started CLI right?But what is when I have to exit the first CLI process?Then child processes get closed too or not?

greetz

Link to post
  • 1 month later...
LCF-AT

Hi guys,

so I'am still fighting to find a method to run/start multible CMD apps (starting works) and to send a CTRL_C_EVENT into every single started CMD app if I want to do that.

Maybe I start with a question about that CTRL_C_EVENT command.Normaly I am using it to tell the CMD window to stop the running CMD app.My question in this case is whether there is any diffrent between using the CTRL_C_EVENT and/or using a EXIT call (maybe taskmanager / kill process of CMD/app)?So I think to send a CTRL_C_EVENT is a more better solution than using a hard process kill but is there a diffrent?

1,) Normal case: I'am setup CreateProcess function paramters with the commandline paramters for the cmd and the app/tool (ffmpeg etc) and the path.

cmd.exe /c ffmpeg -hide_banner etc

Using cmd.exe with /c command...

/C      Carries out the command specified by string and then terminates

Lets say I am doing that a few times to run diffrent ffmpeg instances.So far no problem yet but now I wanna close diffrent still running ffmpeg processes in a soft and elegant method by sending a CTRL_C_EVENT command but for this I have to attach the console windows of each running process.I was trying to do that like I told before using that functions below in a own thread for every running ffmpeg instance....

invoke AttachConsole,PI.dwProcessId                      <-- PID
invoke SetConsoleCtrlHandler,NULL,TRUE                   <-- No PID
invoke GenerateConsoleCtrlEvent,CTRL_C_EVENT,NULL        <-- No PID
invoke GetExitCodeProcess,[PI.hProcess],addr ExitCode    <-- PID
invoke CloseHandle, [PI.hThread]                         <-- PID
invoke CloseHandle, [PI.hProcess]                        <-- PID
invoke FreeConsole                                       <-- No PID
invoke SetConsoleCtrlHandler,NULL,FALSE                  <-- No PID

...as you can see there are no PIDs I can set for diffrent functions what is a problem.The functions can be overlap by the threads are running and using them to etc you know.My question are now...

- What should I note if I use that functions above to exit a single console window if I have running more than one console window?

- Are there other methods?Maybe starting console apps without CMD.exe tool and just calling exit process (hard kill etc)?

Byte the way, I did noticed when I am using the code functions above that something is changed is no more in original state.I see that the "l" key on keyboard dosent pint the "l" anymore in any tools like notepad etc so instead of that the "l" key does pop up a CMD window!=?Not sure yet what it is to trigger that issue.

Anyway, so if you got some more ideas how to handle that problem to exit diffrent running console window processes in a smart / soft way then just tell me.

greetz

Link to post
deepzero

There is a difference, as programs can handle cntrl+c as they wish (including performing cleanup before terminating, or simply ignoring it). But I doubt your cmd-ffmpeg combo is doing that. If you just want to kick off a conversion and kill it if it takes too long, then the simplest solution imo is to ignore consoles and cmd and whatnot, and just use CreateProcess to launch ffmpeg with arguments directly and TerminateProcess it if it takes too long.

if you really need to write to its stdin or log its stdout, you can do that (check CreateProcess documentation).

As so often, though, I am not sure i 100% understand your motives and what you try to accomplish. ;) :D Is there any reason you are launching ffmpeg with cmd /C? What is your goal?

--

after reading the two year old thread above i am not really any wiser. Launching a subprocess and accessing its stdout is a common task, and msdn provides best practice for it: https://docs.microsoft.com/de-de/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output

  • Like 1
Link to post
LCF-AT

Tach deep,

why do you doubt that deep?Above you can see the functions I use to send CTRL+C command.So it takes a little while til the console process get closed (few seconds) but I think it does it with that cleanup.Ok, so you mean I shouldnt use CMD.exe itself anymore and just using TerminateProcess.I also thought about that but could be maybe to rough specially if something gets written on disk.Or what do you think?

So I have these goals.

- starting multible commandline tools instances from one main process (GUI) = Working using CreateProcess function

- A) Getting control of all console processes I did started to read informations / pipes (of all created console instances at same time) and to send infos like CTRL+C etc commands into any of them etc.

- B.) Not need to read pipes to get informations but need to send CTRL+C command into to exit the running console softly / clean.Otherwise I have to use the woodhammer method (kill process)

At the moment I'am just trying out method B more or less so I dont need to read infos from pipe yet.Just need to find a good method to exit running console instances nice and clean for 100%.I dont wanna get some running left overs in background or other issues.

greetz

 

Link to post
deepzero

So it occurred to me you are probably using it to dump some stream. And in that case the cntrl+c thing is indeed legitimate.

A process can only be attached to one console at a time, if you have several threads try to cntrl+c different windows, they will collide and weird things happen. So you need a global lock / mutex so that only one thread at a time does the AttachConsole dance.

If you pm me your streamdumping command or whatever it is you do with ffmpeg, i can try to make it work next weekend.

 

  • Like 1
Link to post
LCF-AT

Hi again,

so you mean if I wanna use that attach - freeconsole functions then I should execute that function loop ONLY ONCE at this time to prevent that overlapping I told beofre yes?

Example:

.data
THREDEXERUNS dd FALSE

Thread Proc
createprocess stuff
......
.while THREDEXERUNS == TRUE
	invoke Sleep,500    <--- others have to wait a half second in loop
.endw

mov THREDEXERUNS, TRUE   <--- Set Lock
invoke AttachConsole,PI.dwProcessId                      <-- PID
invoke SetConsoleCtrlHandler,NULL,TRUE                   <-- No PID
invoke GenerateConsoleCtrlEvent,CTRL_C_EVENT,NULL        <-- No PID
invoke GetExitCodeProcess,[PI.hProcess],addr ExitCode    <-- PID
invoke CloseHandle, [PI.hThread]                         <-- PID
invoke CloseHandle, [PI.hProcess]                        <-- PID
invoke FreeConsole                                       <-- No PID
invoke SetConsoleCtrlHandler,NULL,FALSE                  <-- No PID

mov THREDEXERUNS, FALSE   <--- Set Unlock
xor eax,eax
ret  ; <--- Thread exit

Like this?Just need to secure that last finish functions for the console itself to attch / send CTRL event / Setconsole as last function and then I give it free access again for the next waiting one right?I do not use CRIT_SECTION stuff here just wanna keep it simple here.But you mean it so right or similar right?

Question: So if I attach the console with PID X the I think it also means that all next execution functions like SetConsoleCrtlEvent is used for that attached X console app right?Will see whether it works and whether the "l" key dosent pop up a running visible CMD window to front anymore instead of printing the "l" in notepad and other apps.

PS: Dont have written much code to dump streams yet because just wanna run ffmpeg with paramters and create a controled exit you know.So at the moment it does work so far during my tests to run X instnaces at same time and to exit them as I wanted.Maybe just a random success because the attach functions didnt overlap by random of course.I will try it now with the lock.

greetz

Link to post
deepzero
Quote

But you mean it so right or similar right?

yes.

however, you should use a critical-section or something similar, because otherwise there is always a small chance you get unlucky and end up with a collision again. But the code you posted should as-is make this very very rare, yes. Test it and see if it works.

  • Like 1
Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...