Public/Update-HDTProgressDisplay.ps1
|
function Update-HDTProgressDisplay { <# .SYNOPSIS Tells the progress display what the log now says. Never fails a deployment. .DESCRIPTION DESIGN 11.1'S SUBSCRIPTION, and deliberately the dullest possible one: the engine has just written a record to the JSONL, so this reads the JSONL back, derives progress from it with Get-HDTDeploymentProgress and hands that to whatever host is attached. NO SECOND CHANNEL. There is no progress API a step can call and no in-memory tally kept beside the log, because "there is exactly one source of truth for what the deployment is doing, so the screen and the log can never disagree" (DESIGN 11.1). A tally would be a second truth, and the first thing it would do is drift on a resume. IT MUST NEVER FAIL A DEPLOYMENT, AND THAT IS THE WHOLE OF ITS ERROR HANDLING. This runs inside the step loop, on a machine part-way through partitioning a disk. A log line the machine was cut off in the middle of writing, a file that went with the RAM disk at the relocation, a UI runspace that has died - none of those is a reason to stop building a computer, and every one of them is a failure nobody would ever guess at from the outside. So everything is caught, and the deployment carries on. A HALF-WRITTEN LINE IS SKIPPED, NOT FATAL. A JSONL whose last line is a fragment is exactly what a machine that died mid-step leaves behind - and the window showing that run must not be the second thing that dies. NO PROGRESS SERVICE IS THE NORMAL CASE and costs nothing: it returns before reading anything. A run nobody asked for a screen on does not read its own log once per step to draw one. .PARAMETER Context The execution context. Only Context.Service.Progress and Context.Log are read, and both may be absent. .INPUTS None. This command does not accept pipeline input. .OUTPUTS None. .EXAMPLE Update-HDTProgressDisplay -Context $Context What the step loop calls after each step's outcome is written. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Updates a status display; it changes no system state and runs inside a step loop where a confirmation prompt would hang a deployment.')] [CmdletBinding()] [OutputType([void])] param( [Parameter(Mandatory = $true, Position = 0)] [AllowNull()] [object] $Context ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # EVERY EXIT BEFORE THE READ IS FREE. See the header: a run with no display # must not pay for one. try { if ($null -eq $Context) { return } if ($null -eq $Context.PSObject.Properties['Service'] -or $null -eq $Context.Service) { return } if ($null -eq $Context.Service.PSObject.Properties['Progress']) { return } $display = $Context.Service.Progress if ($null -eq $display) { return } if ($null -eq $Context.PSObject.Properties['Log'] -or $null -eq $Context.Log) { return } $log = $Context.Log if ($null -eq $log.PSObject.Properties['JsonlPath'] -or $null -eq $log.PSObject.Properties['FileSystem']) { return } $path = [string] $log.JsonlPath if ([string]::IsNullOrWhiteSpace($path)) { return } $fileSystem = $log.FileSystem if ($null -eq $fileSystem -or -not $fileSystem.TestPath($path)) { return } $text = [string] $fileSystem.ReadAllText($path) if ([string]::IsNullOrWhiteSpace($text)) { return } $record = @() foreach ($line in ($text -split "`n")) { $trimmed = $line.Trim() if ([string]::IsNullOrWhiteSpace($trimmed)) { continue } # PER LINE, BECAUSE ONE BAD LINE MUST NOT COST THE OTHERS. JSON Lines # is one object per physical line precisely so a reader can do this. try { $record += ConvertFrom-Json -InputObject $trimmed } catch { continue } } if (@($record).Count -eq 0) { return } $display.Update((Get-HDTDeploymentProgress -Record $record)) } catch { # THE LAST LINE OF THE CONTRACT. A progress bar does not get to stop a # deployment, whatever happened to it. Write-Verbose ("the progress display could not be updated: {0}" -f [string] $_.Exception.Message) } } |