How to capture display output from a command prompt in Windows at any moment?

39,514

Solution 1

You can do this in C easily enough, using the ReadConsoleOutputCharacter function.

Solution 2

Ok, there may be a more elegant way to do this, but this will work assuming you have PowerShell.

Create a PowerShell script file called Get-ConsoleAsText.ps1 that contains the script below. Note, I did not create this script. I found it at Windows PowerShell Blog - Capture console screen.

#################################################################################################################
# Get-ConsoleAsText.ps1
#
# The script captures console screen buffer up to the current cursor position and returns it in plain text format.
#
# Returns: ASCII-encoded string.
#
# Example:
#
# $textFileName = "$env:temp\ConsoleBuffer.txt"
# .\Get-ConsoleAsText | out-file $textFileName -encoding ascii
# $null = [System.Diagnostics.Process]::Start("$textFileName")
#

# Check the host name and exit if the host is not the Windows PowerShell console host.
if ($host.Name -ne 'ConsoleHost')
{
  write-host -ForegroundColor Red "This script runs only in the console host. You cannot run this script in $($host.Name)."
  exit -1
}

# Initialize string builder.
$textBuilder = new-object system.text.stringbuilder

# Grab the console screen buffer contents using the Host console API.
$bufferWidth = $host.ui.rawui.BufferSize.Width
$bufferHeight = $host.ui.rawui.CursorPosition.Y
$rec = new-object System.Management.Automation.Host.Rectangle 0, 0, ($bufferWidth), $bufferHeight
$buffer = $host.ui.rawui.GetBufferContents($rec)

# Iterate through the lines in the console buffer.
for($i = 0; $i -lt $bufferHeight; $i++)
{
  for($j = 0; $j -lt $bufferWidth; $j++)
  {
    $cell = $buffer[$i, $j]
    $null = $textBuilder.Append($cell.Character)
  }
  $null = $textBuilder.Append("`r`n")
}

return $textBuilder.ToString()

If you call the PowerShell script by itself, it will read the console buffer and write it back to the screen

PowerShell -noprofile -sta -command "C:\Scripts\Get-ConsoleAsText.ps1"

You can also call it like this to capture the contents to a file:

PowerShell -noprofile -sta -command "C:\Scripts\Get-ConsoleAsText.ps1 | Out-File MyOutput.txt -encoding ascii"

If you want to process it and perform some action within the batch file, you can call it and process the output using a FOR command. I will leave that exercise to you.

So, for example, your batch file would look like this to capture the console output to a file:

c:\myProgramInC.exe
echo "Ending with error"
PowerShell -noprofile -sta -command "C:\Scripts\Get-ConsoleAsText.ps1 | Out-File MyOutput.txt -encoding ascii"
PAUSE

Solution 3

Quick trick would be to execute in context of for /f, you do not even need a batch file for that, you could execute directly from cmd line:
for /f "tokens=*" %F in ('c:\myProgramInC.exe') do @echo %F >>myPrograminC.log

This will suppress all output until your program abends and only then would write all messages to file. If your app writes log messages infrequently (or fails quickly :-)) it should work. I tested it with 10 000 lines.

Batch code below is based on same idea - please note that even it writes only 5 last lines, it still has to scan through all of them so I'm not sure it's any better than above 1 liner.

@echo off
setlocal 

for /f "tokens=*" %%P in ('c:\myProgramInC.exe') do (
 for /L %%C in (4,-1,1) do (
  set /A next=%%C+1
  call set line_%%next%%=%%line_%%C%%
 )
 set line_1=%%P
)
echo program ended abnormally! %date% %time%
for /L %%C in (5,-1,1) do call echo %%line_%%C%% 

Solution 4

Finally this is what I get to make it work. Following the advise of Harry Johnston I searched about the way ReadConsoleOutputCharacter function works and I got the next program running.

#include "stdafx.h"
#include <iostream>
#include <windows.h>
#include <fstream>

#define BUFFER_SIZE 50000


int main(){
    HANDLE hOut;
    COORD location = {0, 0};
    char *buffer=NULL;
    DWORD numberRead;
    std::ofstream fileLog;

    buffer = new TCHAR[BUFFER_SIZE];

    if ( buffer==NULL )
    {
        printf("\nError: memory could not be allocated.\n");
        return 1;
    }

    fileLog.open ("logger.txt");

    if ( fileLog.fail() )
    {
        printf("\nError: file could not be opened.\n");
        return 1;
    }

    hOut = GetStdHandle(STD_OUTPUT_HANDLE);

    ReadConsoleOutputCharacter(hOut, buffer, BUFFER_SIZE, location, &numberRead);

    fileLog << buffer ;

    free(buffer);
    fileLog.close();

    return 0;
}

