Deploying Windows Service via MSBuild

There are multitude of ways a windows service can be deployed – using installutil utility, setup project, using WiX, custom installer, sc command and even manually using xcopy and sc command.

In this post, I’ll show how to automate these manual steps via MSBuild. One of the benefits of using MSBuild is that you can easily plug in this MSBuild project file as part of your regular Visual Studio builds or any Continous Integration server that supports MSBuild projects such as Team Foundation Server, CruiseControl.net etc.

Finally, I’ll also show you how to integrate this with Team Foundation Server’s build.

tl;dr If you’re fairly accustomed with MSBuild project files, you can skip the explanation below and jump straight to the script files here. If not, read on!

Getting started

Service metadata

To customize how the windows service appears in services management console, we need to specify some meta data.

<PropertyGroup Label="ServiceMetaData">
  <ServiceName>ShinyNewService</ServiceName>
  <ServiceDisplayName>Shiny New Service</ServiceDisplayName>
  <ServiceDescription>A shiny new service, that changes the world.</ServiceDescription>
</PropertyGroup>

These properties are fairly self descriptive. Essentially they map to these fields on the actual management console.

Services Managment Console

Note: You can set many other aspects of the service, such as the user account, password, dependencies, startup type and more via sc config command.

Deployment target

To help during development, I wanted the same script to be able to deploy to my local PC as well as a remote server.

Based on the target machine, this section sets up some more build task properties.

<Choose>
  <When Condition="'$(DeploymentServerName)' == ''">
    <PropertyGroup>
      <!-- You can choose any path here. -->
      <DeploymentFolder>C:\$(ServiceName)</DeploymentFolder>
    </PropertyGroup>
  </When>
  <Otherwise>
    <PropertyGroup>
      <!-- should be in \\serverName format-->
      <DeploymentServer Condition="'$(DeploymentServerName)' != ''">
        $(DeploymentServerName)
      </DeploymentServer>

      <DeploymentFolder>$(DeploymentServer)\C$\$(ServiceName)</DeploymentFolder>

      <!-- 4:5:4 => Planned: Application: Upgrade.
                   For more reason codes, run "sc stop" -->
      <DeploymentReason>4:5:4</DeploymentReason>
    </PropertyGroup>
  </Otherwise>
</Choose>

This is a simple if-else path in MSBuild’s terms. The variable DeploymentServerName is used to figure out if this is a local deployment or a remote deployment.

The path to DeploymentFolder is set based on whether this is a local or remote deployment. You can set it to any valid path of your liking.

DeploymentReason is completely optional. You can set it if you want to be extra nice or if your internal poilcy demands, but otherwise you can ignore it.

ReBuild the project

To deploy the service, we need to gather its binaries. Unlike web applications, where you can simply take contents of _PublishedWebsite folder, there is no way to get to the list of files for non-web applications.

To overcome that, we simply ReBuild the project and collect all outputs.

<Import Project="$(ProjectFile)"
        Condition="'$(ImportProjectFile)'=='true'" />

<Target Name="Rebuild"
        Condition="'$(ImportProjectFile)'=='true'"
        DependsOnTargets="$(BuildDependsOn)"
        Outputs="@(AllOutputs->'%(FullPath)')" >

  <CreateItem Include="$(OutputPath)\**\*">
    <Output ItemName="AllOutputs" TaskParameter="Include"/>
  </CreateItem>

  <Message Text="Custom build invoked!" Importance="high"/>

</Target>

Copying Outputs

Now that we have all the neccessary files needed to deploy the service, the next step is to copy those files over to the DeploymentFolder.

<Target Name="CopyOutputs">
  <MSBuild Projects="$(MSBuildProjectFullPath)"
           Properties="ImportProjectFile=true" Targets="Rebuild">
    <Output ItemName="ProjectOutputs" TaskParameter="TargetOutputs"/>
  </MSBuild>

  <Message Text="Stopping Service..." />

  <!-- 4:5:4 => Planned: Application: Upgrade -->
  <Exec Command="safeServiceStop $(ServiceName) $(DeploymentServer)
                 $(DeploymentReason)" ContinueOnError="true" />

  <Message Text="Copying files..." />
  <Copy SourceFiles="@(ProjectOutputs)"
        DestinationFolder="$(DeploymentFolder)"
        SkipUnchangedFiles="true"
        OverwriteReadOnlyFiles="true" />
</Target>

There are couple of caveats here. First, if the service was already deployed before, there is a good chance that the service maybe running. In that case, copy may fail as some files maybe locked. So we need to stop the service first.

We can stop the service via sc stop command, however sc stop command is asynchronous, so it will return immediately. In other words, it will not wait for the service to completely stop before returning.

Second, you may also encounter cases where the service is not installed or the server is offline.

The batch script safeServiceStop takes care of all these cases and ensures that service has completely stopped before returning.

We then proceed to copy the outputs to the DeploymentFolder.

Deploying the Service

