Azure DevOps walkthrough
This sets up a modusOps environment in an Azure DevOps organisation end-to-end: project, repos, feed, vendored templates, and the pipelines that run them.
Prerequisites
- An Azure DevOps organisation (modern
https://dev.azure.com/{org}form). - Permission to create a project, repository, and Artifacts feed in it.
Every step below can be done by hand in the portal with no Personal Access Token. modusOps also automates the setup with cmdlets; that path needs a PAT, given as the second option in each step.
1. Create the project, repo, and feed
Manual (portal)
- Create a project.
- Create a repository in it for your operation.
- Create an Azure Artifacts feed for the modules, and add the project Build Service as a reader on it.
Automated (requires a PAT)
New-MOAzureDevOpsModusEnvironment does all three idempotently (get-or-create). The PAT needs:
| Scope | Access |
|---|---|
| Project and Team | Read, write & manage |
| Code | Read, write & manage |
| Packaging | Read, write & manage |
| Identity | Read |
$pat = Get-Credential -UserName 'pat' -Message 'Azure DevOps PAT' # the PAT is the password
New-MOAzureDevOpsModusEnvironment -OrganizationUri 'https://dev.azure.com/myorg' -Credential $pat -Verbose
The remaining cmdlet-driven steps reuse this $pat. Each can also be done in the portal if you prefer to stay fully manual.
2. Vendor the templates
Vendor the two default templates into a staging tree, pinned in .modusops.lock:
Add-MOTemplate -Name registerModusOpsFeeds -Platform azd -Version v0.1.1 -Path ./staging/templates
Add-MOTemplate -Name installModusOpsModules -Platform azd -Version v0.1.1 -Path ./staging/templates
Test-MOTemplate -ProjectPath ./staging
3. Seed the operations repo
Stage your starter pipeline alongside the vendored templates, then push the folder as the repo’s first commit:
Push-MOAzureDevOpsModusContent -OrganizationUri 'https://dev.azure.com/myorg' -Credential $pat `
-RepositoryName modusOps -SourcePath ./staging -Verbose
4. Create the pipelines
New-MOAzureDevOpsModusPipeline -OrganizationUri 'https://dev.azure.com/myorg' -Credential $pat `
-RepositoryName modusOps -Name 'modusOps Example' -YamlPath '/example.yml'
5. Wire policies and permissions
# Make a pipeline a required PR check
Add-MOAzureDevOpsModusBuildValidation -OrganizationUri 'https://dev.azure.com/myorg' -Credential $pat `
-RepositoryName modusOps -BuildDefinitionId <id> -DisplayName 'PR Validation'
# Let the build service push tags and post PR comments
Set-MOAzureDevOpsModusRepoPermission -OrganizationUri 'https://dev.azure.com/myorg' -Credential $pat `
-RepositoryName modusOps
# Authorize a pipeline to use a referenced repo resource (if any)
Add-MOAzureDevOpsModusResourceAuthorization -OrganizationUri 'https://dev.azure.com/myorg' -Credential $pat `
-RepositoryName modusOpsTemplates -PipelineId <id>
Manual grants to confirm
A few permissions are surfaced as terminal warnings rather than set silently - confirm them in the portal:
- Artifacts -> Feed -> Settings: add the project Build Service as a Feed Publisher (if your pipelines publish modules).
- Project Settings -> Repositories -> Security: the Build Service has Contribute, Contribute to PRs, Create Tag, and Read (
Set-MOAzureDevOpsModusRepoPermissioncovers the first two).
From here, a run uses the credential model to install version-pinned modules with a contained token.
A worked example: a SharePoint data round-trip
Beyond the two load-bearing templates, the library has step templates for connecting to Microsoft Graph and moving SharePoint files - enough to build a real pipeline. Vendor them alongside the others:
Add-MOTemplate -Name connectMicrosoftGraph -Platform azd -Path ./staging/templates
Add-MOTemplate -Name getSharePointFile -Platform azd -Path ./staging/templates
Add-MOTemplate -Name setSharePointFile -Platform azd -Path ./staging/templates
A thin operation that downloads a workbook, runs a module against it, and writes it back - example.yml in your operations repo:
trigger: none
pool: { vmImage: 'ubuntu-latest' }
variables:
- group: modusOps # supplies clientId, tenantId, and the secret variable 'clientSecret'
jobs:
- job: run
variables:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
steps:
- template: templates/registerModusOpsFeeds.yml
parameters:
feeds: [ { name: 'modusops' } ]
- template: templates/installModusOpsModules.yml
parameters:
modules: # public modules resolve from MAR (or an allowed PSGallery)
- { name: 'Microsoft.Graph.Authentication', repository: 'MAR' }
- { name: 'YourBusinessModule', repository: 'modusops', version: '1.0.0' }
- template: templates/connectMicrosoftGraph.yml
parameters:
clientId: $(clientId)
tenantId: $(tenantId) # clientSecretVariable defaults to 'clientSecret'
- template: templates/getSharePointFile.yml
parameters:
sharePointSite: 'MySite'
sharePointFile: 'Reports/workbook.xlsx'
localFile: '$(Pipeline.Workspace)/workbook.xlsx'
- pwsh: |
Import-Module YourBusinessModule
# ... transform $(Pipeline.Workspace)/workbook.xlsx ...
displayName: 'Run business logic'
- template: templates/setSharePointFile.yml
parameters:
sharePointSite: 'MySite'
sharePointFile: 'Reports/workbook.xlsx'
localFile: '$(Pipeline.Workspace)/workbook.xlsx'
Connect once per job.
connectMicrosoftGraphestablishes the Graph connection; theget/setSharePointFilesteps carry no credentials and inherit it (they fail with a directive error if no connection is found). The connection persists across steps within a job - the agent is shared - but not across jobs, which is the same boundary the register/install vault relies on. So keep connect + the SharePoint steps in one job; a separate notification job is cleanly isolated and would re-connect if it needed Graph.