In this article, I will show you the Azure pipeline YAML code that you can use to build and deploy a Windows service application. The Windows service I used is a .NET Core application. The build stage is almost the same as in the post about building & deploying an ASP.NET Core Web API to IIS. However, the steps for deploying a Web API to IIS differ greatly from deploying a Windows service to a virtual machine. I will show you how to deploy the Windows service using PowerShell.
Before I show you the Azure pipeline, let’s recap its use case and how these processes are called. With Azure pipelines, you can automatically build, test and deliver your code to any destination. The process of automatically building and testing your code is called continuous integration. When the pipeline also delivers code to a destination after completion of the build and test process and clicking on a deploy button, it is called continuous delivery. There is a similar approach called continuous deployment. When using this approach, the deployment of your code is automated, rather than requiring a click of a button for this last step.
The image below shows the graphical representation of the Azure pipeline YAML code.
Table of Contents
The Build Stage For The Windows Service (.NET Core)
The example below shows the YAML code for the build stage. At the very top before the build stage, there is a trigger on the main branch. This means that when code is pushed to the main branch, the Azure pipeline will start. The build stage is the first stage of the multi-stage pipeline. As can be seen in the example below, the first step will install the NuGet Tool which is used in step 2 to restore NuGet packages. After restoring the packages, step 3 will build the .NET Core application using the publish command. Then, after successful unit testing in step 4, the build is published as an artifact so it can be downloaded in the deploy stage shown in the next chapter.
trigger: - main stages: - stage: Build displayName: 'Build Windows Service' jobs: - job: 'Build' pool: vmImage: 'windows-latest' variables: solution: '**/*.sln' buildPlatform: 'Any CPU' buildConfiguration: 'Release' steps: - task: NuGetToolInstaller@1 displayName: 'Install NuGet Tool' - task: NuGetCommand@2 displayName: 'Restore Packages' inputs: restoreSolution: '$(solution)' - task: DotNetCoreCLI@2 displayName: 'Start Build' inputs: command: 'publish' publishWebProjects: false projects: '$(Build.SourcesDirectory)\MyWindowsService\MyWindowsService\MyWindowsService.csproj' arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)' zipAfterPublish: false - task: VSTest@2 displayName: 'Run Unit Tests' inputs: platform: '$(buildPlatform)' configuration: '$(buildConfiguration)' testSelector: 'testAssemblies' testAssemblyVer2: | **\*Test.dll **\*Tests.dll !**\*TestAdapter.dll !**\obj\** - task: PublishBuildArtifacts@1 condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) displayName: 'Publish Build' inputs: PathtoPublish: '$(Build.ArtifactStagingDirectory)' ArtifactName: 'drop' publishLocation: 'Container'
The Deploy Stage For The Windows Service (.NET Core)
The example below shows the YAML code for the deploy stage. It starts with a condition that states that the deploy stage will not be executed if the pipeline trigger came from a pull request. The reason I use this condition is because I like to reuse my multi-stage pipelines for pull requests. And when a new pull request is created, I don’t want to deploy the application, but only verify if the build and unit tests were successful.
When this stage runs, it uses the configured environment to connect to a virtual machine. If you want more information on how to configure an environment like this, then read: Create VM Environment And Install Windows Agent.
The first step will download the build artifacts published by the build stage. After that, the second step starts a PowerShell task that executes a script. This script copies the Windows service build artifacts to a file location on the Windows machine. Before starting the new Windows service version, the script first stops the currently installed version. Then, it updates the bin path of the Windows service to the location of the new version and then starts it.
- stage: Deploy condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) displayName: 'Deploy Windows Service' dependsOn: Build jobs: - deployment: 'DeployToTest' displayName: 'Deploy Windows Service to test environment' environment: name: Service-Test resourceType: VirtualMachine strategy: runOnce: deploy: steps: - download: current displayName: 'Download Build' artifact: drop - task: PowerShell@2 inputs: targetType: 'inline' script: | Set-PSDebug -Trace 1 Write-Host "BuildNumber = $(Build.BuildNumber)" Copy-Item -Path "$(Pipeline.Workspace)\drop\MyWindowsService" -Destination "D:/Program Files/Services/MyWindowsService/$(Build.BuildNumber)" -Recurse $serviceName = "MyWindowsService" Write-Host "Stopping current service $serviceName" sc.exe stop "$serviceName" Write-Host "Updating binPath to D:/Program Files/Services/MyWindowsService/$(Build.BuildNumber)/MyWindowsService.exe" sc.exe config "$serviceName" binPath="D:/Program Files/Services/MyWindowsService/$(Build.BuildNumber)/MyWindowsService.exe" Write-Host "Starting service $serviceName" sc.exe start "$serviceName" errorActionPreference: 'silentlyContinue'
Conclusion
In this article, I have shown you how to build and deploy a Windows service application using an Azure pipeline. The build stage restores NuGet packages, builds the .NET Core solution using the publish command, runs unit tests and if all went well, publishes the artifacts. Then, the deploy stage downloads the build artifacts and installs the Windows service using PowerShell.
Now it might be a good time to configure manual approval before deploying.