functions/azure/azcli/Invoke-AzCli.ps1

# <copyright file="Invoke-AzCli.ps1" company="Endjin Limited">
# Copyright (c) Endjin Limited. All rights reserved.
# </copyright>

<#
.SYNOPSIS
Invokes azure-cli commands and returns the results.

.DESCRIPTION
Provides a wrapper for invokes an azure-cli commands.

.PARAMETER Command
The azure-cli command you want to execute, excluding the 'az' reference.

.PARAMETER AsJson
Controls whether you expect the command to have a JSON response that you want returned as output.

.PARAMETER ExpectedExitCodes
An array of exit codes that will not be treated as signifying an error.

.OUTPUTS
When the '-AsJson' parameter is supplied, the JSON output from azure-cli will be returned as a hashtable.

#>

function Invoke-AzCli
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        $Command,
        
        [switch] $AsJson,
        
        [array] $ExpectedExitCodes = @(0),

        [switch] $SuppressConnectionValidation
    )

    # Ensure the AzureCLI is installed and available
    if (!(Get-Command "az")) {
        throw "The AzureCLI could not be found. If recently installed, you may need to restart your session to update your PATH configuration."
    }

    # Ensure we have a validation AzureCLI connection, unless explicitly suppressed
    # (e.g. when this module is trying to login)
    if ( $SuppressConnectionValidation -or (_EnsureAzureConnection -AzureCli) ) {

        # If passed an array of command arguments, concatenate them into single comnmand-line string
        if ($Command -is [array]) { $Command = ($Command -join " ") }

        $cmd = "az $Command"
        if ($asJson) { $cmd = "$cmd -o json" }
        Write-Verbose "azcli cmd: $cmd"

        $ErrorActionPreference = 'Continue'     # azure-cli can sometimes write warnings to STDERR, which PowerShell treats as an error

        # Execute the azure-cli and capture the results and any StdErr output
        $res,$azCliStdErr = _invokeAzCli $cmd
        
        $diagnosticInfo = @"
StdOut:
$($res -join "`n")
StdErr:
$($azCliStdErr -join "`n")
"@

        $ErrorActionPreference = 'Stop'
        if ($expectedExitCodes -inotcontains $LASTEXITCODE) {
            Write-Warning "azure-cli error diagnostic information:`nCommand: $cmd`n$diagnosticInfo"
            Write-Error "azure-cli failed with exit code: $LASTEXITCODE - check previous logs for more details"
        }

        Write-Verbose $diagnosticInfo

        if ($asJson) {
            return ($res | ConvertFrom-Json -Depth 30 -AsHashtable)
        }

        return $res,$azCliStdErr
    }
}

# Extract function for mocking purposes
function _invokeAzCli
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string] $CommandLine
    )

    # Capture any error messages so they can be properly logged
    # NOTE: '-ErrorVariable' on Invoke-Expression seems only to work properly if the command-line contains some STDERR redirection,
    # otherwise the error variable is always null.
    $output = Invoke-Expression "$CommandLine 2>''" -ErrorVariable stdErr

    return $output,$stdErr
}