Public/Start-HDTProgressDisplay.ps1
|
function Start-HDTProgressDisplay { <# .SYNOPSIS Puts DESIGN 11.1's progress window on screen - or reports why it could not, and never stops the deployment either way. .DESCRIPTION THREE OUTCOMES, AND NONE OF THEM IS AN ERROR: Window the window is up. Console it could not be drawn, and the deployment carries on writing status to the console instead. Suppressed HDTSkipProgress said not to show one at all. IT MUST DEGRADE TO THE CONSOLE, in DESIGN 11.1's words: "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. A deployment that refused to run because it cannot draw a progress bar would be a worse toolkit than one with no progress bar at all." SO THIS COMMAND DOES NOT THROW. Not for a missing window file, not for markup that will not parse, not for a machine whose WinPE was built without WinPE-NetFx and has no PresentationFramework to load. Every one of those comes back as Console with a Reason, and the caller decides what to say about it. THE FALLBACK IS TESTED BECAUSE NOBODY EXERCISES IT UNTIL THE NIGHT IT MATTERS. That is DESIGN 11.1's own reasoning, and it is why the host is injected: New-HDTFakeProgressHost -FailOpen is a machine with no WPF, on a developer's desktop, in three milliseconds. Suppressed AND Console ARE DIFFERENT FACTS and are never conflated. One is a machine nobody is standing at; the other is a machine that could not draw. Reporting the second as the first would hide a broken boot image behind a deliberate setting. THE WINDOW IS NOT IN THIS FUNCTION. An injected IProgressHost owns Add-Type, XamlReader and the runspace the window lives in; this owns whether there should be one. Same split as the wizard, for the same reason (DESIGN 12.2.1). .PARAMETER XamlPath The window. X:\HDT\UI\HDTProgress.xaml inside a boot image. .PARAMETER Variable The resolved variables. HDTSkipProgress is the only one read. .PARAMETER DisplayHost An IProgressHost. Defaults to the real adapter. .PARAMETER FileSystem An IFileSystem. Defaults to the real adapter. .INPUTS None. This command does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject with Mode ('Window', 'Console' or 'Suppressed'), Reason, XamlPath and DisplayHost. .EXAMPLE $display = Start-HDTProgressDisplay -XamlPath 'X:\HDT\UI\HDTProgress.xaml' -Variable $resolved .EXAMPLE if ($display.Mode -eq 'Window') { $display.DisplayHost.Update($progress) } What the engine does on every step: render if there is something to render to, and never ask whether it worked. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Opens a status window the deployment asked for; it changes no system state and there is no operator to confirm to.')] [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [string] $XamlPath, [Parameter()] [AllowNull()] [hashtable] $Variable, [Parameter()] [AllowNull()] [object] $DisplayHost, [Parameter()] [AllowNull()] [object] $FileSystem, # FOR F8, AND ONLY FOR F8. The window opens a command prompt on that key # - MDT's "Enable command support" - and it runs in a runspace with no # Hephaestus module in it, so it cannot resolve ComSpec for itself. The # path is worked out here, where a provider can be injected, and handed # over with the markup. [Parameter()] [AllowNull()] [object] $EnvironmentProvider ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if ($null -eq $FileSystem) { $FileSystem = New-HDTFileSystem } if ($null -eq $DisplayHost) { $DisplayHost = New-HDTProgressHost } $resolved = $Variable if ($null -eq $resolved) { $resolved = @{} } $result = [ordered] @{ Mode = 'Window' Reason = '' XamlPath = $XamlPath DisplayHost = $DisplayHost } # -- was there meant to be one at all? --------------------------------- # # Read loosely for the reason Get-HDTWizardPage reads its keys loosely: # rules.yaml gives a real boolean and a command line gives text, and a skip # that silently failed to apply is a full-screen window on a machine that # was supposed to deploy unattended. $skip = $false if ($resolved.ContainsKey('HDTSkipProgress')) { $value = $resolved['HDTSkipProgress'] if ($value -is [bool]) { $skip = [bool] $value } elseif ($null -ne $value) { $skip = (@('true', 'yes', '1', 'on') -contains ([string] $value).Trim().ToLowerInvariant()) } } if ($skip) { # AND THE FILE IS NOT READ. A missing window is not a failure on a run # that was never going to draw one. $result['Mode'] = 'Suppressed' $result['Reason'] = 'HDTSkipProgress' return [pscustomobject] $result } # -- the window file ---------------------------------------------------- # THE FALLBACK IS A HOST, NOT A BRANCH AT THE CALL SITE. The caller gets an # object with the same Update it would have had, so the engine reports # progress the same way whatever machine it is on - and there is no # `if ($mode -eq 'Window')` anywhere for somebody to forget. That matters # precisely because this path only happens on machines nobody is testing on. $fallback = { param([string] $Why) $result['Mode'] = 'Console' $result['Reason'] = $Why $result['DisplayHost'] = New-HDTConsoleProgressHost return [pscustomobject] $result } if (-not $FileSystem.TestPath($XamlPath)) { return (& $fallback ("the progress window '{0}' is not there, so the deployment will report progress to the console instead. In a boot image it is staged to X:\HDT\UI\ by Update-HDTBootImage." -f $XamlPath)) } $xaml = '' try { $xaml = [string] $FileSystem.ReadAllText($XamlPath) [void] ([xml] $xaml) } catch { return (& $fallback ("the progress window '{0}' could not be read as XML, so the deployment will report progress to the console instead: {1}" -f $XamlPath, [string] $_.Exception.Message)) } # -- and whether this machine can show one ------------------------------ # # THE PATH NOBODY EXERCISES UNTIL THE NIGHT IT MATTERS. A boot image built # without WinPE-NetFx has no PresentationFramework, and the adapter throws # here rather than returning anything. It is caught because a deployment # that will not run without a progress bar is worse than one with none. if (-not $PSBoundParameters.ContainsKey('EnvironmentProvider')) { $EnvironmentProvider = New-HDTEnvironmentProvider } $commandPromptPath = Get-HDTCommandPromptPath -Environment $EnvironmentProvider try { $DisplayHost.Open($xaml, $commandPromptPath) } catch { return (& $fallback ("the progress window could not be opened, so the deployment will report progress to the console instead: {0}" -f [string] $_.Exception.Message)) } return [pscustomobject] $result } |