Reduce azure-cli chattiness
In its default configuration Azure CLI can be quite chatty, even accidentally echoing secrets to the console if you're not using it wisely. There are a number of settings you can apply to reduce the chattiness and by doing so automatically improve your security posture:
az config set core.only_show_errors=true
az config set core.error_recommendation=off
az config set core.collect_telemetry=false
az config set logging.enable_log_file=false
az config set core.survey_message=false
az config set auto-upgrade.enable=false
az config set core.no_color=true
az config set extension.use_dynamic_install=false
Most switches are explained in the azure cli configuration docs. There is a module called
init that you can use to configure a number of these settings with ease (not all unfortunately):
> az extension add --name init
> az init
Select an option by typing its number
 Optimize for interaction
These settings improve the output legibility and optimize for human interaction
 Optimize for automation
These settings optimize for machine efficiency
AzureCLI@2 task in Azure Pipelines, by default, ignores the global configuration (see below), an even better way to set these options is through environment variables:
For self-hosted runners/agents you can either set these variables in the VMs global environment settings, or in the runner/agent's
Be sure to restart the agent afterwards.
Since workflow/pipeline variables are automatically lifted to environment variables, you can also define these in your workflow or in the repository settings:
# Azure Pipelines
# GitHub Actions
For GitHub Actions, you can import the env file to an organization level variable library in a single command with the github cli:
> gh variable set --env-file .env --organization myorg
For Azure Pipelines, you can create a variable group and import that into every pipeline:
pwsh> az pipelines variable-group create --name azure-cli-default-settings --authorize --variables `
For security capture the output
Some commands (such as reading appsettings) may return connection strings or passwords. You don't want these to end up in the logs. In addition to setting
core.only_show_errors=true, you can protect yourself further by redirecting the output from
az to a variable,
null or a file.
# capture the result in a variable
$output = & az ...
# redirect output
& az ... > $null
& az ... 2>&1 > $null # incl the error stream for extra protection
# write output to a file
& az ... -o ./output.json
& az ... --output ./output.json
Mind the case
While the Azure CLI accepts its commands and parameters in any case you use:
⚠️ Works, but don't do it
az CoNfIG SeT a=b
It greatly reduces the performance, especially on windows, due to the way the internal caching mechanism looks up the command implementations.
Instead, pass all the commands and switches in lowercase:
✅ use lowercase for all commands and switches
az config set a=b
This will save you precious time (about 10 seconds on Linux and up to a minute on Windows) every time you run
Use the global configuration on Hosted Runners/Agents
The Hosted Runners/Agents take great care to set-up and warm-up the Azure CLI to improve its performance, especially the first-run performance. It does so by setting the
AZURE_GLOBAL_CONFIG environment variable to a folder that has been prepped during the virtual machine image creation.
Do not overwrite the
AZURE_GLOBAL_CONFIG variable in your own scripts.
However, on Azure Pipelines, the
AzureCLI@2 task overwrites the
AZURE_GLOBAL_CONFIG variable and redirects it to the agent's temp directory.
You can add a switch to the task to turn off this behavior:
- task: AzureCLI@2
This was the default behavior in
Using the global configuration will reduce the time needed to set-up the task by more than 1 minute on the Windows Hosted Runner/Agent and by about 10 seconds on Linux.
You might wonder why it doesn't use global config by default. The reason for this is to support multiple agents on the same VM calling
azat the same time. By redirecting the global config for each agent to its own temp directory they can't accidentally overwrite each other's settings or fight over file locks.
For self-hosted non-ephemeral runners/agents it also ensures that each job starts with a fresh set of settings as the temp folder is cleared before each run.
Since the hosted runners/agents don't run more than one job in parallel and always start with a fresh VM, there is no need to perform this redirection.
az devops: Don't rely on auto-discover for speed
The Azure DevOps extension for Azure CLI offers an auto-discover option which uses the git repository's remote to automatically identify the Azure DevOps organization and project. While super convenient, it takes time to look up this information each time you run
az devops or a related command.
Instead, pass the
--project settings explicitly:
- pwsh: |
az pipelines show --organization=$env:SYSTEM_COLLECTIONURI --project=$env:SYSTEM_TEAMPROJECT
Or set them once as defaults at the start of your workflow:
- pwsh: |
az devops configure --defaults organization=$env:SYSTEM_COLLECTIONURI project=$env:SYSTEM_TEAMPROJECT
az devops: Don't use
AzureCLI@2, use standard shell instead
AzureCLI@2 task does a number of setup steps, authenticates to Azure, redirects the global configuration folder, does an update-check... But if you only need to run
az devops commands, then you don't need any of that.
In that case you can use the standard scripting features of Azure Pipelines such as
- task: Bash@2 or
- task: PowerShell@2 to gain a massive performance boost.
az devops: Use environment variable for authentication in Azure Pipelines
When you're using the
az devops extension, you can authenticate in 3 ways:
az devops login
- environment variable
Since Azure Pipelines already holds an authentication token in its environment, the fastest way to authenticate is to leverage that token:
- pwsh: |
az pipelines list --organization=$env:SYSTEM_COLLECTIONURI --project=$env:SYSTEM_TEAMPROJECT
You do have to pass the token explicitly, because Azure Pipelines won't pass secrets to tasks by default.