Private/Console.ps1
|
# What a Technician sees while a Run is happening. # # The Report is the product of a Run; this is only the running commentary, captured into # console.log by the transcript. Nothing here decides anything, and no Check calls it: # Findings are returned as values and the Run displays them. # One Severity, one colour, in one place. The Report renders the same decision as CSS # classes; these are the console's half of it. $script:SeverityColour = @{ OK = 'Green'; INFO = 'Gray'; WARN = 'Yellow'; FAIL = 'Red' } # The console's own switches, as Windows numbers them (SetConsoleMode, on the input handle). $script:ConsoleStdInputHandle = -10 $script:ConsoleQuickEditMode = 0x0040 $script:ConsoleExtendedFlags = 0x0080 function Initialize-ConsoleNativeMethod { <# .SYNOPSIS Loads the three Windows calls that read and set a console's mode. Once, on first use. .DESCRIPTION Not at import: importing this module must do nothing but define functions. #> [CmdletBinding()] param() if ('Gutcheck.ConsoleNative' -as [type]) { return } Add-Type -Namespace Gutcheck -Name ConsoleNative -MemberDefinition @' [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr GetStdHandle(int nStdHandle); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); '@ } function Get-ConsoleModeWithoutQuickEdit { <# .SYNOPSIS A console mode with QuickEdit taken out, and nothing else about it changed. Pure. .DESCRIPTION Windows only honours a change to QuickEdit when the extended-flags bit is set in the same call, so that bit goes in with it. #> [CmdletBinding()] [OutputType([uint32])] param([Parameter(Mandatory)][uint32]$Mode) [uint32](($Mode -band (-bnot [uint32]$script:ConsoleQuickEditMode)) -bor $script:ConsoleExtendedFlags) } function Disable-ConsoleQuickEdit { <# .SYNOPSIS Stops a click in the console window from freezing the Run. Returns what the mode was, for Restore-ConsoleMode, or $null when there was nothing to change. .DESCRIPTION In a classic console window a click starts a text selection, and while one is active Windows blocks every program that writes to the window. The program stands still at its next line of output until somebody presses Enter. The Elevated Part runs in such a window, and one click on it - to bring it to the front, to see what it is - left the Main Part waiting out its deadline for a process that was fine. With QuickEdit off the window can still be read, scrolled and marked from its menu; it just no longer stops what is running in it. Windows Terminal never had the problem and ignores the switch. Never throws: no console, a redirected input, a host that is no console at all - each is a Run with no window to click on. #> [CmdletBinding()] param() try { Initialize-ConsoleNativeMethod $handle = [Gutcheck.ConsoleNative]::GetStdHandle($script:ConsoleStdInputHandle) [uint32]$mode = 0 if (-not [Gutcheck.ConsoleNative]::GetConsoleMode($handle, [ref]$mode)) { return $null } $wanted = Get-ConsoleModeWithoutQuickEdit -Mode $mode if ($wanted -eq $mode) { return $null } if (-not [Gutcheck.ConsoleNative]::SetConsoleMode($handle, $wanted)) { return $null } $mode } catch { $null } } function Restore-ConsoleMode { <# .SYNOPSIS Puts a console back the way Disable-ConsoleQuickEdit found it. Never throws. #> [CmdletBinding()] param([AllowNull()]$Mode) if ($null -eq $Mode) { return } try { Initialize-ConsoleNativeMethod $handle = [Gutcheck.ConsoleNative]::GetStdHandle($script:ConsoleStdInputHandle) $null = [Gutcheck.ConsoleNative]::SetConsoleMode($handle, [uint32]$Mode) } catch { } } function Show-Banner { [CmdletBinding()] param($Version) # The version goes on its own line rather than inside the block: the art is fixed # width, and a version number is not. The taglines are placed rather than written in, # for the same reason - German is longer than English and the box must not move. $art = @( '' ' .================.' ' || - - || gutcheck' ' || o o ||' (' || ____ || {0}' -f (Get-Text 'Console.Banner.Tagline1')) (' || || {0}' -f (Get-Text 'Console.Banner.Tagline2')) " '================'" ) Write-Host ($art -join [Environment]::NewLine) -ForegroundColor Cyan Write-Host (" v{0}`n" -f $Version) -ForegroundColor DarkCyan } function Write-FindingToHost { [CmdletBinding()] param([Parameter(Mandatory)]$Finding) Write-Host (' [{0,-4}] {1}: {2}' -f $Finding.Severity, $Finding.Check, $Finding.Value) ` -ForegroundColor $script:SeverityColour[$Finding.Severity] } function Write-WorstFindingsToHost { <# .SYNOPSIS Repeats the Findings that matter, worst first, once a Run has finished. .DESCRIPTION A Technician watching a Run scroll past needs the FAILs and WARNs gathered in one place at the end. This is not the Report; it is a pointer to it. #> [CmdletBinding()] param( [AllowNull()][AllowEmptyCollection()]$Finding, [Parameter(Mandatory)][string]$ReportPath ) $failed = @($Finding | Where-Object { $_.Severity -eq 'FAIL' }).Count $warned = @($Finding | Where-Object { $_.Severity -eq 'WARN' }).Count Write-Host (Get-Text 'Console.Summary.Heading' $failed $warned) -ForegroundColor Cyan Sort-Finding -Finding $Finding | Where-Object { $_.Severity -eq 'FAIL' -or $_.Severity -eq 'WARN' } | ForEach-Object { Write-Host (' [{0}] {1}: {2}' -f $_.Severity, $_.Check, $_.Value) ` -ForegroundColor $script:SeverityColour[$_.Severity] } Write-Host (Get-Text 'Console.Summary.Report' $ReportPath) -ForegroundColor Cyan } |