Azure Pipelines for Legacy .NET Projects
If, like me, you’ve had to go through the process of migrating build pipelines for legacy .NET Framework to Azure DevOps, you know it can be a struggle without a template to work from.
To that end, here is a very quick post you can use as the basis for your shiny new YAML pipeline.
NB: Sending your artifacts to your deployment system and pushing packages to NuGet are left as an exercise to the reader
name: 1.0.0.$(Rev:r) # Ensures build name follows the version number
trigger:
- '*' # Build on changes to any branch
pool:
name: 'MyPool' # Ensures Pipeline runs on a certain pool that may have specially configured agents
demands:
- agent.os -equals Windows_NT
variables:
- name: applicationName
value: 'MyApplication'
- name: solution
value: '$(applicationName).sln'
- name: 'buildPlatform'
value: 'Any CPU'
- name: 'buildConfiguration'
value: 'Release'
- name: hostOutputPath # Build output location for the main application binaries
value: '$(applicationName)/bin/$(buildConfiguration)'
- name: nugetSource
value: 'https://api.nuget.org/v3/index.json' # Public NuGet feed, but replace with a custom one if necessary
- name: signCommand
value: '"C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe" sign /a /fd SHA256' # Automatically locate signing certificate
workspace:
clean: all # Ensure agent build directories start empty
steps:
- script: |
echo Restoring packages for ${{ solution }} using source ${{ nugetSource }}
nuget.exe restore ${{ solution }} -Source ${{ nugetSource }} -Verbosity Detailed -NonInteractive -NoCache
displayName: "Restore NuGet packages with CLI"
- task: VSBuild@1
displayName: 'Build Solution'
inputs:
solution: '$(solution)'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
- task: VSTest@2
displayName: 'Run Tests'
inputs:
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
otherConsoleOptions: '/platform:x64' # Only needed to use the 64-bit test runner
# Ignore integration tests on non-master branches
${{ if ne(variables['Build.SourceBranch'], 'refs/heads/master') }}:
testFiltercriteria: 'Category!=Integration'
runInParallel: True
- script: |
echo Using '${{ signCommand }}' to sign executables in '${{ hostOutputPath }}'
${{ signCommand }} ${{ hostOutputPath }}/*.exe
displayName: 'Sign Executables'
# Add steps to send artifacts to your deployment system e.g. Octopus
# Create and push NuGet packages with NuGet CLI
Simply create a new file in the root
of your repository (e.g. azure-pipelines.yaml
) and push. You can then configure the pipeline through the Azure UI.
Step Templates and Shared Variables
As you build out more pipelines, you may wish to create standardised step templates. To do so, create a new repository to hold your YAML files.
As an example, the Sign Executables
task in the pipeline above could be extracted out to its own template called sign-executables.yaml
:
parameters:
- name: hostOutputPath
type: string
- name: signCommand
type: string
steps:
- script: |
echo Using '${{ signCommand }}' to sign executables in '${{ hostOutputPath }}'
${{ signCommand }} ${{ hostOutputPath }}/*.exe
displayName: 'Sign Executables'
The template repository can then be referred to you in your pipeline with a resources
section as follows:
resources:
repositories:
- repository: taskTemplatesAndVariables # This is a label used to refer to tasks in the steps section
type: git
name: TeamProject/TemplatesAndVariables # Path to your template repo in Azure DevOps
ref: refs/heads/v1 # Branch name
The task template can then be added as a step in your pipeline:
- template: sign-executables.yaml@taskTemplatesAndVariables
parameters:
hostOutputPath: $(hostOutputPath)
signCommand: $(signCommand)
Variable values can also be configured in a yaml file in your new repository (e.g. variables.yaml
):
variables:
buildConfiguration: 'Release'
buildPlatform: 'Any CPU'
These can then be referenced in a similar way as part of the variables
section of your pipeline:
variables:
- template: variables.yaml@taskTemplatesAndVariables
- name: applicationName
value: 'MyApplication'
- name: solution
value: '$(applicationName).sln'
Image credits:
- Factory header image by Ant Rozetsky on Unsplash