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:
- The output of AZ CLI needs to be parsed in order to chain commands together
- 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 😊.
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