简体   繁体   中英

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

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?

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%% 

您可以使用ReadConsoleOutputCharacter函数在C语言中轻松完成此操作。

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

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.

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

One thing that comes in minde immediately is redirecting output of your command line to to a file:

c:\myProgramInC.exe >> myProgramInC.log

then you can see what is in log file.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM