Public/Resume-AzureLocalFleetUpdate.ps1

function Resume-AzureLocalFleetUpdate {
    <#
    .SYNOPSIS
        Resumes a previously interrupted fleet update operation.
     
    .DESCRIPTION
        Loads a saved fleet operation state and continues processing any
        pending or failed clusters. This enables recovery from:
        - Pipeline timeouts
        - Network interruptions
        - Manual cancellations
        - Transient failures
     
    .PARAMETER StateFilePath
        Path to the saved state file from a previous operation.
     
    .PARAMETER State
        A state object loaded via Import-AzureLocalFleetState.
     
    .PARAMETER RetryFailed
        Also retry clusters that previously failed (not just pending).
     
    .PARAMETER MaxRetries
        Maximum additional retry attempts for failed clusters.
     
    .PARAMETER Force
        Skip confirmation prompts.
     
    .PARAMETER PassThru
        Return the updated state object.
     
    .EXAMPLE
        Resume-AzureLocalFleetUpdate -StateFilePath "C:\Logs\fleet-state.json" -Force
        Resumes pending clusters from the saved state.
     
    .EXAMPLE
        Resume-AzureLocalFleetUpdate -StateFilePath "C:\Logs\fleet-state.json" -RetryFailed -Force
        Resumes pending clusters and retries failed ones.
    #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'ByPath')]
        [ValidateScript({ Test-Path $_ })]
        [string]$StateFilePath,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'ByState')]
        [PSCustomObject]$State,
        
        [Parameter(Mandatory = $false)]
        [switch]$RetryFailed,
        
        [Parameter(Mandatory = $false)]
        [ValidateRange(0, 10)]
        [int]$MaxRetries = 3,
        
        [Parameter(Mandatory = $false)]
        [switch]$Force,
        
        [Parameter(Mandatory = $false)]
        [switch]$PassThru
    )
    
    # Load state
    $resumeState = if ($State) { $State } else { Import-AzureLocalFleetState -Path $StateFilePath }
    
    if (-not $resumeState) {
        Write-Error "Failed to load fleet state."
        return $null
    }
    
    Write-Log -Message "========================================" -Level Header
    Write-Log -Message "Resuming Fleet Operation" -Level Header
    Write-Log -Message "Original Run ID: $($resumeState.RunId)" -Level Header
    Write-Log -Message "========================================" -Level Header
    
    # Identify clusters to process
    $pendingClusters = @($resumeState.Clusters | Where-Object { $_.Status -eq "Pending" })
    $failedClusters = @($resumeState.Clusters | Where-Object { $_.Status -eq "Failed" })
    
    Write-Log -Message "State Summary:" -Level Info
    Write-Log -Message " Pending: $($pendingClusters.Count)" -Level Info
    Write-Log -Message " Failed: $($failedClusters.Count)" -Level Info
    Write-Log -Message " Succeeded: $($resumeState.SucceededCount)" -Level Info
    
    $clustersToProcess = $pendingClusters
    if ($RetryFailed) {
        $clustersToProcess += $failedClusters
        # Reset failed clusters to pending
        foreach ($cluster in $failedClusters) {
            $cluster.Status = "Pending"
            $cluster.Attempts = 0
            $cluster.LastError = $null
        }
        $resumeState.FailedCount = 0
    }
    
    if ($clustersToProcess.Count -eq 0) {
        Write-Log -Message "No clusters to process. All clusters have succeeded." -Level Success
        return $resumeState
    }
    
    Write-Log -Message "Clusters to process: $($clustersToProcess.Count)" -Level Info
    
    # Confirmation
    if (-not $Force) {
        $confirmation = Read-Host "Resume operation on $($clustersToProcess.Count) cluster(s)? (y/n)"
        if ($confirmation -ne 'y') {
            Write-Log -Message "Resume cancelled by user." -Level Warning
            return $resumeState
        }
    }
    
    # Collect resource IDs for processing
    $resourceIds = $clustersToProcess | ForEach-Object { $_.ResourceId }
    
    # Use Invoke-AzureLocalFleetOperation with the specific clusters
    $params = @{
        ClusterResourceIds = $resourceIds
        Operation = $resumeState.Operation
        BatchSize = $resumeState.BatchSize
        ThrottleLimit = $resumeState.ThrottleLimit
        MaxRetries = $MaxRetries
        Force = $true
        PassThru = $true
    }
    
    if ($resumeState.UpdateName) {
        $params['UpdateName'] = $resumeState.UpdateName
    }
    
    if ($resumeState.StateFilePath) {
        $params['StateFilePath'] = $resumeState.StateFilePath
    }
    
    $result = Invoke-AzureLocalFleetOperation @params
    
    if ($PassThru) {
        return $result
    }
}