Parts 2 & 3 - Wrapping up
Here are some things I had to learn when building distribution for azd.
Note This blog series was started in 2022, and the Azure Developer CLI plus its installer story have changed considerably since then. Let’s get this finished so I can go on to writing other things.
Part 2: Download and install
The azd installers come in two flavors: Bash and cross-platform PowerShell. This gives us good coverage on our supported OS platforms.
| Platform | Bash | PowerShell |
|---|---|---|
| Windows | 🚫 Not by Default | ✅ Default |
| Linux | ✅ Default | 🚫 Not by Default |
| MacOS | ⚠️ Bash 3 | 🚫 Not by Default |
Most non-Windows platforms have bash or a straightforward way to install bash.
⚠️ MacOS uses bash 3. So our script needs to run without some of the fancy features available in bash 4 (like associative arrays).
PowerShell, MSI, Cross Platform installs
On Windows we switched to using an MSI and in the install script uses msiexec to install. If the PowerShell script is running on a non-Windows platform it uses the bash install script with equivalent parameters.
2.1. Bash fun: Detecting platform and architecture
The current bash installer pulls apart the download target into three pieces:
- platform (
linuxordarwin) - archive format (
tar.gzorzip) - CPU architecture (
amd64orarm64)
It gets all of that from the machine it is currently running on.
Detecting the platform
The installer asks uname -s for the operating system name and then normalizes that value into the names used in the published artifact filenames. uname is available on all platforms tested.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
get_platform() {
local platform_raw
platform_raw="$(uname -s)"
if [ "$platform_raw" = "Linux" ]; then
echo 'linux'
return 0
elif [ "$platform_raw" = "Darwin" ]; then
echo 'darwin'
return 0
else
say_error "Platform not supported: $platform_raw"
return 1
fi
}
There are only two successful paths here:
LinuxbecomeslinuxDarwinbecomesdarwin
Anything else is treated as unsupported and the script stops. No support today for BSD or other platforms.
Choosing the archive extension
Once the platform is known, the installer uses it to decide which kind of archive it should download.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
get_extension_for_platform() {
platform=$1
if [ "$platform" = "linux" ]; then
echo 'tar.gz'
return
elif [ "$platform" = "darwin" ]; then
echo 'zip'
return
else
say_error "Platform not supported: $platform"
exit 1
fi
}
That means Linux downloads a .tar.gz archive, while macOS downloads a .zip.
Detecting the architecture
The CPU architecture comes from uname -m. The script then maps a few common machine identifiers to the names used by the release pipeline.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
get_architecture() {
local platform=$1
local architecture_raw
architecture_raw="$(uname -m)"
if [ "$architecture_raw" = "x86_64" ]; then
echo 'amd64'
return
elif [ "$architecture_raw" = "arm64" ]; then
echo "$architecture_raw"
return
elif [ "$architecture_raw" = "aarch64" ]; then
echo 'arm64'
return
else
say_error "Architecture not supported: $architecture_raw on platform: $platform"
exit 1
fi
}
So today the mapping is:
x86_64→amd64arm64→arm64aarch64→arm64
One notable change from the original 2022 version: Apple Silicon is no longer forced onto the amd64 build. If uname -m reports arm64, the installer now downloads the native arm64 binary.
Putting it together
The installer wires those pieces up right away:
1
2
3
4
5
6
platform="$(get_platform)"
extension="$(get_extension_for_platform "$platform")"
if [ "${architecture:-}" = "" ]; then
architecture="$(get_architecture "$platform")"
fi
By the time URL construction happens, the script already knows the normalized platform, the right archive extension, and the architecture string to plug into the download path.
2.2. Download the binary
The URL format, which hasn’t changed much since 2022, is:
1
https://<base-url>/<version>/azd-<platform>-<architecture>.<extension>
<base-url>is the base part of the URL used to download releases (e.g.https://azuresdkartifacts.z5.web.core.windows.net/azd/standalone/release)<version>represents either a specific version or a “channel” of release. Today that meansdaily,stable, or a specific version like1.23.7
⚠️ Warning: The
<base-url>has changed several times as we’ve migrated hosting providers and storage accounts. Don’t rely on the host name you see here as it could easily change in the future.
You’ll notice that the URL assembly process is specific to the publishing system that we’ve built. You’ll want to assemble URLs specific to your system. Ours gives us flexibility to assemble install stanzas for a variety of practical scenarios that would exercise our install script in PRs (similar to how we would exercise the azd tool itself in pull requests).
In bash
In bash we use curl to download the file (curl is implied to be on the system in the install stanza itself curl -fsSL https://aka.ms/install-azd.sh | bash).
In PowerShell
We do similar platform detection, it’s easier on Windows because the install script supports only x86_64 today so there’s one MSI to download.
2.3. Copy the binary to a standard location, install the MSI
Linux, MacOS: copy to /opt/microsoft/azd, link to /usr/local/bin
The /opt folder is generally used for optional software packages on Linux and MacOS. It’s not specifically in $PATH so we’ll need to symlink the binary in a location that’s already in $PATH.
/usr/local/bin is a good spot to link. It’s a common location for binaries installed on the local system and is already included in $PATH on tested systems. There are corner cases on MacOS where it’s in $PATH but the directory itself doesn’t exist.
In that case, we detect it’s not there and report an error with instructions to the user on how to fix it.
1
2
3
4
5
6
if [ "$symlink_folder" != "" ] && [ ! -d "$symlink_folder" ]; then
say_error "Symlink folder does not exist: $symlink_folder. The symlink folder should exist and be in \$PATH"
say_error "Create the folder (and ensure that it is in your \$PATH), specify a different folder using -s or --symlink-folder, or specify an empty value using -s \"\" or --symlink-folder \"\""
save_error_report_if_enabled "InstallFailed" "SymlinkFolderDoesNotExist"
exit 1
fi
Why not copy the binary directly to /usr/local/bin?
The zip package includes other files that are required to be placed alongside the installed binary. Instead of writing those files to a location where the intent of those files can be ambiguous and not directly useful to the user’s day to day tasks, we place the full installation in /opt/microsoft/azd and link the relevant binary from /usr/local/bin.
I want to install in ~/.bin
The install bash script supports both --install-folder and --symlink-folder parameters. Make sure you specify those properly each time you upgrade otherwise the installer will use the default configuration.
Windows: Install the MSI
Use msiexec and Wait-Process so that the script doesn’t finish before the installation completes. You can also evaluate the exit code of msiexec to determine if the installation succeeded or failed.
Part 3: Other distribution mechanisms
WinGet & Chocolatey
One of the early benefits of using MSI is that we can easily onboard to WinGet and Chocolatey, two popular package managers on Windows.
For WinGet, we use wingetcreate.exe to create a manfiest and open a PR to the winget-pkgs repository. Once the PR is merged, the package is available in the WinGet repository.
For Chocolatey, we use choco new to create a package and then choco push to publish it to the Chocolatey repository.
Homebrew
We created a Homebrew tap to distribute azd on MacOS. The tap is a GitHub repository that contains the formula for installing azd. The formula is a Ruby file that defines how to download and install azd on MacOS.
Later edits include Linux support which means that users can now brew install azd on Linux as well.
Part 4: Testing
I found setting up Dockerfile-based tests to be helpful for testing azd installation in various types of popular Linux distributions. Azure DevOps and GitHub Actions both offer MacOS and Windows machines for more complete coverage.
Since install tests can be automated, I’m able to run these tests before release to catch any issues with the install process before customers do.
Install testing has detected more platform-specific issues with the azd binary itself than with the installer.
ShellCheck
Use ShellCheck on your bash scripts to catch common issues. I’m already bad at writing bash scripts but ShellCheck makes me look like a very smart cookie.
Special Thanks
Heath Stewart is responsible for much of the MSI work to date and his knowledge has been indispensable to the success of azd’s growth and distribution. He also helped with other parts of the install process. I have immense gratitude for his help and guidance. If you need to land bits on a machine, Heath is one of the best people to talk to.
Claims, Disclaims
AI Assistance
I wrote this blog post with some help from AI. I read it over and edited it but some parts have been written by AI.
It’s important to be transparent about AI assistance. So much of the stuff I read now is crowded with AI turns of phrase and can be skimmed or ignored. Phrases like “It’s not just a blog post, it’s a platform for ideation. This is a step change.” are a waste of human cognition so I eliminated them where I found them.
Warranty?
No warranty. This is a personal blog. There are no guarantees about relevance, product direction, support, or anything else. It’s intended as educational information. Getting things right is an exercise for the reader who is in charge of their own success.