Functions/GenXdev.Helpers/ConsoleReader.cs
|
// ################################################################################
// Part of PowerShell module : GenXdev.Helpers // Original cmdlet filename : ConsoleReader.cs // Original author : René Vaessen / GenXdev // Version : 2.0.2025 // ################################################################################ // Copyright (c) René Vaessen / GenXdev // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ################################################################################ using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.ComponentModel; namespace GenXdev.Helpers { /// <summary> /// <para type="synopsis"> /// Provides static methods for reading text content from the Windows console screen buffer. /// </para> /// /// <para type="description"> /// The ConsoleReader class offers functionality to extract text from specific regions of the console /// screen buffer using Windows API calls. It includes methods for reading rectangular areas of text /// and retrieving console buffer information. /// </para> /// </summary> public static class ConsoleReader { /// <summary> /// <para type="synopsis"> /// Reads a rectangular region of text from the console screen buffer. /// </para> /// /// <para type="description"> /// This method extracts text content from a specified rectangular area of the console screen buffer /// using the Windows ReadConsoleOutput API. It returns each line of the region as a string. /// The method performs validation on input parameters and adjusts the region if it extends beyond /// the buffer boundaries. /// </para> /// /// <param name="x"> /// The left coordinate (column) of the rectangular region to read from the console buffer. /// Must be non-negative and within the buffer width. /// </param> /// <param name="y"> /// The top coordinate (row) of the rectangular region to read from the console buffer. /// Must be non-negative and within the buffer height. /// </param> /// <param name="width"> /// The width (number of columns) of the rectangular region to read. /// Must be positive and will be adjusted if it exceeds buffer boundaries. /// </param> /// <param name="height"> /// The height (number of rows) of the rectangular region to read. /// Must be positive and will be adjusted if it exceeds buffer boundaries. /// </param> /// /// <returns> /// An enumerable collection of strings, where each string represents one line of text /// from the specified rectangular region of the console buffer. /// </returns> /// /// <exception cref="ArgumentException"> /// Thrown when width or height are not positive, or when coordinates are negative, /// or when the adjusted region has invalid dimensions. /// </exception> /// <exception cref="InvalidOperationException"> /// Thrown when the console handle cannot be obtained. /// </exception> /// <exception cref="Win32Exception"> /// Thrown when Windows API calls fail, with the corresponding error code. /// </exception> /// <exception cref="OutOfMemoryException"> /// Thrown when memory allocation for the buffer fails. /// </exception> /// </summary> public static IEnumerable<string> ReadFromBuffer(short x, short y, short width, short height) { // Validate input parameters to ensure they are within acceptable ranges if (width <= 0 || height <= 0) throw new ArgumentException("Width and height must be positive values"); if (x < 0 || y < 0) throw new ArgumentException("X and Y coordinates must be non-negative"); // Obtain the handle to the standard output console var consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE); if (consoleHandle == IntPtr.Zero || consoleHandle == new IntPtr(-1)) throw new InvalidOperationException("Cannot get console handle"); // Retrieve information about the console screen buffer CONSOLE_SCREEN_BUFFER_INFO csbi; if (!GetConsoleScreenBufferInfo(consoleHandle, out csbi)) { int code = Marshal.GetLastWin32Error(); throw new Win32Exception(code, "Cannot get console screen buffer info"); } // Check that the starting coordinates are within the buffer boundaries if (x >= csbi.dwSize.X || y >= csbi.dwSize.Y) throw new ArgumentOutOfRangeException( $"Coordinates ({x},{y}) are outside console buffer. " + $"Buffer size: {csbi.dwSize.X}x{csbi.dwSize.Y}"); // Adjust the width and height to not exceed buffer boundaries if (x + width > csbi.dwSize.X) width = (short)(csbi.dwSize.X - x); if (y + height > csbi.dwSize.Y) height = (short)(csbi.dwSize.Y - y); // Verify that after adjustments, we still have a valid region to read if (width <= 0 || height <= 0) throw new ArgumentException( $"Invalid read region after bounds checking. " + $"Adjusted size: {width}x{height}"); // Allocate unmanaged memory for the character information buffer IntPtr buffer = Marshal.AllocHGlobal( width * height * Marshal.SizeOf(typeof(CHAR_INFO))); if (buffer == IntPtr.Zero) throw new OutOfMemoryException(); try { // Set up coordinates for the buffer and screen region COORD coord = new COORD { X = 0, Y = 0 }; SMALL_RECT rc = new SMALL_RECT { Left = x, Top = y, Right = (short)(x + width - 1), Bottom = (short)(y + height - 1) }; COORD size = new COORD { X = width, Y = height }; // Read the console output into the allocated buffer if (!ReadConsoleOutput(consoleHandle, buffer, size, coord, ref rc)) { int code = Marshal.GetLastWin32Error(); throw new Win32Exception(code, $"ReadConsoleOutput failed. Error code: {code}"); } // Iterate through each row of the buffer IntPtr ptr = buffer; for (int h = 0; h < height; h++) { // Build a string for the current row System.Text.StringBuilder sb = new System.Text.StringBuilder(width); for (short w = 0; w < width; w++) { // Extract the character information from the buffer CHAR_INFO ci = (CHAR_INFO)Marshal.PtrToStructure( ptr, typeof(CHAR_INFO)); // Append the Unicode character to the string builder sb.Append(ci.UnicodeChar); // Move to the next character in the buffer ptr = new IntPtr(ptr.ToInt64() + Marshal.SizeOf(typeof(CHAR_INFO))); } // Return the completed row string yield return sb.ToString(); } } finally { // Free the allocated unmanaged memory Marshal.FreeHGlobal(buffer); } } /// <summary> /// <para type="synopsis"> /// Retrieves information about the current console screen buffer. /// </para> /// /// <para type="description"> /// This method obtains detailed information about the console screen buffer using the /// Windows GetConsoleScreenBufferInfo API. It returns a formatted string containing /// buffer size, window coordinates, cursor position, and maximum window size. /// </para> /// /// <returns> /// A string containing formatted console buffer information including size, window /// coordinates, cursor position, and maximum window size. Returns an error message /// if the console handle cannot be obtained or buffer info retrieval fails. /// </returns> /// </summary> public static string GetConsoleInfo() { // Attempt to get the handle for the standard output console var consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE); if (consoleHandle == IntPtr.Zero || consoleHandle == new IntPtr(-1)) return "Cannot get console handle"; // Retrieve the console screen buffer information CONSOLE_SCREEN_BUFFER_INFO csbi; if (!GetConsoleScreenBufferInfo(consoleHandle, out csbi)) { int code = Marshal.GetLastWin32Error(); return $"Cannot get console screen buffer info. Error: {code}"; } // Format and return the buffer information as a string return $"Buffer Size: {csbi.dwSize.X}x{csbi.dwSize.Y}, " + $"Window: ({csbi.srWindow.Left},{csbi.srWindow.Top})-" + $"({csbi.srWindow.Right},{csbi.srWindow.Bottom}), " + $"Cursor: ({csbi.dwCursorPosition.X},{csbi.dwCursorPosition.Y}), " + $"Max Window: {csbi.dwMaximumWindowSize.X}x{csbi.dwMaximumWindowSize.Y}"; } [StructLayout(LayoutKind.Sequential)] private struct CHAR_INFO { [MarshalAs(UnmanagedType.U2)] public char UnicodeChar; [MarshalAs(UnmanagedType.U2)] public short Attributes; } [StructLayout(LayoutKind.Sequential)] private struct COORD { public short X; public short Y; } [StructLayout(LayoutKind.Sequential)] private struct SMALL_RECT { public short Left; public short Top; public short Right; public short Bottom; } [StructLayout(LayoutKind.Sequential)] private struct CONSOLE_SCREEN_BUFFER_INFO { public COORD dwSize; public COORD dwCursorPosition; public short wAttributes; public SMALL_RECT srWindow; public COORD dwMaximumWindowSize; } [DllImport("kernel32.dll", SetLastError = true)] private static extern bool ReadConsoleOutput(IntPtr hConsoleOutput, IntPtr lpBuffer, COORD dwBufferSize, COORD dwBufferCoord, ref SMALL_RECT lpReadRegion); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GetStdHandle(int nStdHandle); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool GetConsoleScreenBufferInfo(IntPtr hConsoleOutput, out CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo); private const int STD_OUTPUT_HANDLE = -11; } } |