0

I want to write a batch utility to copy the output of a command prompt window to a file. I run my command prompt windows with the maximum depth of 9999 lines, and occasionally I want to grab the output of a command whose output is off-screen. I can do this manually with the keys Ctrl-A, Ctrl-Cand then pasting the result into Notepad - I just want to automate it in a batch file with a call to:

SaveScreen <text file name>  

I know I can do it with redirection, but that would involve knowing that I will need to save the output of a batch command sequence beforehand.

So if I had a batch script:

call BuildPhase1.bat
if "%ErrorLevel% gtr 0 goto :ErrorExit
call BuildPhase2.bat
if "%ErrorLevel% gtr 0 goto :ErrorExit
call BuildPhase3.bat
if "%ErrorLevel% gtr 0 goto :ErrorExit

I could write:

cls
call BuildPhase1.bat
if "%ErrorLevel% gtr 0 call SaveScreen.bat BuildPhase1.err & goto :ErrorExit
call BuildPhase2.bat
if "%ErrorLevel% gtr 0 call SaveScreen.bat BuildPhase2.err & goto :ErrorExit
call BuildPhase3.bat
if "%ErrorLevel% gtr 0 call SaveScreen.bat BuildPhase3.err & goto :ErrorExit

or I could just type SaveScreen batch.log when I see that a run has failed.

My experiments have got me this far:

<!-- : Begin batch script

    @cscript //nologo "%~f0?.wsf" //job:JS
    @exit /b

----- Begin wsf script --->
<package>
  <job id="JS">
    <script language="JScript">

      var oShell = WScript.CreateObject("WScript.Shell");
      oShell.SendKeys ("hi folks{Enter}") ;

      oShell.SendKeys ("^A") ;              // Ctrl-A  (select all)
      oShell.SendKeys ("^C") ;              // Ctrl-C  (copy)
      oShell.SendKeys ("% ES") ;            // Alt-space, E, S  (select all via menu)
      oShell.SendKeys ("% EY") ;            // Alt-space, E, Y  (copy via menu)

      // ... invoke a notepad session, paste the clipboard into it, save to a file

      WScript.Quit () ; 
    </script>
  </job>
</package>

My keystrokes are making it to the command prompt so presumably I have the correct window focused - it just seems to be ignoring the Ctrl and Alt modifiers. It also recognises Ctrl-C but not Ctrl-A. Because it has ignored the Ctrl-A to select all the text, the Ctrl-C causes the batch file to think it has seen a break command.

I've seen the other answers like this one but they all deal with methods using redirection, rather than a way of doing it after the fact "on demand".

* UPDATE *

On the basis of @dxiv's pointer, here is a batch wrapper for the routine:

Get-ConsoleAsText.bat

::  save the contents of the screen console buffer to a disk file.

    @set "_Filename=%~1"
    @if "%_Filename%" equ "" @set "_Filename=Console.txt" 

    @powershell Get-ConsoleAsText.ps1 >"%_Filename%"
    @exit /b 0

The Powershell routine is pretty much as was presented in the link, except that:

  • I had to sanitise it to remove some of the more interesting character substitutions the select/copy/paste operation introduced.

  • The original saved the trailing spaces as well. Those are now trimmed.

Get-ConsoleAsText.ps1

# Get-ConsoleAsText.ps1  (based on: https://devblogs.microsoft.com/powershell/capture-console-screen/)
#  
# 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")
#

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

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

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

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

return $textBuilder.ToString()
rossmcm
  • 5,493
  • 10
  • 55
  • 118
  • Maybe something like [Capture console screen](https://devblogs.microsoft.com/powershell/capture-console-screen/) (using powershell). – dxiv May 03 '20 at 00:01
  • It looks as if it can only access the visible screen area at most. It seems to take a rectangular region of the screen and then returns the text in that region. It seems a strange way to do it. – rossmcm May 03 '20 at 01:36
  • [`PSHostRawUserInterface.GetBufferContents`](https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.host.pshostrawuserinterface.getbuffercontents?view=powershellsdk-1.1.0) does get the whole *buffer* regardless of what's visible in the console window. And it works on the host console, which can be a cmd prompt, not necessarily PowerShell's own console. – dxiv May 03 '20 at 02:08
  • Remarks If the rectangle is completely outside of the screen buffer, a BufferCell array of zero rows and column will be returned. If the rectangle is partially outside of the screen buffer, the area where the screen buffer and rectangle overlap will be read and returned. The size of the returned array is the same as that of r. Each BufferCell in the non-overlapping area of this array is set as follows: – rossmcm May 03 '20 at 02:54
  • (excerpt from the link) is the full text available elsewhere? – rossmcm May 03 '20 at 02:55
  • Right, but the emphasis in that remark is on "*buffer*". It may be an unfortunate choice of words, but it refers to the console *buffer*, not the *window*. You can use the code in the link I posted to verify that it does in fact capture the entire buffer. – dxiv May 03 '20 at 03:00
  • OK, thanks. All good. Repackage your comment as an answer and I'll give it the green tick. I cleaned up the linked code a bit and will add it to the question. – rossmcm May 03 '20 at 07:53
  • Glad it helped, and I learned something new today, too ;-) – dxiv May 03 '20 at 08:11
  • for `Ctrl-A`, `Ctrl-V`, and `Ctrl-C` to work, you have to change the properties (or defaults for a permanent change). Tab "Options", check `Enable Ctrl key Shortcuts` in "Edit Options" – Stephan May 03 '20 at 08:18

1 Answers1

2

The contents of the console buffer can be retrieved with the PS script from PowerShell's team blog Capture console screen mentioned in a comment, now edited into OP's question.

The last line could also be changed to copy the contents to the clipboard instead of returning it.

Set-Clipboard -Value $textBuilder.ToString()

As a side note, the reasons for using a StringBuilder rather than direct concatenation are discussed in How does StringBuilder work internally in C# and How the StringBuilder class is implemented.

dxiv
  • 16,984
  • 2
  • 27
  • 49