PureStorage.RunCommandLauncher.ps1
Import-Module PureStorage.RunCommandWrapper function Get-DefaultAzureSubscriptionId { # Azure Resource Manager cmdlets use this settings by default when making Azure Resource Manager requests. # To list all of subscription, it would be Get-AzContext -ListAvailable # In our case, getting default account is sufficient $DefaultAzContext = Get-AzContext if ($DefaultAzContext.Subscription) { return $DefaultAzContext.Subscription.Id } else { throw "Could not find default Azure subscription. Please make sure you are logging in to Azure using 'Connect-AzAccount'. If you have multiple subscriptions, please use 'Select-AzSubscription' to select the subscription you want to use." } } function Invoke-AvsScriptExecution { param ( [string]$AVSCloudName, [string]$SubscriptionId, [string]$AVSResourceGroup, [string]$RunCommandId, [string]$RunCommandExecName, [int]$TimeoutInMinutes = 10, [string]$ProductName, [string]$ProductVersion, [hashtable]$CommandParameters, [string]$ErrorVariable = "ExecutionError" ) # Convert Timeout to ISO 8601 duration format $Timeout = "P0Y0M0DT0H$TimeoutInMinutes`M0S" # Construct JSON payload for script execution $Payload = @{ properties = @{ scriptCmdletId = $RunCommandId timeout = $Timeout parameters = @() } } foreach ($key in $CommandParameters.Keys) { $value = $CommandParameters[$key] # Convert large numbers (above 2^53) to string to avoid 'Number ...M' issue if ($value -is [UInt64] -or ($value -is [int64] -and $value -ge [math]::Pow(2,53))) { $value = "$value" } $Payload.properties.parameters += @{ name = $key type = "Value" value = $value } } $JsonPayload = $Payload | ConvertTo-Json -Depth 10 # REST API call to create script execution $ExecutionUri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$AVSResourceGroup/providers/Microsoft.AVS/privateClouds/$AVSCloudName/scriptExecutions/$($RunCommandExecName)?api-version=2022-05-01" Write-Verbose "Invoking script execution: $ExecutionUri`nRun Command ID: $RunCommandId`nPayload: $($JsonPayload)" $ExecutionResponse = Invoke-AzRestMethod -Uri $ExecutionUri -Method Put -Payload $JsonPayload if ($ExecutionResponse.StatusCode -lt 200 -or $ExecutionResponse.StatusCode -gt 299) { throw "Failed to create script execution. Status code: $($ExecutionResponse.StatusCode), Response: $($ExecutionResponse.Content)" } $ExecutionId = $ExecutionResponse.Content.id Write-Debug "Script Execution ID: $ExecutionId" # Poll for completion with a timeout $StartTime = Get-Date $TimeoutLimit = New-TimeSpan -Minutes $TimeoutInMinutes $LastStatusLogTime = $StartTime $Status = "Unknown" $ExecutionOutput = $null do { Start-Sleep -Seconds 5 # Poll every 5 seconds $Elapsed = (Get-Date) - $StartTime # Fetch script execution status and output in a single call $StatusResponse = Invoke-AzRestMethod -Uri $ExecutionUri -Method Get if ($StatusResponse.StatusCode -eq 200) { $ResponseData = $StatusResponse.Content | ConvertFrom-Json -Depth 10 $Status = $ResponseData.properties.provisioningState $ExecutionOutput = $ResponseData.properties.output } # Log status every 60 seconds to avoid excessive logging if ((Get-Date) - $LastStatusLogTime -ge (New-TimeSpan -Seconds 60)) { Write-Verbose "Current Status: $Status" $LastStatusLogTime = Get-Date } if ($Status -eq "Succeeded") { Write-Debug "Script executed successfully!" break } elseif ($Status -eq "Failed") { throw "Script execution failed! Execution ID: $ExecutionId, URI: $ExecutionUri, Details: $ExecutionOutput" } } while ($Elapsed -lt $TimeoutLimit) # If timeout is reached, terminate if ($Elapsed -ge $TimeoutLimit) { throw "Timeout reached! Script execution did not complete in $TimeoutInMinutes minutes. Execution ID: $ExecutionId, URI: $ExecutionUri, Last known status: $Status" } Write-Host "Script Execution Completed. Execution ID: $ExecutionId, URI: $ExecutionUri" if ($ExecutionOutput) { Write-Host "Script Output: $ExecutionOutput" } 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 ) $SubscriptionId = Get-DefaultAzureSubscriptionId Write-Host "Using Azure default subscription: $SubscriptionId" # Determine which RunCommand version to use $UseAvsClient = $env:CBS_AVS_SCRIPT_EXECUTION_VIA_AVS_CLIENT -eq "true" # Microsoft recommends using the stable version if ($RunCommandModule -eq "Microsoft.AVS.VMFS") { $RunCommandPackageVersion = "1.0.154" } elseif($RunCommandModule -eq "Microsoft.AVS.VVOLS") { $RunCommandPackageVersion = "1.0.110-dev" } else { throw "Unknown RunCommandModule $RunCommandModule" } # Allow overriding the version via an environment variable $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-RunCommandPackageAvailability -SubscriptionId $SubscriptionId -RunCommandModule $RunCommandModule -RunCommandPackageVersion $RunCommandPackageVersion -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup # Construct command execution details $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 (use AvsClient: $UseAvsClient) ..." if ($UseAvsClient) { # Use the original AvsClient-based function Invoke-AvsScript -AVSCloudName $AVSCloudName -SubscriptionId $SubscriptionId -AVSResourceGroup $AVSResourceGroup ` -RunCommandId $CmdletId -RunCommandExecName $RunCmdExecutionName -TimeoutInMinutes $TimeoutInMinutes ` -ProductName $ProductName -ProductVersion $ProductVersion ` -CommandParameters $Parameters -ErrorVariable stopError } else { # Use the new REST API-based function Invoke-AvsScriptExecution -AVSCloudName $AVSCloudName -SubscriptionId $SubscriptionId -AVSResourceGroup $AVSResourceGroup ` -RunCommandId $CmdletId -RunCommandExecName $RunCmdExecutionName -TimeoutInMinutes $TimeoutInMinutes ` -ProductName $ProductName -ProductVersion $ProductVersion ` -CommandParameters $Parameters -ErrorVariable stopError } if ($stopError) { Write-Host "RunCommand $RunCmdExecutionName failed with error:" throw $stopError } if ($GetNamedOutputs) { $Output = Get-RunCommandNamedOutput -RunCmdExecutionName $RunCmdExecutionName -AvsPrivateCloudName $AVSCloudName -AvsResourceGroupName $AVSResourceGroup Set-Variable -Name NamedOutputs -Value $Output -Scope Script } } Export-ModuleMember -Function Invoke-RunScript |