In the previous post in this series, we talked about how to create a Windows Service that would use an OWIN compatible host, to host an OWIN HTTP application and package that up into an easy to manage executable. This post describes an approach to deploying that executable using simple command line tooling.
Push vs Pull Deployments
I often hear people talk about xcopy deployment as if it were the epitomy of deployment simplicity. My experience is that very often I want to deploy to a machine that is in a completely different security realm and the idea of being able to simply copy files from the source machine to target machine is not practical. I also find that I much prefer to pull stuff onto a machine rather than push onto it. If you are running a big web farm, then maybe pushing does make more sense, but if you are running a big web farm, I highly doubt you are considering self-hosting with Windows Services!
Chocolatey
One of the most promising solutions for doing pull deployments is Chocolatey. Chocolatey downloads specially configured Nuget packages from a standard Nuget feed, to install applications onto machines. In my opinion the future of Chocolatey was recently validated when Microsoft announced that Powershell V5 will contain tooling called OneGet that will provide “in-the-box” support for Chocolatey packages.
I thought Nugets were just for libraries
The majority of Nuget usage at the moment is for consuming reusable .Net libraries. However, the internal structure of a Nuget package is quite flexible and is more than capable of delivering a payload of binaries and Powershell scripts for installing those binaries.
The oversimplified explanation of a Chocolatey Nuget is that it is a regular nuget with a powershell script called ChocolateyInstall.ps1. After you have installed Chocolatey, there are a number of command line tools that are available for managing packages. For example,
cinst MyApplication –Source https://myfeeds.company.com/feed
This command instructs Chocolatey to download the MyApplication nuget package from the nuget feed specified in the “source” parameter and extract the contents of the nuget into a sub-folder of [SystemDrive]:\Chocolatey\lib. Once that is done, Chocolatey will attempt to run the ChocolateyInstall.ps1 file if one was in the nuget. That’s the essence of the process. For our purposes we are going to use the install script to run our application with the Topshelf “install” command to install the Windows Service. We will also include a ChocolatelyUninstall.ps1 to handle the removal of the Windows Service when the package is uninstalled.
Putting your HTTP Application in a Nuget
I tried using the shortcut approach of building a nuget package by generating the .nuspec file directly from the application project, however, when you do this, the Nuget only contains the binary of the project and assemblies that were referenced directly. Assemblies that are referenced via Nuget references are left simply as references and not embedded in the nupkg. This has the benefit of making the Nuget really lightweight, but it would require some fancy powershell footwork after deploying our service to copy all the binaries from the dependent nuget packages into a single folder, ensuring that we get the correct framework version of all the binaries.
The simpler solution is just to package up the binaries that are in the debug/release folder. A sample nuspec would look something like this:
<?xml version="1.0"?> <package > <metadata> <id>SampleService</id> <version>1.0.2</version> <title>Sample Service</title> <authors>Darrel Miller</authors> <owners>Darrel Miller</owners> <requireLicenseAcceptance>false</requireLicenseAcceptance> <description>Sample Owin Http host</description> <copyright>Copyright 2014</copyright> </metadata> <files> <file src="..\..\SampleService\bin\release\*" target="tools"/> <file src="ChocolateyInstall.ps1" target="tools"/> <file src="ChocolateyUninstall.ps1" target="tools"/> </files> </package>
Instead of using nuget.exe to pack this file into a nuget, you use the Chocolatey cpack command to create the nupkg file.
The install script looks like this,
$packageName = 'SampleService' $serviceFileName = "SampleService.exe"try {
\(installDir = <span class="str">"\)(Split-Path -parent $MyInvocation.MyCommand.Definition)" $fileToInstall = Join-Path $installDir $serviceFileName . $fileToInstall install . $fileToInstall start
Write-ChocolateySuccess "$packageName" } catch { Write-ChocolateyFailure "\(packageName"</span> <span class="str">"\)($_.Exception.Message)" throw }
And the uninstall script is almost identical:
$packageName = 'SampleService' $serviceFileName = "SampleService.exe"try {
\(installDir = <span class="str">"\)(Split-Path -parent $MyInvocation.MyCommand.Definition)" $fileToInstall = Join-Path $installDir $serviceFileName . $fileToInstall uninstall
Write-ChocolateySuccess "$packageName" } catch { Write-ChocolateyFailure "\(packageName"</span> <span class="str">"\)($_.Exception.Message)" throw }
I must confess ignorance to the incantations required to get the install folder. I simply found other scripts that did something similar and “re-purposed”. The source for this sample can be found here.
Now where should I put my nupkg
Once you have built your nuget package you can host it either on Chocolatey.org, on Nuget itself, or do what I did and create a feed on myget.org and upload your package there. If you create an account on MyGet.org, all the instructions on how to upload a package are provided on your Feed page.
Installing the service is as simple as using the Chocolatey "cinst" command:
cinst SampleService -source https://myget.org/f/darrel
If you are brave enough to trust me, you can try installing it on your own machine. If you currently don't have Chocolatey installed, there is single command line that you can copy and pasted from the Chocolatey home page that will set it up for you. Once you have Chocolatey and the SampleService installed you can prove it is working by doing the following:
start http://localhost:1002/
If everything works as intended you should see the following,
and once it is installed, and you launch the service you will see this magnificent output.
Removing the service is as easy as,
cuninst SampleService
What doesn't work?
What we have seen so far gives us a solution that makes it very simple to deploy to a new machine. My initial attempts to do an in-place update has not worked as I had hoped. Which leaves us to do a uninstall old version / install new version process. That's fine as long as there isn't configuration data that you want to bring over from the old version to the new version. I'm sure there is a solution to this, I just haven't dug deep enough yet.
Using a self-hosted windows service is not likely to fit the bill for every web api you deploy, however, it can sometimes be the ideal solution. Hopefully you will find having this set of tricks up your sleeve will come in handy one day.
Image Credit: Kinder Surprise https://flic.kr/p/fw9eVB
Image Credit: Tug of War https://flic.kr/p/nD2nj