Private/Resolve-UpgradeCompletion.ps1
|
function Resolve-UpgradeCompletion { <# .SYNOPSIS Decide what state a VM in UpgradeStarted is in (pure, no Azure calls) .DESCRIPTION The Check side of the state machine. Four situations are distinguished: guest reports the target build -> Completed guest unreachable or Setup running -> InProgress, or Failed once the timeout passed guest on the old build, task ended in error -> Failed VM stopped or deallocated -> Failed A task result counts as an error only when it is an HRESULT with the severity bit set (0x8xxxxxxx / 0xCxxxxxxx, which covers every MOSETUP_E_* code) or a plain 1. Task Scheduler's own status codes (0x41301 running, 0x41303 not yet run, 0x41306 terminated) appear when a reboot cuts the task short and are not failures. #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory)]$Target, [Parameter(Mandatory)][string]$PowerState, [Parameter()][AllowNull()]$Status, [Parameter()][AllowNull()][nullable[datetime]]$StartedAt, [Parameter()][ValidateRange(1, 10080)][int]$TimeoutMinutes = 240, [Parameter()][datetime]$Now = (Get-Date).ToUniversalTime() ) $ageMinutes = $null $expired = $false if ($null -ne $StartedAt) { $ageMinutes = [int]($Now - $StartedAt.ToUniversalTime()).TotalMinutes $expired = $ageMinutes -gt $TimeoutMinutes } function ConvertTo-Outcome { param([string]$Result, [string]$Reason, [bool]$TaskFailed = $false) [pscustomobject]@{ PSTypeName = 'AzureInPlaceUpgrade.CompletionDecision' Result = $Result Reason = $Reason TaskFailed = $TaskFailed Expired = $expired AgeMinutes = $ageMinutes } } if ($PowerState -in @('deallocated', 'stopped')) { return ConvertTo-Outcome 'Failed' "VM is '$PowerState' while an upgrade was in progress." } if ($null -eq $Status) { if ($expired) { return ConvertTo-Outcome 'Failed' "Guest unreachable and the upgrade started $ageMinutes minutes ago (timeout $TimeoutMinutes)." } return ConvertTo-Outcome 'InProgress' 'Guest not reachable (reboot in progress).' } if ($Status.Build -eq $Target.TargetBuild) { return ConvertTo-Outcome 'Completed' "$($Status.ProductName) build $($Status.Build)." } if ($Status.SetupRunning -or $Status.TaskState -eq 'Running') { if ($expired) { return ConvertTo-Outcome 'Failed' "Setup still running after $ageMinutes minutes (timeout $TimeoutMinutes)." } return ConvertTo-Outcome 'InProgress' "Setup running (build $($Status.Build))." } if ($null -ne $Status.TaskResult) { # 0x80000000 as a literal is a negative Int32 in PowerShell; test the severity bit by shift. $code = [uint32]$Status.TaskResult if (($code -shr 31) -eq 1 -or $code -eq 1) { $hex = '0x{0:X8}' -f $code return ConvertTo-Outcome 'Failed' "Setup task exited with $hex and the build is still $($Status.Build)." $true } } if ($expired) { return ConvertTo-Outcome 'Failed' "Build is still $($Status.Build) after $ageMinutes minutes (timeout $TimeoutMinutes)." } return ConvertTo-Outcome 'InProgress' "Build is still $($Status.Build); waiting." } |