Public/Watch-AzLocalDeployment.ps1
|
Function Watch-AzLocalDeployment { <# .SYNOPSIS Monitors an Azure Local ARM deployment by polling for status changes. .DESCRIPTION Periodically polls an Azure Resource Group deployment and displays status transitions with timestamps. Useful for monitoring long-running Azure Local cluster validation or deployment operations from the same or a separate PowerShell session. The function will poll until the deployment reaches a terminal state (Succeeded, Failed, or Canceled), or until the optional -TimeoutMinutes limit is reached. When used with -PassThru, returns the final deployment object which can be inspected or passed to subsequent commands. .PARAMETER DeploymentName The name of the ARM deployment to monitor. .PARAMETER ResourceGroupName The name of the resource group containing the deployment. .PARAMETER PollingIntervalSeconds How often (in seconds) to poll for status changes. Default: 30 seconds. .PARAMETER TimeoutMinutes Optional. Maximum time (in minutes) to monitor before stopping. Default: 0 (no timeout). .PARAMETER PassThru If specified, returns the final deployment object when monitoring completes. .PARAMETER LogFilePath Optional. Path to a log file for debug/diagnostic output. .EXAMPLE Watch-AzLocalDeployment -DeploymentName "deploy-S001-SingleNode" -ResourceGroupName "rg-S001-azurelocal-prod" .EXAMPLE Watch-AzLocalDeployment -DeploymentName "deploy-S001-SingleNode" -ResourceGroupName "rg-S001-azurelocal-prod" -PollingIntervalSeconds 60 -TimeoutMinutes 120 .EXAMPLE $deployment = Watch-AzLocalDeployment -DeploymentName "deploy-S001-SingleNode" -ResourceGroupName "rg-S001-azurelocal-prod" -PassThru #> [OutputType('Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkModels.PSResourceGroupDeployment')] [CmdletBinding()] param ( [Parameter(Mandatory = $true, Position = 0)] [string]$DeploymentName, [Parameter(Mandatory = $true, Position = 1)] [string]$ResourceGroupName, [Parameter(Mandatory = $false)] [ValidateRange(10, 600)] [int]$PollingIntervalSeconds = 30, [Parameter(Mandatory = $false)] [ValidateRange(0, 1440)] [int]$TimeoutMinutes = 180, [Parameter(Mandatory = $false)] [switch]$PassThru, # Skip searching the Azure Local Supportability TSG repository on failure # (Online TSG search is enabled by default; use this switch to disable it) [Parameter(Mandatory = $false)] [switch]$SkipOnlineTSGSearch, [Parameter(Mandatory = $false)] [string]$LogFilePath = "" ) # Reset module-scoped log path (prevents bleed-over from previous function calls) $script:AzLocalLogFilePath = $null # Initialise log file if specified if (-not [string]::IsNullOrWhiteSpace($LogFilePath)) { Initialize-AzLocalLogFile -LogFilePath $LogFilePath } Write-AzLocalLog "========================================================" -Level Info -NoTimestamp Write-AzLocalLog " Monitoring Deployment: $DeploymentName" -Level Info -NoTimestamp Write-AzLocalLog " Resource Group: $ResourceGroupName" -Level Info -NoTimestamp Write-AzLocalLog " Polling Interval: ${PollingIntervalSeconds}s" -Level Info -NoTimestamp if ($TimeoutMinutes -gt 0) { Write-AzLocalLog " Timeout: ${TimeoutMinutes} minutes" -Level Info -NoTimestamp } Write-AzLocalLog "========================================================" -Level Info -NoTimestamp $terminalStates = @("Succeeded", "Failed", "Canceled") $previousStatus = "" $startTime = Get-Date $statusHistory = @() # Initial deployment lookup try { $deployment = Get-AzResourceGroupDeployment -ResourceGroupName $ResourceGroupName -Name $DeploymentName -ErrorAction Stop } catch { Write-AzLocalLog "Unable to find deployment '$DeploymentName' in resource group '$ResourceGroupName'." -Level Error Write-AzLocalLog "$($_.Exception.Message)" -Level Error throw "Deployment '$DeploymentName' not found in resource group '$ResourceGroupName'. $($_.Exception.Message)" } Write-AzLocalLog "Deployment found. Current state: $($deployment.ProvisioningState)" -Level Success $previousStatus = $deployment.ProvisioningState $statusHistory += [PSCustomObject]@{ Timestamp = Get-Date Status = $deployment.ProvisioningState } # Check if the deployment is already in a terminal state if ($deployment.ProvisioningState -in $terminalStates) { Write-AzLocalLog "Deployment is already in terminal state: $($deployment.ProvisioningState)" -Level Warning Write-AzLocalLog " Duration: $($deployment.Duration)" -Level Verbose if ($PassThru) { return $deployment } return } Write-AzLocalLog "Polling every $PollingIntervalSeconds seconds. Press Ctrl+C to stop monitoring." -Level Verbose # Polling loop while ($true) { # Check timeout if ($TimeoutMinutes -gt 0) { $elapsed = (Get-Date) - $startTime if ($elapsed.TotalMinutes -ge $TimeoutMinutes) { Write-AzLocalLog "Timeout reached after $TimeoutMinutes minutes. Stopping monitor." -Level Warning Write-AzLocalLog " Last known state: $previousStatus" -Level Warning break } } # Wait before next poll Start-Sleep -Seconds $PollingIntervalSeconds # Poll deployment status try { $deployment = Get-AzResourceGroupDeployment -ResourceGroupName $ResourceGroupName -Name $DeploymentName -ErrorAction Stop } catch { Write-AzLocalLog "Failed to poll deployment status. Retrying..." -Level Warning continue } $currentStatus = $deployment.ProvisioningState $elapsedTime = (Get-Date) - $startTime # Display status change or heartbeat if ($currentStatus -ne $previousStatus) { # Status changed Write-AzLocalLog "Status changed: $previousStatus -> $currentStatus (elapsed: $($elapsedTime.ToString('hh\:mm\:ss')))" -Level Info $statusHistory += [PSCustomObject]@{ Timestamp = Get-Date Status = $currentStatus } $previousStatus = $currentStatus } else { # No change - heartbeat Write-AzLocalLog "Status: $currentStatus (elapsed: $($elapsedTime.ToString('hh\:mm\:ss')))" -Level Verbose } # Check for terminal state if ($currentStatus -in $terminalStates) { $totalElapsed = (Get-Date) - $startTime $finalLevel = if ($currentStatus -eq 'Succeeded') { 'Success' } elseif ($currentStatus -eq 'Failed') { 'Error' } else { 'Warning' } Write-AzLocalLog "========================================================" -Level Info -NoTimestamp Write-AzLocalLog " Deployment Complete" -Level Info -NoTimestamp Write-AzLocalLog "========================================================" -Level Info -NoTimestamp Write-AzLocalLog " Deployment: $DeploymentName" -Level Info -NoTimestamp Write-AzLocalLog " Final State: $currentStatus" -Level $finalLevel -NoTimestamp Write-AzLocalLog " Total Monitoring Time: $($totalElapsed.ToString('hh\:mm\:ss'))" -Level Info -NoTimestamp if ($deployment.Duration) { Write-AzLocalLog " ARM Deployment Duration: $($deployment.Duration)" -Level Info -NoTimestamp } # Show status history Write-AzLocalLog " Status History:" -Level Verbose foreach ($entry in $statusHistory) { Write-AzLocalLog " [$($entry.Timestamp.ToString('HH:mm:ss'))] $($entry.Status)" -Level Verbose } if ($currentStatus -eq "Succeeded") { Write-AzLocalLog "Deployment succeeded. If this was a Validate phase, you can now re-submit with deploymentMode set to 'Deploy'." -Level Success Write-Verbose "Ref: https://learn.microsoft.com/en-us/azure/azure-local/deploy/deployment-azure-resource-manager-template" } elseif ($currentStatus -eq "Failed") { Write-AzLocalLog "Deployment failed. Check the Azure Portal for detailed error information." -Level Error # Attempt to display error details if available $troubleshootErrorText = "" if ($deployment.Error) { Write-AzLocalLog " Error Code: $($deployment.Error.Code)" -Level Error -NoTimestamp Write-AzLocalLog " Error Message: $($deployment.Error.Message)" -Level Error -NoTimestamp $troubleshootErrorText = "$($deployment.Error.Code) $($deployment.Error.Message)" if ($deployment.Error.Details) { foreach ($errDetail in $deployment.Error.Details) { $troubleshootErrorText += " $($errDetail.Code) $($errDetail.Message)" } } } # Provide troubleshooting hints for common validation/deployment failures if (-not [string]::IsNullOrWhiteSpace($troubleshootErrorText)) { $troubleshootParams = @{ ErrorText = $troubleshootErrorText } if (-not $SkipOnlineTSGSearch) { $troubleshootParams['SearchOnline'] = $true } Get-AzLocalValidationTroubleshootingHints @troubleshootParams } } break } } if ($PassThru) { return $deployment } } |