Public/Format-HDTProgressLine.ps1
|
function Format-HDTProgressLine { <# .SYNOPSIS Renders one line of deployment progress for a machine that cannot draw a window. .DESCRIPTION DESIGN 11.1'S CONSOLE FALLBACK, and the half of it a technician actually reads: "If XAML fails to load - a boot image built without the right components, an exotic display, a serial console - the engine logs the reason and WRITES STYLED CONSOLE LINES INSTEAD, then carries on." EIGHTY COLUMNS, BECAUSE THAT IS WHAT IT HAS. A WinPE command prompt and a serial console are both eighty wide and neither wraps kindly; a line that spills leaves a technician reading half of every second line, which is worse than a shorter line that does not. THE STEP NAME IS THE PART THAT GIVES WAY. Where a deployment is up to - the counter, the percentage, the phase, the clock - is fixed width and always survives. WHICH step it is on is allowed to be abbreviated, because a truncated name still identifies a step to somebody watching, and a missing counter does not identify anything. THE FIXED WIDTHS ARE WHAT MAKES IT "STYLED". Consecutive lines form columns rather than ragged text, so a technician reads down the numbers instead of hunting along each line for them. That is the whole of what styling can mean where there is no window. IT SHOUTS ONLY WHEN SOMETHING HAPPENED. A line that says RUNNING on every step is a line nobody is reading by the fourth one, and then FAILED goes past unnoticed. ELAPSED IS A CLOCK, NOT A COUNT OF SECONDS. Nobody divides by sixty at a bench. .PARAMETER Progress What Get-HDTDeploymentProgress returned. .INPUTS None. This command does not accept pipeline input. .OUTPUTS System.String - one line, at most eighty characters. .EXAMPLE Format-HDTProgressLine -Progress (Get-HDTDeploymentProgress -Record $record) [ 3/8 25%] WinPE Apply Windows 11 Enterprise LTSC 37% 00:01:24 #> # $Progress IS used - inside the $valueOf closure below, which the analyzer # does not follow. Removing it to satisfy the rule would remove the input. [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'Used inside a closure, which PSReviewUnusedParameter does not follow.')] [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNull()] [object] $Progress ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $valueOf = { param([string] $Name, [object] $Default) if ($null -eq $Progress.PSObject.Properties[$Name]) { return $Default } if ($null -eq $Progress.$Name) { return $Default } return $Progress.$Name } $stepNumber = [int] (& $valueOf 'StepNumber' 0) $stepCount = [int] (& $valueOf 'StepCount' 0) $percent = [int] (& $valueOf 'PercentComplete' 0) $phase = [string] (& $valueOf 'Phase' '') $stepName = [string] (& $valueOf 'StepName' '') $status = [string] (& $valueOf 'Status' '') $elapsed = [int] (& $valueOf 'ElapsedSecond' 0) $stepPercent = [int] (& $valueOf 'StepPercent' 0) # A COUNT NOBODY STATED IS NOT PRINTED AS ZERO. '3/0' is a wrong fact where # '3' is an incomplete one. $counter = '{0,2}' -f $stepNumber if ($stepCount -gt 0) { $counter = '{0,2}/{1,-2}' -f $stepNumber, $stepCount } $header = '[{0} {1,3}%]' -f $counter.PadRight(5), $percent $span = [timespan]::FromSeconds($elapsed) $clock = '{0:00}:{1:00}:{2:00}' -f [int] $span.TotalHours, $span.Minutes, $span.Seconds # ONLY WHEN SOMETHING HAPPENED - see the header. $flag = '' if ($status -eq 'Failed') { $flag = ' FAILED' } if ($status -eq 'Succeeded') { $flag = ' DONE' } $phaseText = '' if (-not [string]::IsNullOrWhiteSpace($phase)) { $phaseText = ' {0,-6}' -f $phase } # THE STEP'S OWN PERCENTAGE, AND THE COLUMN IS RESERVED EVEN WHEN NOTHING # REPORTED ONE. Five characters, blank for a step that says nothing about # itself: a column that appeared and disappeared would shift the clock # sideways mid-deployment, and consecutive lines forming columns is the # whole of the styling this fallback has. $stepText = ' ' if ($stepPercent -gt 0) { $stepText = ' {0,3}%' -f $stepPercent } # WHAT IS LEFT AFTER THE PARTS THAT MAY NOT SHRINK. Two spaces before the # clock keep the columns apart when a name runs the full width. $fixed = $header.Length + $phaseText.Length + $stepText.Length + $flag.Length + $clock.Length + 3 $room = 80 - $fixed if ($room -lt 0) { $room = 0 } $name = $stepName if ($name.Length -gt $room) { $name = $name.Substring(0, $room) } return ('{0}{1} {2}{3} {4}{5}' -f $header, $phaseText, $name.PadRight($room), $stepText, $clock, $flag).TrimEnd() } |