Much of the code I found it in internet and right now I don't have the URL (besides it's from another forum site and I don't know if it's OK to make references to competitor sites) but with a single search in google wouldn't be difficult to find it.

I added the part where it writes to a file and allocation of memory. It run's perfectly in VS C++ 6.

This works fine for me although could be better. Two things aren't very nice like

  1. It writes all the content from the buffer to a file but it doesn't keep the newlines so I get a text file with all the information in ONE single line.
  2. Can not be defined how much lines to be captured. It just takes everything from the beginnig of the buffer to the point where the program starts, wich is not bad but since this app should run in different servers where each one has a different buffer for each set of command prompt windows, is not much efficient just to use a buffer of 50000 bytes when in some cases the buffer is smaller. I think that could be a lot but since it is allocated in real time maybe is not so wrong to use as much memory.

Solution 5

If any error messages are going to be reported back to the command output stream, it can be easily redirected via Redirection Operators for STDOUT (Standard Output) and STDERR (Standard Errors). Depending on whether or not you want to capture output and/or errors, is up to you. More than likely, you are looking for something like:

  • command 2>> filename

The "2" is a part of the redirection operator, and indicates that STDERR will be redirected and appended to filename.

Sources, examples, and documentation:

  1. http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/redirection.mspx
  2. http://ss64.com/nt/syntax-redirection.html
Share:
39,514
Admin
Author by

Admin

Updated on July 29, 2020

Comments

  • Admin
    Admin almost 4 years

    I'm tring to capture de information shown in a command prompt (cmd window) at a specific moment and send it to a text file.

    I have an app in C/C++ that is launched by a batch script like this:

    c:\myProgramInC.exe
    echo "Ending with error"
    PAUSE
    

    myProgramInC.exe is always running (with an infinite loop) so if my script gets to the echo command it means my app ended with an abend.

    What I want to get is the previous lines before the end execution of the script since my myProgramInC.exe always prints info about what is going on and it would be very useful to see what it was happening when the error ocurred. Something like this

    c:\myProgramInC.exe
    **** Execution of process to get te previous N lines in the command prompt ****
    echo "Ending with error"
    PAUSE
    

    I know the cmd window have a buffer and I've been thinking to capture all of this data from such buffer. It is any way to get this info as text to store it in a file?

    I'v been trying with something more professional shuch as to make a dump of the memory with ProcDump but since I have various myProgramInC.exe running at the same time (each one stored in a different location) I just get the message "Multiple processes match the specified name." and the rest of the options are just not useful for me since my app doesn't get unresponsive, it simply ends.

    Any ideas?

  • Admin
    Admin over 11 years
    Thanks artpet but that's not an option since all the time my app would be writing on disk and it is a critical time response program, besides there are many instances of it and having around 30 programs writting down all the time to disk would be very stressful to the server resources. That's why it should write down on disk only when necessary.
  • artapet
    artapet over 11 years
    What is that exact moment you need to capture output of your program ? Is it a specific string or some timeout ? Also, procdump accepts PId of a running program instead of name. You'd obtain Process Id either using windows task manager or Process Explorer tool from Sysinternals.
  • Admin
    Admin over 11 years
    I need to capture the last lines printed by my app. It shows info about what it was working on with and I can make some analysis to try to get what the problem is. In fact, most of the time it is just a matter of array overflow but since the my app works with info given by an external app it would be easy for me to show that my app had an abend because of the wrong information given to it.
  • Admin
    Admin over 11 years
    Using windows task manager or Process Explorer is not an option since the idea is to get the batch to capture the data automatically when ending; and in fact, that PAUSE that I used in the example of my batch script will not exist assuring that the commmand prompt close and a third app (running like some kind of daemon) will identify this and open a new command prompt making sure myProgramInC.exe will be running always, even after an error occurs.
  • Admin
    Admin over 11 years
    I'm gonna take a look on this function and maybe it would be the best option. I'm gonna let you know if this worked for me back at work on monday.
  • Admin
    Admin over 11 years
    I like the first option wmz but I don't know if this will impact with the time response since it'll have another background instance of command prompt (the FOR loop) running my app. But since it could be a quick trick is worth give it a try.
  • Admin
    Admin over 11 years
    I've proved this option and there is one incovenience that makes it not useful according to what is needed. The output is never showed and it's important to see it since it's an indicator that all is working... or not. It even doesn't shows up it's necessary to end the script when abend occurs.
  • Michael
    Michael over 5 years
    Oh bless you. Just bless you. Give yourself 1000 up votes.
  • BuvinJ
    BuvinJ about 3 years
    Awesome! This captures messages directed by sub processes to the CON stream rather than only stdout/err. The fact it can be embedded in a batch file and it works on a CMD.exe window in addition to a PS terminal is fantastic bonus.