Public/Steps/Invoke-HDTConfigureBootStep.ps1
|
function Invoke-HDTConfigureBootStep { <# .SYNOPSIS Makes the machine boot the Windows it was just given. .DESCRIPTION The post-apply work, as a step: - name: Prepare Boot type: ConfigureBoot firmware: auto # auto | UEFI | BIOS recovery: true # optional; default true where R: exists setBootOrder: true # optional; default true Three things, in this order: 1. InstallBootFile(<W>:\, <S>:, UEFI|BIOS) - bcdboot. 'auto' reads HDTIsUEFI. 2. the recovery image: create <R>:\Recovery\WindowsRE, copy <W>:\Windows\System32\Recovery\Winre.wim into it, then SetRecoveryImage. 3. SetBootOrderFirst(). STEP 3 CAME OUT OF A LAB TEST AND IT IS NOT OPTIONAL POLISH. After apply, a machine whose firmware still lists the installation media first simply reboots into WinPE - and the deployment appears to loop, forever, with every log saying it succeeded. bcdboot does not fix that; the firmware boot order does. TWO FAILURES WARN AND CONTINUE, DELIBERATELY: * anything about the recovery image - a missing Winre.wim, or SetRecoveryImage itself throwing. An image with no registered WinRE still boots. Failing a whole deployment over it trades a working machine for a detail nobody asked for. And this is the one call nobody has ever made in anger: it runs the APPLIED IMAGE's own Reagentc.exe against an offline target from inside WinPE (WinPE ships no reagentc, and reagentc has no /setosimage verb - 04-01's verified facts), and 04-04 is the first thing to try it. * the firmware reorder. A machine whose firmware refuses is still deployed. The technician needs to be told to remove or demote the media, not to lose the build. bcdboot FAILING IS NOT ONE OF THEM. A machine with no boot files does not boot, so that is a failed step. .PARAMETER Step A flattened step from Import-HDTSequenceDocument. .PARAMETER Context A New-HDTExecutionContext context. Its Service catalog must carry Image and FileSystem services. .OUTPUTS A New-HDTStepResult. Data carries osVolume, systemVolume, firmware, recoveryVolume and bootOrder. .EXAMPLE Invoke-HDTConfigureBootStep -Step $step -Context $context #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory = $true)] [ValidateNotNull()] [object] $Step, [Parameter(Mandatory = $true)] [ValidateNotNull()] [object] $Context ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $fail = { param([string] $Message, [string] $ErrorId) $data = [ordered] @{} if (-not [string]::IsNullOrWhiteSpace($ErrorId)) { $data['errorId'] = $ErrorId } Write-HDTLog -Context $Context.Log -Message $Message -Severity Error -Event step.fail ` -Component 'ConfigureBoot' -Data $data return (New-HDTStepResult -Status Failed -Message $Message -Data $data) } $letterOf = { param([string] $Value) $text = ([string] $Value).Trim().TrimEnd(':') if ($text.Length -eq 0) { return '' } return $text.Substring(0, 1).ToUpperInvariant() } try { $firmware = Get-HDTStepProperty -Step $Step -Name 'firmware' -Default 'auto' -Context $Context -Expand -As String $recovery = Get-HDTStepProperty -Step $Step -Name 'recovery' -Default $true -Context $Context -Expand -As Bool $setBootOrder = Get-HDTStepProperty -Step $Step -Name 'setBootOrder' -Default $true -Context $Context -Expand -As Bool } catch { return (& $fail ([string] $_.Exception.Message) 'HDTConfigurationError') } $osVolume = & $letterOf ([string] $Context.Variable['HDTOSVolume']) if ($osVolume.Length -eq 0) { return (& $fail ("step '{0}' makes the Windows volume bootable and HDTOSVolume is not set. The partition step publishes it." -f $Step.Name) 'HDTConfigurationError') } $systemVolume = & $letterOf ([string] $Context.Variable['HDTSystemVolume']) if ($systemVolume.Length -eq 0) { return (& $fail ("step '{0}' writes the boot files to the system partition and HDTSystemVolume is not set. The partition step publishes it." -f $Step.Name) 'HDTConfigurationError') } $recoveryVolume = & $letterOf ([string] $Context.Variable['HDTRecoveryVolume']) if ($firmware -eq 'auto') { $gathered = $Context.Variable['HDTIsUEFI'] $isUefi = $false if ($gathered -is [bool]) { $isUefi = [bool] $gathered } elseif ($null -ne $gathered) { $isUefi = ([string] $gathered).Trim() -eq 'True' } $firmware = 'BIOS' if ($isUefi) { $firmware = 'UEFI' } } $firmware = $firmware.ToUpperInvariant() try { $imageService = $Context.Service.GetRequired('Image', 'ConfigureBoot') $fileSystem = $Context.Service.GetRequired('FileSystem', 'ConfigureBoot') } catch { return (& $fail ([string] $_.Exception.Message) '') } $osRoot = '{0}:\' -f $osVolume $systemRoot = '{0}:' -f $systemVolume $data = [ordered] @{ osVolume = $osVolume systemVolume = $systemVolume firmware = $firmware } # -- 1. the boot files ------------------------------------------------- Write-HDTLog -Context $Context.Log -Event 'native.exec' -Component 'ConfigureBoot' ` -Message ('installing {0} boot files from {1} to {2}' -f $firmware, $osRoot, $systemRoot) -Data $data try { $imageService.InstallBootFile($osRoot, $systemRoot, $firmware) } catch { return (& $fail ("the boot files could not be written from {0} to {1}: {2}" -f $osRoot, $systemRoot, [string] $_.Exception.Message) '') } # -- 2. the recovery image, warn and continue -------------------------- $recoveryRegistered = $false if ($recovery -and $recoveryVolume.Length -gt 0) { $recoveryDirectory = '{0}:\Recovery\WindowsRE' -f $recoveryVolume $sourceWim = '{0}Windows\System32\Recovery\Winre.wim' -f $osRoot $data['recoveryVolume'] = $recoveryVolume if (-not $fileSystem.TestPath($sourceWim)) { Write-HDTLog -Context $Context.Log -Severity Warning -Component 'ConfigureBoot' ` -Message ("the applied image carries no {0}, so no recovery environment was registered. The machine still boots; Reset This PC and the recovery options will not be available." -f $sourceWim) ` -Data ([ordered] @{ winre = $sourceWim; recovery = $recoveryDirectory }) } else { try { $fileSystem.CreateDirectory($recoveryDirectory) $fileSystem.CopyItem($sourceWim, ('{0}\Winre.wim' -f $recoveryDirectory)) # The applied image's OWN Reagentc.exe, run against an offline # target. 04-04 is the first thing to try it for real. $imageService.SetRecoveryImage($osRoot, $recoveryDirectory) $recoveryRegistered = $true } catch { Write-HDTLog -Context $Context.Log -Severity Warning -Component 'ConfigureBoot' ` -Message ("the recovery environment at {0} could not be registered: {1}. The machine still boots, so the deployment continues." -f $recoveryDirectory, [string] $_.Exception.Message) ` -Data ([ordered] @{ recovery = $recoveryDirectory }) } } } $data['recoveryRegistered'] = $recoveryRegistered # -- 3. the firmware boot order, warn and continue --------------------- $bootOrderSet = $false if ($setBootOrder) { try { # SPIKES S6: without this the machine reboots into the installation # media and the deployment appears to loop. $imageService.SetBootOrderFirst() $bootOrderSet = $true } catch { Write-HDTLog -Context $Context.Log -Severity Warning -Component 'ConfigureBoot' ` -Message ("the firmware boot order could not be changed: {0}. Windows is installed and bootable, but this machine will boot the installation media again unless it is removed or demoted by hand." -f [string] $_.Exception.Message) } } $data['bootOrder'] = $bootOrderSet $message = '{0} boot files were written from {1} to {2}.' -f $firmware, $osRoot, $systemRoot if ($bootOrderSet) { $message = '{0} The Windows Boot Manager is now first in the firmware boot order.' -f $message } Write-HDTLog -Context $Context.Log -Message $message -Component 'ConfigureBoot' -Data $data return (New-HDTStepResult -Status Completed -Message $message -Data $data) } |