Run cross-platform scripts

Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019

With Azure Pipelines, you can run your builds on macOS, Linux, and Windows machines. If you develop on cross-platform technologies such as .NET Core, Node.js and Python, these capabilities bring both benefits and challenges.

For example, most pipelines include one or more scripts that you want to run during the build process. But scripts often don't run the same way on different platforms. You can use the script keyword shortcut to make writing scripts easier and also can use conditions to target specific platforms with your scripts.

Run cross-platform tools with a script step

The script keyword is a shortcut for the command line task. The script keyword runs Bash on Linux and macOS and cmd.exe on Windows.

Using script can be useful when your task just passes arguments to a cross-platform tool. For instance, calling npm with a set of arguments can be easily accomplished with a script step. script runs in each platform's native script interpreter: Bash on macOS and Linux, cmd.exe on Windows.

steps:
- script: |
    npm install
    npm test

Handle environment variables

Environment variables throw the first wrinkle into writing cross-platform scripts. Command line, PowerShell, and Bash each have different ways of reading environment variables. If you need to access an operating system-provided value like PATH, you'll need different techniques per platform.

However, Azure Pipelines offers a cross-platform way to refer to variables that it knows about called macro syntax. By surrounding a variable name in $( ), it's expanded before the platform's shell ever sees it. For instance, if you want to echo out the ID of the pipeline, the following script is cross-platform friendly:

steps:
- script: echo This is pipeline $(System.DefinitionId)

This also works for variables you specify in the pipeline.

variables:
  Example: 'myValue'

steps:
- script: echo The value passed in is $(Example)

Consider Bash or pwsh

If you have more complex scripting needs than the examples shown above, then consider writing them in Bash. Most macOS and Linux agents have Bash as an available shell, and Windows agents include Git Bash or Windows Subsystem for Linux Bash.

For Azure Pipelines, the Microsoft-hosted agents always have Bash available.

For example, if you need to make a decision about whether your build is triggered by a pull request:

trigger:
    batch: true
    branches:
        include:
        - main
steps:
- bash: |
    echo "Hello world from $AGENT_NAME running on $AGENT_OS"
    case $BUILD_REASON in
            "Manual") echo "$BUILD_REQUESTEDFOR manually queued the build." ;;
            "IndividualCI") echo "This is a CI build for $BUILD_REQUESTEDFOR." ;;
            "BatchedCI") echo "This is a batched CI build for $BUILD_REQUESTEDFOR." ;;
        *) $BUILD_REASON ;;
    esac
  displayName: Hello world

PowerShell Core (pwsh) is also an option. It requires each agent to have PowerShell Core installed.

Switch based on platform

In general, we recommend that you avoid platform-specific scripts to avoid problems such as duplication of your pipeline logic. Duplication causes extra work and extra risk of bugs. However, if there's no way to avoid platform-specific scripting, then you can use a condition to detect what platform you're on.

For example, suppose that for some reason you need the IP address of the build agent. On Windows, ipconfig gets that information. On macOS, it's ifconfig. And on Ubuntu Linux, it's ip addr.

Set up the below pipeline, then try running it against agents on different platforms.

steps:
# Linux
- bash: |
    export IPADDR=$(ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1  -d'/')
    echo "##vso[task.setvariable variable=IP_ADDR]$IPADDR"
  condition: eq( variables['Agent.OS'], 'Linux' )
  displayName: Get IP on Linux
# macOS
- bash: |
    export IPADDR=$(ifconfig | grep 'en0' -A3 | grep inet | tail -n1 | awk '{print $2}')
    echo "##vso[task.setvariable variable=IP_ADDR]$IPADDR"
  condition: eq( variables['Agent.OS'], 'Darwin' )
  displayName: Get IP on macOS
# Windows
- powershell: |
    Set-Variable -Name IPADDR -Value ((Get-NetIPAddress | ?{ $_.AddressFamily -eq "IPv4" -and !($_.IPAddress -match "169") -and !($_.IPaddress -match "127") } | Select-Object -First 1).IPAddress)
    Write-Host "##vso[task.setvariable variable=IP_ADDR]$IPADDR"
  condition: eq( variables['Agent.OS'], 'Windows_NT' )
  displayName: Get IP on Windows

# now we use the value, no matter where we got it
- script: |
    echo The IP address is $(IP_ADDR)