modules/HomeLab.Azure/Private/Deploy-Component.ps1

<#
.SYNOPSIS
    Deploys an Azure infrastructure component using Bicep templates.
.DESCRIPTION
    This internal helper function handles the common logic for deploying infrastructure components,
    supporting both foreground and background deployment modes.
.PARAMETER ResourceGroup
    The name of the resource group to deploy to.
.PARAMETER TemplateFile
    The path to the Bicep template file.
.PARAMETER ResourceName
    The name of the resource to deploy.
.PARAMETER ResourceType
    The type of resource being deployed (e.g., "vnet", "vnet-gateway", "nat-gateway").
.PARAMETER ComponentName
    A friendly display name for the component (e.g., "Network", "VPN Gateway", "NAT Gateway").
.PARAMETER CommonParams
    An array of common parameters to pass to the deployment command.
.PARAMETER PollIntervalSeconds
    The interval in seconds between polling for deployment status.
.PARAMETER TimeoutMinutes
    The timeout in minutes for the deployment.
.PARAMETER Monitor
    If specified, monitors the deployment until completion.
.PARAMETER BackgroundMonitor
    If specified, starts background monitoring jobs instead of blocking the console.
.EXAMPLE
    Deploy-Component -ResourceGroup "my-rg" -TemplateFile "network.bicep" -ResourceName "my-vnet" -ResourceType "vnet" -ComponentName "Network" -CommonParams $params -BackgroundMonitor
.NOTES
    Author: Jurie Smit
    Date: March 10, 2025
#>

