PureStorage.RunCommandLauncher.ps1

Import-Module PureStorage.RunCommandWrapper
. $PSScriptRoot/PureStorage.CommonUtil.ps1

$MICROSOFT_AVS_VMFS_VERSION = "1.0.154"
$MICROSOFT_AVS_VVOLS_VERSION = "1.0.110-dev"

function Invoke-AvsScriptExecution {
    param (
        [string]$AVSCloudName,                # Matches -AVSCloudName
        [string]$SubscriptionId,              # Matches -SubscriptionId
        [string]$AVSResourceGroup,            # Matches -AVSResourceGroup
        [string]$RunCommandId,                # Matches -RunCommandId
        [string]$RunCommandExecName,          # Matches -RunCommandExecName
        [int]$TimeoutInMinutes = 10,          # Matches -TimeoutInMinutes
        [string]$ProductName,                 # Matches -ProductName
        [string]$ProductVersion,              # Matches -ProductVersion
        [hashtable]$CommandParameters,        # Matches -CommandParameters
        [string]$ErrorVariable = "ExecutionError" # Matches -ErrorVariable
    )

    # Convert Timeout to ISO 8601 duration format
    $Timeout = "P0Y0M0DT0H$TimeoutInMinutes`M0S"

    # Convert hashtable parameters to Azure CLI format
    $paramString = ""
    foreach ($key in $CommandParameters.Keys) {
        $paramString += "--parameter name=`"$key`" type=Value value=`"$($CommandParameters[$key])`" "
    }

    # Construct and execute the command
    $command = @"
az vmware script-execution create --resource-group `"$AVSResourceGroup`" --private-cloud `"$AVSCloudName`" --name `"$RunCommandExecName`" --script-cmdlet-id `"$RunCommandId`" --timeout `"$Timeout`" $paramString --output json --yes
"@


    Write-Output "Executing command: $command"

    try {
        $execution = Invoke-Expression $command 2>&1 | Out-String
        if ($execution -match "ERROR") {
            Write-Error "Azure CLI error: $execution"
            exit 1
        }
        $executionJson = $execution | ConvertFrom-Json -ErrorAction Stop
        $ExecutionId = $executionJson.id
        Write-Output "Script Execution ID: $ExecutionId"
    } catch {
        Write-Error "Failed to execute script: $_"
        exit 1
    }

    # Set timeout limit
    $startTime = Get-Date
    $timeout = New-TimeSpan -Minutes $TimeoutInMinutes
    $lastStatusLogTime = $startTime

    # Initialize status variable
    $status = "Running"

    # Poll for completion with a timeout
    do {
        Start-Sleep -Seconds 5  # Poll every 5 seconds
        $elapsed = (Get-Date) - $startTime

        $statusCommand = "az vmware script-execution show --resource-group `"$AVSResourceGroup`" --private-cloud `"$AVSCloudName`" --name `"$RunCommandExecName`" --query `"provisioningState`" -o tsv"
        try {
            $status = Invoke-Expression $statusCommand 2>&1 | Out-String
            $status = $status.Trim()  # Ensure no extra spaces

            # Only log status every 60 seconds to reduce spam
            if ((Get-Date) - $lastStatusLogTime -ge (New-TimeSpan -Seconds 60)) {
                Write-Output "Current Status: $status"
                $lastStatusLogTime = Get-Date
            }

            if ($status -eq "Succeeded") {
                Write-Output "✅ Script executed successfully!"
                break
            } elseif ($status -eq "Failed") {
                Write-Error "❌ Script execution failed!"
                break
            }
        } catch {
            Write-Error "Failed to retrieve script execution status: $_"
        }
    } while ($elapsed -lt $timeout)

    # If timeout is reached, terminate
    if ($elapsed -ge $timeout) {
        Write-Error "⏳ Timeout reached! The script execution did not complete in $TimeoutInMinutes minutes."
        exit 1
    }

    # Retrieve execution output
    $executionOutputCommand = "az vmware script-execution show --resource-group `"$AVSResourceGroup`" --private-cloud `"$AVSCloudName`" --name `"$RunCommandExecName`" --query `"output`" -o json"
    try {
        $executionOutput = Invoke-Expression $executionOutputCommand | ConvertFrom-Json -ErrorAction Stop
        Write-Output "Script Output: $executionOutput"
    } catch {
        Write-Error "Failed to retrieve script execution output: $_"
    }

    if ($status -eq "Failed") {
        Write-Error "❌ Execution failed with status: $status" -ErrorVariable $ErrorVariable
    }

    return $executionOutput
}

function Invoke-RunScript {
    Param (
        [Parameter(Mandatory=$true)]
        [String] $RunCommandName,

        [Parameter(Mandatory=$true)]
        [ValidateSet("Microsoft.AVS.VMFS", "Microsoft.AVS.VVOLS")]
        [String] $RunCommandModule,

        [Parameter(Mandatory=$true)]
        $Parameters,

        [Parameter(Mandatory=$false)]
        [String]$AVSCloudName,

        [Parameter(Mandatory=$false)]
        [String]$AVSResourceGroup,

        [Parameter(Mandatory=$false)]
        [switch]$GetNamedOutputs,

        [Parameter(Mandatory=$false)]
        [int] $TimeoutInMinutes = 10
    )

    # Retrieve the execution mode (default to PowerShell-based Invoke-AVSScript)
    $ExecutionMode = [Environment]::GetEnvironmentVariable("CBS_AVS_SCRIPT_EXECUTION_MODE")
    if (-not $ExecutionMode) {
        $ExecutionMode = "PowerShell"
    }

    Write-Host "Using script execution mode: $ExecutionMode"

    # Get AzContext and SubscriptionId
    $AzContext = Get-AzContextWrapper
    $SubscriptionId = $AzContext.Subscription.Id
    Write-Host "Using Azure subscription: $SubscriptionId"

    # Determine RunCommand package version
    if ($RunCommandModule -eq "Microsoft.AVS.VMFS") {
        $RunCommandPackageVersion = $MICROSOFT_AVS_VMFS_VERSION
    } elseif ($RunCommandModule -eq "Microsoft.AVS.VVOLS") {
        $RunCommandPackageVersion = $MICROSOFT_AVS_VVOLS_VERSION
    } else {
        throw "Unknown RunCommandModule $RunCommandModule"
    }

    # Allow environment override for specific module versions
    $EnvVariableName = ($RunCommandModule -replace "\.", "_") + '_Version'
    $UpdatedVersion = [Environment]::GetEnvironmentVariable($EnvVariableName)
    if ($UpdatedVersion) {
        Write-Warning "Using customized AVS Run Command version $($UpdatedVersion) for Module ($RunCommandModule). Please consult with Pure Storage Support before using this version"
        $RunCommandPackageVersion = $UpdatedVersion
    }

    # Test RunCommand availability
    Test-RunCommandPackageAvailabilityGeneric -AzContext $AzContext -RunCommandModule $RunCommandModule -RunCommandPackageVersion $RunCommandPackageVersion -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup

    # Construct the RunCommand ID
    $CmdletId = "/subscriptions/$($SubscriptionId)/resourceGroups/$($AVSResourceGroup)/providers/Microsoft.AVS/privateClouds/$($AVSCloudName)/scriptPackages/$($RunCommandModule)@$($RunCommandPackageVersion)/scriptCmdlets/$($RunCommandName)"
    $RandomID = [System.Guid]::NewGuid().ToString().Substring(0,8)
    $RunCmdExecutionName = "$($RunCommandName)-PureStorage.RunCommandWrapper-$($RandomID)"
    $ProductName = "PureStorage.CBS.AVS"
    $ProductVersion = (Get-Module $ProductName).Version.ToString()

    Write-Host "Invoking RunCommand $RunCmdExecutionName with ID $CmdletId..."

    # Function to check if az CLI and az vmware extension are installed
    function Test-AzCliAndExtension {
        $azExists = $false
        $azVmwareInstalled = $false

        # Check if az CLI is installed
        if (Get-Command "az" -ErrorAction SilentlyContinue) {
            $azExists = $true
        } else {
            Write-Error "`n❌ Azure CLI (az) is not installed."
            Write-Host "`n📌 **Install Azure CLI using the following command for your platform:**"

            if ($IsWindows) {
                Write-Host "`n▶ **Windows:** Download from: https://aka.ms/installazurecliwindows"
            } elseif ($IsLinux) {
                Write-Host "`n▶ **Linux (Debian/Ubuntu-based):**"
                Write-Host "`n curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash"

                Write-Host "`n▶ **Linux (RHEL/CentOS-based):**"
                Write-Host "`n sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc"
                Write-Host " sudo dnf install -y https://packages.microsoft.com/config/rhel/8/packages-microsoft-prod.rpm"
                Write-Host " sudo dnf install -y azure-cli"

                Write-Host "`n▶ **Other Linux distributions:** Visit https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-linux for instructions."
            } elseif ($IsMacOS) {
                Write-Host "`n▶ **MacOS (Homebrew):**"
                Write-Host "`n brew install azure-cli"
            }

            return $false
        }

        # Check if az vmware extension is installed
        $azVmwareCheck = az extension list --query "[?name=='vmware']" --output json | ConvertFrom-Json
        if ($azVmwareCheck) {
            $azVmwareInstalled = $true
        } else {
            Write-Error "`n❌ Azure VMware extension is not installed in the Azure CLI."
            Write-Host "`n▶ Install it using:"
            Write-Host "`n az extension add --name vmware"

            return $false
        }

        return $azExists -and $azVmwareInstalled
    }

    # Choose the correct implementation based on the environment variable
    if ($ExecutionMode -eq "PowerShell") {
        # Use the original Invoke-AVSScript method
        Invoke-AVSScript -AVSCloudName $AVSCloudName -SubscriptionId $SubscriptionId -AVSResourceGroup $AVSResourceGroup `
            -RunCommandId $CmdletId -RunCommandExecName $RunCmdExecutionName  -TimeoutInMinutes $TimeoutInMinutes `
            -ProductName $ProductName -ProductVersion $ProductVersion `
            -CommandParameters $Parameters -ErrorVariable stopError
    } elseif ($ExecutionMode -eq "AzureCLI") {
        # Validate az CLI and vmware extension before execution
        if (-not (Test-AzCliAndExtension)) {
            throw "Azure CLI or az vmware extension is missing. Please follow the installation instructions above."
        }

        # Use the new Invoke-AvsScriptExecution method
        Invoke-AvsScriptExecution -AVSCloudName $AVSCloudName -SubscriptionId $SubscriptionId -AVSResourceGroup $AVSResourceGroup `
            -RunCommandId $CmdletId -RunCommandExecName $RunCmdExecutionName -TimeoutInMinutes $TimeoutInMinutes `
            -ProductName $ProductName -ProductVersion $ProductVersion `
            -CommandParameters $Parameters -ErrorVariable stopError
    } else {
        throw "Invalid execution mode: '$ExecutionMode'. Supported values: 'PowerShell', 'AzureCLI'"
    }

    # Error handling
    if ($stopError) {
        Write-Host "RunCommand $RunCmdExecutionName failed with error:"
        throw $stopError
    }

    # Handle named outputs if requested
    if ($GetNamedOutputs) {
        $Output = Get-RunCommandNamedOutput -RunCmdExecutionName $RunCmdExecutionName -AvsPrivateCloudName $AVSCloudName -AvsResourceGroupName $AVSResourceGroup
        Set-Variable -Name NamedOutputs -Value $Output -Scope Script
    }
}

Export-ModuleMember -Function Invoke-RunScript