Capturing the Console Buffer
Vladimir Averkin’s https://blogs.msdn.com/b/powershell/archive/2009/01/11/colorized-capture-of-console-screen-in-html-and-rtf.aspx provides the code. Here’s the core magic.
$Host.UI.RawUI.GetBufferContents(System.Management.Automation.Host.Rectangle) will return a two-dimensional character array of the contents of the buffer.
This entry cleans up the HTML function by creating XML instead of using System.Text.StringBuilder to append strings. Sadly, I don’t know RTF at all, so this is merely a merging of that code into a single function.
# .synopsis
# Capture console screen buffer.
#
# .returns
# HTML File
#
# .example
# PSH> Get-ConsoleBufferAsHtml
#
# C:\Users\timdunn\AppData\Local\Temp\Console-2013-09-20_150204.html
#
# .Notes
# See https://blogs.msdn.com/b/powershell/archive/2009/01/11/colorized-capture-of-console-screen-in-html-and-rtf.aspx
#
# .param Path
# Path to save screen buffer as HTML. Defaults to "$env:TEMP\Console-<datestamp>", with suffix as appropriate to file format.
#
# .param Full
# Save entire console buffer. Default is to only capture the current window.
#
# .param Preview
# Open the file in the default handler upon successful capture.
#
# .param HTML
# Save console buffer in HTML format. Default is to save in ASCII.
#
# .param RTF
# Save console buffer in RTF format. Default is to save in ASCII.
#
# .param Test
# Generate test pattern, save to file, then display files.
param (
[string]$Path = "$env:temp\Console-$(Get-Date -Format yyyy-MM-dd_HHmmss).",
[switch]$Full,
[switch]$Preview,
[switch]$HTML,
[switch]$RTF,
[switch]$Test
);
function Get-ConsoleBuffer {
#region header
param (
[string]$Path = "$env:temp\Console-$(Get-Date -Format yyyy-MM-dd_HHmmss).",
[switch]$Full,
[switch]$Preview,
[switch]$HTML,
[switch]$RTF,
[bool]$_boolFull = $false,
[bool]$_boolPreview = $false,
[bool]$_boolHTML = $false,
[bool]$_boolRTF = $false
);
#endregion
#region html functions
# The Windows PowerShell console host redefines DarkYellow and DarkMagenta colors and uses them as defaults.
# The redefined colors do not correspond to the color names used in HTML, so they need to be mapped to digital color codes.
function Normalize-HtmlColor ($color) {
if ($color -eq "DarkYellow") { "#eeedf0"; }
elseif ($color -eq "DarkMagenta") { "#012456"; }
else { $color; }
}
function Add-HtmlSpan ($Text, $ForegroundColor = "DarkYellow", $BackgroundColor = "DarkMagenta") {
$ForegroundColor= Normalize-HtmlColor $ForegroundColor;
$BackgroundColor= Normalize-HtmlColor $BackgroundColor;
$node = $script:xml.CreateElement("span");
$node.SetAttribute('style', "font-family:Courier New;color:$ForegroundColor;background:$backgroundColor");
$node.InnerText = $text;
$script:xml.LastChild.AppendChild($node) | Out-Null;
}
function Add-HtmlBreak { $script:xml.LastChild.AppendChild($script:xml.CreateElement("br")) | Out-Null; }
#endregion
#region rtf functions
function Get-RtfColorIndex ([string]$color) {
switch ($color) {
'DarkBlue' { 2 }
'DarkGreen' { 3 }
'DarkCyan' { 4 }
'DarkRed' { 5 }
'DarkMagenta' { 6 }
'DarkYellow' { 7 }
'Gray' { 8 }
'DarkGray' { 9 }
'Blue' { 10 }
'Green' { 11 }
'Cyan' { 12 }
'Red' { 13 }
'Magenta' { 14 }
'Yellow' { 15 }
'White' { 16 }
'Black' { 17 }
default { 0 }
}
}
function New-RtfBuilder {
# Initialize the RTF string builder.
$script:rtfBuilder = new-object system.text.stringbuilder
# Set the desired font
$fontName = 'Lucida Console'
& {
# Append RTF header
$script:rtfBuilder.Append("{\rtf1\fbidis\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fnil\fcharset0 $fontName;}}")
$script:rtfBuilder.Append("`r`n")
# Append RTF color table which will contain all Powershell console colors.
# script version
$script:rtfBuilder.Append('{\colortbl;red0\green0\blue128;\red0\green128\blue0;\red0\green128\blue128;\red128\green0\blue0;\red1\green36\blue86;\red238\green237\blue240;\red192\green192\blue192;\red128\green128\blue128;\red0\green0\blue255;\red0\green255\blue0;\red0\green255\blue255;\red255\green0\blue0;\red255\green0\blue255;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue0;}')
# Append RTF document settings.
$script:rtfBuilder.Append('\viewkind4\uc1\pard\ltrpar\f0\fs23 ')
} | Out-Null;
}
# Append line break to RTF builder
function Add-RtfBreak {
$script:rtfBuilder.Append("\shading0\cbpat$(Get-RtfColorIndex $currentBackgroundColor)\par`r`n") | Out-Null;
}
# append text to RTF builder
function Add-RtfBlock ($Text, $ForegroundColor = "DarkYellow", $BackgroundColor = "DarkMagenta") {
$ForegroundColor= Get-RtfColorIndex $ForegroundColor;
$BackgroundColor= Get-RtfColorIndex $BackgroundColor;
$script:rtfBuilder.Append("{\cf$ForegroundColor") | Out-Null;
$script:rtfBuilder.Append("\chshdng0\chcbpat$BackgroundColor") | Out-Null;
$script:rtfBuilder.Append("$Text}") | Out-Null;
}
#endregion
#region core code
# Check the host name and exit if the host is not the Windows PowerShell console host.
if ($host.Name -ne 'ConsoleHost') {
Write-Warning "$((Get-Variable -ValueOnly -Name MyInvocation).MyCommand)runs only in the console host. You cannot run this script in $($Host.Name).";
return;
}
# handle [switch] parameters in nested functions
if (!$Full) { $Full = $_boolFull; }
if (!$Preview) { $Preview = $_boolPreview; }
if (!$HTML) { $HTML = $_boolHTML; }
if (!$RTF) { $RTF = $_boolRTF; }
# initialize document name and object
if ($HTML) {
[xml]$script:xml = "<pre style='MARGIN: 0in 10pt 0in;line-height:normal' />";
$Path += 'html';
$RTF = $false;
}
elseif ($RTF) {
New-RtfBuilder;
$Path += 'rtf';
}
else {
$fileBuilder = New-Object system.Text.StringBuilder;
$Path += 'txt';
}
# Grab the console screen buffer contents using the Host console API.
$bufferWidth = $Host.UI.RawUI.BufferSize.Width
$bufferHeight = $Host.UI.RawUI.CursorPosition.Y
# Line at which capture starts is either top of buffer or top of window.
if ($Full) { $startY = 0; }
elseif (($startY = $bufferHeight - $Host.UI.RawUI.WindowSize.Height) -lt 0) { $startY = 0; }
$rec = New-Object System.Management.Automation.Host.Rectangle 0,$startY,($bufferWidth - 1),$bufferHeight
$buffer = $Host.UI.RawUI.GetBufferContents($rec);
# Iterate through the lines in the console buffer.
for($i = 0; $i -lt $bufferHeight; $i++)
{
$stringBuilder = New-Object System.Text.StringBuilder;
if ($HTML -or $RTF) {
# Track the colors to identify spans of text with the same formatting.
$currentForegroundColor = $buffer[$i, 0].ForegroundColor;
$currentBackgroundColor = $buffer[$i, 0].BackgroundColor;
}
for($j = 0; $j -lt $bufferWidth; $j++) {
$cell = $buffer[$i,$j];
# If the colors change, generate an HTML span and append it to the HTML string builder.
if (($HTML -or $RTF) -and (($cell.ForegroundColor -ne $currentForegroundColor) -or ($cell.BackgroundColor -ne $currentBackgroundColor))) {
if ($HTML) { Add-HtmlSpan -Text $stringBuilder.ToString() -ForegroundColor $currentForegroundColor -BackgroundColor $currentBackgroundColor; }
elseif ($RTF) { Add-RtfBlock -Text $stringBuilder.ToString() -ForegroundColor $currentForegroundColor -BackgroundColor $currentBackgroundColor; }
# Reset the span builder and colors.
$stringBuilder = New-Object System.Text.StringBuilder;
$currentForegroundColor = $cell.ForegroundColor;
$currentBackgroundColor = $cell.BackgroundColor;
}
if ($RTF) {
switch ($cell.Character) {
"`t" { $rtfChar = '\tab' }
'\' { $rtfChar = '\\' }
'{' { $rtfChar = '\{' }
'}' { $rtfChar = '\}' }
default { $rtfChar = $cell.Character; }
}
$stringBuilder.Append($rtfChar) | Out-Null;
}
else { $stringBuilder.Append($cell.Character) | Out-Null; }
}
if ($HTML) {
Add-HtmlSpan -Text $stringBuilder.ToString() -ForegroundColor $currentForegroundColor -BackgroundColor $currentBackgroundColor;
Add-HtmlBreak;
}
elseif ($RTF) {
Add-RtfBlock -Text $stringBuilder.ToString() -ForegroundColor $currentForegroundColor -BackgroundColor $currentBackgroundColor;
Add-RtfBreak;
}
else {
$fileBuilder.Append(($stringBuilder.ToString() -replace ".$", "`r`n")) | Out-Null;
}
}
& {
if ($HTML) { $script:xml.OuterXml; }
elseif ($RTF) { $script:rtfBuilder.ToString() + '}'; }
else { $fileBuilder.ToString(); }
} | Out-File -FilePath $Path -Encoding ascii;
if (Test-Path -Path $Path) {
if ($Preview) { Invoke-Item $Path; }
$Path;
}
else { Write-Warning "Unable to save to -Path $Path"; }
#endregion
}
# if we execute this file instead of dot-source it
if ($MyInvocation.invocationName -ne '.') {
if ($Test) {
[System.Enum]::GetValues('System.ConsoleColor') | % { Write-Host ((" " + $_.ToString() + (" " * 8)).SubString(0,11) + " ") -NoNewline; }
Write-Host '';
foreach ($f in [System.Enum]::GetValues('System.ConsoleColor') ) {
foreach ($b in [System.Enum]::GetValues('System.ConsoleColor')) {
Write-Host -ForegroundColor $f -BackgroundColor $b " $($f.tostring().substring(0,3)) on $($b.tostring().substring(0,3)) " -NoNewline;
}
Write-Host '';
}
& {
Get-ConsoleBuffer -HTML;
Get-ConsoleBuffer -RTF;
Get-ConsoleBuffer;
} | % {
Invoke-Item $_;
Out-Host -InputObject $_;
}
}
else {
Get-ConsoleBuffer -_boolFull $Full -_boolHtml $HTML -_boolRTF $RTF -_boolPreview $Preview -Path $Path;
}
}
Comments
- Anonymous
January 18, 2018
The comment has been removed