Continuous Deployment of Windows Services using VSTS

I have to admit writing this post feels a bit “old skool”. Prior to the last week I can’t remember the last time I had to break out a Windows Service to solve anything. Regardless, for one cloud-based IaaS project I’m working on I needed a simple worker-type solution that was private and could post data to a private REST API hosted on the other end of an Azure VNet Peer.

While I could have solved this problem any number of ways I plumped for Windows Service primarily because it will be familiar to developers and administrators at the organisation I’m working with, but I figured if I’m going to have to deploy onto VMs I’m sure not deploying in an old-fashioned way! Luckily we’re already running in Azure and hosting on VSTS so I have access to all the tools I need!

Getting Setup

The setup for this process is very similar to the standard “Deploy to Azure VM” scenario that is very well covered in the official documentation and which I added some context to in a blog post earlier in the year.

Once you have the basics in place (it only takes a matter of minutes to prepare each machine) you can head back here to cover off the changes you need to make.

Note: this process is going to assume you have a Windows Service Project in Visual Studio 2015 that is being built using VSTS’s in-built build infrastructure. If you have other configurations you may need to take different steps to get this to work ūüôā

Tweak build artefact output

First we need to make sure that the outputs from our build are stored as artefacts in VSTS. I didn’t use any form of installer packaging here so I needed to ensure my build outputs were all copied to the “drops” folder.

Here is my build definition which is pretty vanilla:

Build Process

The tweak I made was on the Visual Studio build step (step 2) where I defined an additional MSBuild Argument that set the OutputPath to be the VSTS build agent’s artifacts directory which will automatically be copied by the Publish Artifacts step:

Build Update

If I look at a history entry for my CI build and select Artifacts I can see that my Windows Service binary and all its associated assemblies, config files (and importantly Deployment Script) are stored with the build.

Build Artefacts

Now we have the build in the right configuration let’s move on to the deployment.

Deploying a Service

This is actually easier than it used to be :). Many of us would remember the need to package the Windows Service into an MSI and then use InstallUtil.exe to do the install on deployment.

Fear not! You no longer need this approach for Windows Services!

PowerShell FTW!

Yes, that Swiss Army knife comes to the rescue again with the Get-Service, New-Service, Stop-Service and Start-Service Cmdlets.

We can combine these handy Cmdlets in our Deployment script to manage the installation of our Windows Service as shown below.

Configuration Main
Import-DscResource ‚ÄďModuleName 'PSDesiredStateConfiguration'
Node ('localhost')
Script DeployWindowsService
GetScript = {
Result = ""
TestScript = {
SetScript = {
$serviceName = "ResponseProcessor"
$displayName = "Simon Demonstration Response Processor"
# location where our AzureVMs File Copy RM Task copies our build outputs
$sourceLocation = "C:\temp\*"
$destinationLocation = "C:\Program Files\Simon\PaymentProcessorService\"
$binaryName = $destinationLocation + "Simon.Demo.ClearanceProcessor.exe"
$serviceDef = Get-Service Name $serviceName ErrorAction SilentlyContinue
If ($serviceDef -eq $null)
# first install – create directory
New-Item $destinationLocation ItemType directory
Copy-Item $sourceLocation $destinationLocation Force
New-Service Name $serviceName StartupType Automatic DisplayName $displayName BinaryPathName $binaryName
# has already been installed
if($serviceDef.Status -eq "Running")
Stop-Service Name $serviceName
Copy-Item $sourceLocation $destinationLocation Force
Start-Service Name $serviceName

The Release Management definition remains unchanged – all we had to do was ensure our build outputs were available to copy from the ‘Drop’ folder on the build and that they are copied to C:\temp\ on the target VM(s). Our Desployment Script takes care of the rest!

That’s it! Next time your CI build passes your CD kicks in and your Windows Service will be updated on your target VMs!

12 thoughts on “Continuous Deployment of Windows Services using VSTS

  1. Hi, noob question, but where do I put the deployment script? How to I make the PS script execute during deployment?

    1. Hi Jeff

      Not a noob question at all. The previous blog post I had put together detailed how to bundle the PowerShell into the deployment package, but it appears the documentation I referenced from Microsoft has been moved or retired. I’ve updated the post with additional details. The way you execute the PowerShell on the VM is to use the VSTS Release Management feature and specifically the “Run PowerShell on Target VMs” Task. I hope this helps out, if not please feel free to reply and I will see what I can do to set you on the right path.

      Thanks for the question.

      1. There is one thing. The PowerShell script above creates a .mof file. So I believe we need another PowerShell task that invokes Start-DscConfiguration on that .mof file, right?

      2. Samrat,

        There is no need to create a separate wrapper script.

        PowerShell Desired State Configuration (DSC), which is on the hosts already, will know what to do with the MOF that is generated.

        Hope this helps,

  2. I am trying to run this script on my laptop to understand code better but the script does not installs the windows service on my laptop at all. It only generates .mof file and than when I go services windows my windows service is not shown on that window. Any suggestions?

  3. Hi, I’m having a problem executing the script for creating windows servive:

    The WS-Management service can not process the request. The WMI service returned an ‘access denied’ error.
    ¬†¬†¬†¬† + CategoryInfo: PermissionDenied: (root / Microsoft / … gurationManager: String) [], CimException
         + FullyQualifiedErrorId: HRESULT 0x80338104
         + PSComputerName: localhost

    Would you help me?

    Ps: I do not speak English, sorry.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s