Azure DevOps pipelines theory
All steps in an Azure DevOps pipeline get executed on a build agent, which is in essence a computer that provides a particular set of functionalities (like having PowerShell or node.js installed, etc.).
Things get a bit more complicated when you need to run actions with conflicting dependencies on a build agent. The solution to this is either multiple build agents with their own set of dependencies installed, or running a Docker container for each set of dependencies and run your actions in the container.
Azure DevOps calls this a Container Job. Per usual, you still select a build agent but you also specify the container(s) from Docker Hub or other repositories (like Azure Container Registry) and they will be started on the agent.
You can still keep using all DevOps tasks and functionalities (like variables) in your pipeline, but they will now be executed in the container instead of directly on the build agent!
Very elegant, no?
There is a much better, much more detailed explanation of Container Jobs on docs.microsoft.com but this should be enough to understand the next part.
Using the CLI for Microsoft 365 Docker image in DevOps
CLI for Microsoft 365 is publishing images on Docker Hub, for each new release of CLI a new image will be made available automatically. It is a very easy and convenient way to get CLI and it's dependencies in your DevOps pipeline, to execute actions.
In my YAML definition I define the trigger, connect to the variable group and select a build agent on which everything will run:
trigger: - master variables: - group: "Key Vault Secrets" pool: vmImage: 'ubuntu-latest'
I define a job and configure it so that every step will be executed inside the Dcoker container provided by CLI for Microsoft 365:
jobs: - job: RunInContainer container: image: m365pnp/cli-microsoft365 options: --user 0:0 steps:
The special trick here is to provide
--user 0:0 as options. When DevOps spins up the container, it does some magic tricks to make build variables and custom variables available inside it. It needs root access and this option will provide it.
Every subsequent step inside of this job will be executed in the container, instead of on the agent.
Inline script in bash
- script: | m365 login --authType certificate --certificateBase64Encoded "$(YannicksDevOpsPipeline)" --appId e5c08ad8-xxxx-xxxx-b429-a266c94bc638 --tenant b868e8f4-xxxx-xxxx-8c95-7f432eba848b m365 spo web get --webUrl https://contoso.sharepoint.com/sites/crisismanagement m365 logout displayName: "Docker: Run M365 CLI in Bash"
Variables (both out-of-the-box as well as custom defined) can be used directly within the script itself.
Inline PowerShell script
- task: PowerShell@2 inputs: targetType: 'inline' script: | Write-Host "I'm PowerShell" m365 login --authType certificate --certificateBase64Encoded "$(YannicksDevOpsPipeline)" --appId e5c08ad8-xxxx-xxxx-b429-a266c94bc638 --tenant b868e8f4-xxxx-xxxx-8c95-7f432eba848b m365 spo web get --webUrl https://contoso.sharepoint.com/sites/crisismanagement m365 logout pwsh: true displayName: "Docker: Run M365 CLI in PowerShell, inline script"
Similar as in bash, the variables are available in here too.
External PowerShell script
- task: PowerShell@2 inputs: filePath: './m365cliscript.ps1' arguments: '-certificate "$(YannicksDevOpsPipeline)" -clientId e5c08ad8-xxxx-xxxx-b429-a266c94bc638 -tenantId b868e8f4-xxxx-xxxx-8c95-7f432eba848b' pwsh: true displayName: "Docker: Run M365 CLI in PowerShell, file path script"
With an externalized script, if you need pipeline variables in your script, you'll need to pass them as arguments.
Container jobs in Azure DevOps are very powerful, and with the Docker image provided by CLI for Microsoft 365 we have another great way to automate actions against our Microsoft 365 environment (+ it is about twice as fast in my tests as opposed to installing it on the hosted build agent).
Keep building, keep deploying!