function Deploy-Component {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [string]$ResourceGroup,
        
        [Parameter(Mandatory=$true)]
        [string]$TemplateFile,
        
        [Parameter(Mandatory=$true)]
        [string]$ResourceName,
        
        [Parameter(Mandatory=$true)]
        [string]$ResourceType,
        
        [Parameter(Mandatory=$true)]
        [string]$ComponentName,
        
        [Parameter(Mandatory=$true)]
        [array]$CommonParams,
        
        [Parameter(Mandatory=$false)]
        [int]$PollIntervalSeconds = 30,
        
        [Parameter(Mandatory=$false)]
        [int]$TimeoutMinutes = 60,
        
        [Parameter(Mandatory=$false)]
        [switch]$Monitor,
        
        [Parameter(Mandatory=$false)]
        [switch]$BackgroundMonitor
    )

    # Verify template file exists
    if (-not (Test-Path -Path $TemplateFile)) {
        Write-Log -Message "Template file not found: $TemplateFile" -Level Error
        Write-Output $false
        return
    }
    
    if ($BackgroundMonitor) {
        Write-Log -Message "Launching $ComponentName deployment in background." -Level Info
        
        # Create a unique job name for better tracking
        $jobName = "Deploy_$($ComponentName.Replace(' ', ''))_$(Get-Random)"
        
        # Create the deployment job
        $deploymentScriptBlock = {
            param($ResourceGroup, $TemplateFile, $CommonParams, $ResourceName, $ResourceType, $ComponentName)
            
            # Define a simple logging function for the background job
            function Write-JobLog {
                param([string]$Message)
                $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
                Write-Output "[$timestamp] $Message"
            }
            
            Write-JobLog "Background job started for $ComponentName deployment..."
            
            # Convert commonParams array to a proper command line
            $deployCmd = @("az", "deployment", "group", "create", "--template-file", $TemplateFile)
            foreach ($param in $CommonParams) {
                $deployCmd += $param
            }
            
            Write-JobLog "Executing: $($deployCmd -join ' ')"
            
            try {
                # Execute the deployment command and capture output
                $result = & $deployCmd[0] $deployCmd[1..($deployCmd.Length-1)] 2>&1
                $exitCode = $LASTEXITCODE
                
                Write-JobLog "Command output: $result"
                Write-JobLog "Deployment completed with exit code: $exitCode"
                
                return @{
                    Status = if ($exitCode -eq 0) { "DeploymentCompleted" } else { "Failed" }
                    ResourceGroup = $ResourceGroup
                    ResourceName = $ResourceName
                    ResourceType = $ResourceType
                    ExitCode = $exitCode
                    Output = $result
                    ComponentName = $ComponentName
                }
            }
            catch {
                Write-JobLog "Deployment failed: $_"
                return @{
                    Status = "Failed"
                    ResourceGroup = $ResourceGroup
                    ResourceName = $ResourceName
                    ResourceType = $ResourceType
                    Error = $_.Exception.Message
                    ComponentName = $ComponentName
                }
            }
        }
        
        # Start the deployment job
        $job = Start-Job -Name $jobName -ScriptBlock $deploymentScriptBlock -ArgumentList $ResourceGroup, $TemplateFile, $CommonParams, $ResourceName, $ResourceType, $ComponentName
        
        try {
            # Start monitoring in the main script after job creation
            $monitoringDetails = Start-ResourceMonitoring -ResourceGroup $ResourceGroup -ResourceType $ResourceType -ResourceName $ResourceName -PollIntervalSeconds $PollIntervalSeconds -TimeoutMinutes $TimeoutMinutes -BackgroundJob
            
            Write-Log -Message "Background deployment job started for $ComponentName (Job ID: $($job.Id))." -Level Info
            Write-Log -Message "Background monitoring started for $ComponentName." -Level Info
            
            # Prompt user to export job info
            $exportChoice = Read-Host "Do you want to export the background job info? (Y/N)"
            if ($exportChoice -match '^(Y|y)$') {
                # Create a hashtable with deployment info
                $deploymentInfo = @{
                    "Job ID" = $job.Id
                    "Job Name" = $job.Name
                    "Resource Group" = $ResourceGroup
                    "Resource Name" = $ResourceName
                    "Resource Type" = $ResourceType
                    "Component Name" = $ComponentName
                    "Template File" = $TemplateFile
                }

                # Use the fixed Export-JobInfo function
                try {
                    $exportPath = Export-JobInfo -DeploymentInfo $deploymentInfo -MonitoringInfo $monitoringDetails -Name $ComponentName.Replace(' ', '')
                }
                catch {
                    $errorMessage = $_.Exception.Message
                    Write-Log -Message "Failed to export job info: $errorMessage" -Level Error
                    # Continue execution even if export fails
                }

                Show-BackgroundJobInfo -Job $job -Detailed
            }
            
            # Don't return a hashtable directly, use Write-Output to avoid System.Object[] display
            Write-Output $true
        }
        catch {
            $errorMessage = $_.Exception.Message
            Write-Log -Message "Error deploying $ComponentName`: $errorMessage" -Level Error
            Write-ColorOutput -Text "Error deploying $ComponentName`: $errorMessage" -ForegroundColor Red
            Read-Host -Prompt "Press Enter to continue..."
            Write-Output $false
        }
    }
    else {
        # If foreground monitoring is chosen, run the deployment and monitoring in the current session
        try {
            if (-not (Invoke-Deployment -TemplateFile $TemplateFile -ComponentName $ComponentName -ResourceType $ResourceType -ResourceName $ResourceName -ResourceGroup $ResourceGroup -DeploymentParams $CommonParams -Monitor:$Monitor -PollIntervalSeconds $PollIntervalSeconds -TimeoutMinutes $TimeoutMinutes)) {
                Write-Output $false
                return
            }
        }
        catch {
            $errorMessage = $_.Exception.Message
            Write-Log -Message "Error deploying $ComponentName`: $errorMessage" -Level Error
            Write-ColorOutput -Text "Error deploying $ComponentName`: $errorMessage" -ForegroundColor Red
            Read-Host -Prompt "Press Enter to continue..."
            Write-Output $false
            return
        }
    }
    
    Write-Output $true
}