<Target Name="DeployService">

  <Exec Command="safeServiceStop $(ServiceName) $(DeploymentServer) $(DeploymentReason)" />

  <Exec Command="safeServiceDelete $(ServiceName) $(DeploymentServer)"
        ContinueOnError="true" />

  <Exec Command="sc $(DeploymentServer) create $(ServiceName)
                    binPath= "$(ServiceExecutablePath)"
                    start= delayed-auto
                    displayName= "$(ServiceDisplayName)"" />

  <Exec Command="sc $(DeploymentServer) description $(ServiceName)
                     "$(ServiceDescription)"" />

  <Exec Command="safeServiceStart $(ServiceName) $(DeploymentServer) "
        ContinueOnError="true" />
</Target>

The final step is to install or re-install the service as a windows service. This target first stops the service, then deletes (equivalent to uninstall) the previous version, installs the newly deployed one, sets the metadata and then starts the service. The batch scripts safeServiceStop, safeServiceDelete, safeServiceStart all take care of the asynchronous nature of sc command.

Usage

To use the script:

  • Save deploy.proj, safeServiceStart.bat, safeServiceStop.bat, safeServiceDelete.bat files in the same folder
  • Open command prompt in admin mode
  • To deploy to local PC, run msbuild deploy.proj
  • To deploly to remote server, run msbuild deploy.proj \\.

Note: You must be an admin on the remote server to be able to deploy to it.

The final code can be found here: Deploy-Windows-Service-Via-MSBuild

Integrating with Team Foundation Server build

Integrating the build script with Team Foundation Server is pretty easy. Using the default.xaml template,

  • Add deploy.proj to Items to Build ► Projects to Build setting.
  • Under Advanced ► MSBuild Arguments, specify the deployment server name and other parameters if needed.
  • Finally, save the build and take it for spin!

Team Build definition

References

Advertisements

Running gulp tasks with JetBrains WebStorm

JetBrains WebStorm 8 provides grunt integration but as of this writing, gulp integration is part of WebStorm 9’s roadmap.

However, that doesn’t mean that gulp users cannot Run/Debug gulp tasks. Here’s how to enable gulp configuration within WebStorm 8 (should work with 7 as well).

Image

  • Create a new NodeJs configuration
  • Set your working directory to where your gulpfile.js resides
  • If you have installed gulp globally, point to %AppData%\npm\node_modules\gulp\bin\gulp.js; otherwise point to your local node_modules’ gulp folder.
  • Save it!

Now you can run/debug your gulp tasks to your heart’s content!!

Simplify Visual Studio Context Menus

What is it?

Visual Studio is a monster of a development tool. However, with great power, comes great number of options! Right out of the box, this is how the context menu for a Web Application looks like:

Web Application - Out of the box
Web Application – Out of the box

Add extensions, third party addins and customizations and pretty soon you end up having a long scrolling list of items. Now every time you right click on a project/solution node, not only it takes longer to draw the menu, but also you spend time in ‘finding’ the right item. You almost ‘pause’ for few seconds (or milliseconds) looking for the right menu entry. Mike Fourie talks more about this ‘stutter’ or ‘pause’ effect and also proposes a few solutions here.

Mike suggested redesigning the GUI to have horizontally laid out options (like Chrome) or customizing the existing menus to introduce nesting or sub-levels. Though having a Chrome-like arrangement looks really neat and avoids the inconvenience of nested menus, it probably is not possible till some future version of Visual Studio (or maybe never).

The other option is to customize individual menu items manually which is a huge trade-off. You either spend time customizing it (one-time) or spend time ‘finding’ (every time). I would take the one time inconvenience over the countless minutes of frustration that I’ve been dealing with so far. Thus borne this open source effort to address this one-time inconvenience.

What it does

The settings file removes some of the menu clutter by moving menu items to a nested submenu. Here are the rules I came up with in determining what goes in a submenu and what stays on the main menu. In no particular order of preference:

  • Third party addons (such as Resharper) or extension goes to a sub menu
  • Actions that are performed via keyboard shortcuts more frequently (such as Build, Cut/Copy/Paste etc.)
  • Any group that has more than 3 items and is not frequently used

These rules are based on me and my colleagues observations. Your IDE settings may look different so take it with a grain of salt. Of course, if you don’t like something, you can always reset those items back. Even better, you can submit your version with a nice description for rest of the world to use!

How to use it

Up-to-date instructions and caveats can be found in README. I’ll state them here for quick reference only (may not be up to date).

  1. Backup your existing settings first!!
  2. Download Reduce Menu Clutter.vssettings file from github repository
  3. Go to Visual Studio -> Tools -> Import and Export Settings… -> Import Selected environment settings
  4. Follow the wizard to complete the import.

There is no need to restart IDE. Changes will be effective immediately.
Note: These customizations were done for Visual C# environment. While this should not affect anything (as they settings are language agnostic), if you run into any issues, do let me know.

What do I get?

Here’s the most interesting part. I have posted screenshots of how the simplified menus look like. You can scroll through them before deciding to try them out.

Class Library - Before, AfterClass Library – Before, After Web Application - Before, AfterWeb Application – Before, After

I hope this benefits my fellow .Net Developers. If you have any comments/suggestion, do send me a note or create an issue on github repository.