Skip to content

Instantly share code, notes, and snippets.

@manualbashing
Last active March 5, 2023 21:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save manualbashing/10636d8140b1d1f58534e5b9c7ec4425 to your computer and use it in GitHub Desktop.
Save manualbashing/10636d8140b1d1f58534e5b9c7ec4425 to your computer and use it in GitHub Desktop.
A primitive az cli PowerShell wrapper for output parsing and error handling

Az CLI has many advantages over the Az PowerShell modules, but natively it does not integrate very nicely with PowerShell scripts. That is mainly because of two reasons:

  1. The output of AZ CLI needs to be parsed in order to chain commands together
  2. Exception handling within PowerShell's try-catch clauses is not possible

Both problems can be tackled with a very simple wrapper function:

function paz {

    $errorLogPath = "$([system.io.path]::GetTempPath())/azerror.log"
    $outLogPath = "$([system.io.path]::GetTempPath())/azout.log"
    
    filter InvokeAzCommand {
        
        $null > $errorLogPath
        $null > $outLogPath
        $_ | 
            Invoke-Expression 2>$errorLogPath | 
            Tee-Object -Path $outLogPath
    }

    $azCommand = "az $args"
    switch ($azCommand) {
        { $_ -match '--help' } {

            $azCommand | 
                InvokeAzCommand | 
                Write-Host
        }
        { $_ -match '^az find' } {

            # Quotes are not preserved in args, but needed for `az find "foo bar"`
            $azCommand -replace '^(az find )(.*$)', '$1"$2"' | 
                InvokeAzCommand
            
        }
        Default {
            "$azCommand -o json" | 
                InvokeAzCommand | 
                ConvertFrom-Json
        }
    }

    if ($LastExitCode -eq 0) {

        <# 
            Commands like `az find` write progress information into the error stream, so we take 
            it out from there.
          #>
        Get-Content $errorLogPath -Raw | Write-Host
        $null > $errorLogPath
        
    } else {
        <# 
                Any none zero exit code is "not successful"
                If something is written to stdout at the same time we assume, that the content
                of the error stream is meant to be a mere warning.
              #>
        $lastErrorFromAzCommand = Get-Content $errorLogPath -Raw
        $lastOutFromAzCommand = Get-Content $outLogPath -Raw
        if ($lastErrorFromAzCommand -is [string]) {
        
            if ($lastOutFromAzCommand -is [string]) {
        
                Write-Warning $lastErrorFromAzCommand
            } else {
                throw $lastErrorFromAzCommand
            }
        }
    }
}

Note: This probably does not work with all commands, yet. It seems unpredictable, which commands will pollute the error stream with warnings or progress information (az find wtf?) But maybe the approach is not too bad after all. Any suggestions are welcome 😊.

Example usage

Output (other than help pages) is always parsed into PowerShell objects:

paz group list | Group-Object location

Errors from the command are thrown as execptions:

try {
    paz group show --name 'this-rg-doesnt-exist'
}
catch {
    Write-Error "Yep, that didn't work: $_"
}

Help pages go to the information stream and do not clutter the pipeline:

paz group list --help | ForEach-Object { "You will never see this message" }

Find even works without explicitly quoting the search string:

paz find delete azure backup vault
published: true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment