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:
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:
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.
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Configuration Main | |
{ | |
Import-DscResource –ModuleName 'PSDesiredStateConfiguration' | |
Node ('localhost') | |
{ | |
Script DeployWindowsService | |
{ | |
GetScript = { | |
@{ | |
Result = "" | |
} | |
} | |
TestScript = { | |
$false | |
} | |
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 | |
} | |
else | |
{ | |
# has already been installed | |
if($serviceDef.Status -eq "Running") | |
{ | |
Stop-Service –Name $serviceName | |
} | |
Copy-Item $sourceLocation $destinationLocation –Force | |
} | |
Start-Service –Name $serviceName | |
} | |
} | |
} | |
} | |
Main |
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!
Reblogged this on Kloud Blog.
Hi, noob question, but where do I put the deployment script? How to I make the PS script execute during deployment?
Thanks
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.
Simon.
Thanks a lot. This was very useful. Didn’t image it would be so simple.
You’re welcome – glad it helped!
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?
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,
Simon.
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?
Jay, the mof file is the output expected in this instance. If you want to understand how to execute the resulting file I’d recommend having a bit of a read around how PowerShell Desired State Configuration (DSC) works so you understand what the MOF file is and how it is used at execution time. This is a good starting place: http://www.informit.com/articles/article.aspx?p=2350701&seqNum=5
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.
When I run this script I have permission issues, even using a user with admin permission
Hi Renan,
Could you provide a bit more context? What Windows Server version are you deploying to, and can you confirm you ran the setup steps necessary on the target VM? See “Local permissions” section of this post: https://blog.siliconvalve.com/2016/09/07/deploying-to-azure-vms-using-vsts-release-management/.
Hope this helps (if not please let me know!)
Simon.