Using NAnt on creating Windows Services

Automation

Software development frequently involves doing a set of repeated actions in day-to-day life. If you take a web developer some of the activities he does frequently are building the web application and the referenced projects, running unit tests, generating documents, packaging the source files, copying the package to FTP server etc. Typically in big projects these activities occur numerous times. Many times developers end up doing these activities manually due to mostly lack of knowledge on automation and build tools. Build tools plays a crucial role in Continuous Integration where every time a developer checks-in his changes, a set of actions will take place automatically through build scripts to make sure the latest code won't create any build issues.

I recently started exploring NAnt when I was developing a couple of windows services for a client who asked me to write build scripts for those services. I was not much aware of automation and NAnt before so I thought these scripts are just going to help at the time of deployment to build the services without Visual Studio and to install them easily without much trouble. Soon I started to realise how much these scripts helps to save time and effort at the time of development and after. In this article I planned to explain how to take the advantages of build scripts while developing windows services.

NAnt

What is NAnt?

NAnt is free and open-source build tool for .NET which was first released on July 2011. The latest stable version is 0.91 released on October 2011. Many open-sources and products are available on top of NAnt like NAntContrib, NAntBuilder, NAntGUI, NAntMenu etc.

Basics

NAnt build scripts are nothing but xml files having extension .build which contain a series of activities that has to be executed. The series of activities is called as targets and each target contains a set of tasks. Below is a simple NAnt script that builds the project HelloWorld. Though building a project sounds easy but a series of actions takes place when we do that. In Visual Studio when we build a project many things happen like creating a set of folders (bin, obj etc.), cleaning the old assemblies and other files from the folders, building all the class files using C# or VB compiler, copying the config file to the debug or release folder etc.

helloworld.build

<?xml version="1.0"?>
<project name="HelloWorld" default="build">

   	<property name="build.debug" value="false" />
   	<property name="build.dir" value="build" />

   	<target name="clean" description="removes all build products">
        <delete dir="${build.dir}"  if="${directory::exists(build.dir)}" />
   	</target>
   	
   	<target name="init" depends="clean" description="creates the build directory">
        <mkdir dir="${build.dir}" />
   	</target>

   	<target name="build" depends="init" description="compiles the sources files">
        <csc target="exe" output="${build.dir}/HelloWorld.exe" debug="${build.debug}">
            <sources>
                <include name="HelloWorld/Program.cs" />
            </sources>
        </csc>
   	</target>
   	
</project>

Listing 1. Simple HelloWorld build script

The root node is the <project> which contains name and a default target; there should be only one <project> node. If the user doesn't pass any target name explicitly then the default target specified in the <project> node will be called. Below the <project> node there are <property> and <target> nodes. The <property> nodes are nothing but they are global variables that can be accessed anywhere throughout the script, they are usually placed at the top. In above script we have two properties each having a name and value. The property build.debug is used to configure the compiler to build the project either in debug or release mode. The property build.dir contains the name of the output directory where the compiled executable will be placed.

The <target> nodes do the actual job and they contain more than one task. Every <target> node should have a name and an optional description. The more interesting attribute is the depends and it represents the target that has to be called before running this target. In our above example there are three targets, the first one deletes the previous build directory, this is done by using the NAnt's <delete> task. The second target creates the build directory using the built-in task <mkdir>. The third and the main target is the one that compiles the HelloWorld project by calling the csharp compiler using <csc> task. The <csc> task takes the output type (exe or dll), output folder and other things as attributes. Inside the <csc> task we can specify the files that have to be included for compilation.

Executing the above script is very easy. If you have NantMenu then you can run the script directly from the windows explorer or else you have to open the command prompt and run the nant.exe passing the build file name.

"C:\Program Files\nant-0.91\bin\nant.exe" -buildfile:helloworld.build

Since we are not passing any target the default target set in the project node will be called by the NAnt build engine. The default target is build, which depends upon init and that depends upon clean, so all the targets are executed one by one as in the order in script.

Building windows services

When creating windows services a developer do the following tasks frequently.

Fig a. Repeated tasks on developing a windows service

Repeated tasks on developing a windows service

Usually for testing we install and run the service when we make changes to it, for that first we have to stop and uninstall the old service, compile the latest projects, install the new service and run it. Doing these steps frequently will irritate any one. Let see how we can take the advantages of build scripts to automate those tasks and save time. NAnt don't have all the tasks and functions that we need here but there is another project called NAntContrib that contains more advanced tasks and functions. In our case we need a function to know whether the service is already installed or not and also whether the status of the service is stopped or running. Unfortunately NAnt don't have functions to support this and there comes NAntContrib. Let's start writing the script.

As in the previous example we need targets to clean previous build outputs, to create build directory and to compile the source files. We have added one more property service.name that contains the name of the service.

Build script for windows service

<property name="build.debug" value="false" />
<property name="build.dir" value="build" />
<property name="service.name" value="SimpleService" />
<target name="clean" description="remove all build products">
	<echo message="Deleting the build folder..." />
    <delete dir="${build.dir}" if="${directory::exists(build.dir)}" />
</target>
<target name="init" depends="clean" description="creates the build directory">
	<echo message="Creating the build folder..." />
    <mkdir dir="${build.dir}" />
</target>
	
<target name="compile" depends="init" description="compiles the application">
	<echo message="Compiling the source files..." />
	<csc target="exe" output="${build.dir}\${project::get-name()}.exe" debug="${build.debug}">
		<sources>
			<include name="SimpleService\**\*.cs" />	
			<exclude name="SimpleService\**\AssemblyInfo.cs" />
		</sources>
	</csc>
</target>

Listing 2. SimpleService build script with targets to delete the previous build outputs, create build folder and compile.

Along with the above three targets we need other main targets to install, uninstall, stop and start service.

Target to install service

	
<target name="install" description="installs the service" depends="uninstall, compile">
	<echo message="Installing the service..." />	
	<exec program="${framework::get-framework-directory('net-4.0')}\InstallUtil.exe">
		<arg value="${build.dir}/${project::get-name()}.exe" />      
	</exec>
</target>

Listing 3. Target to install service

If you see the dependencies there are two targets uninstall and compile. Before installing the service we have to uninstall the previous installation (if there is one) and then compile the project. To install the service we have used the <exec> task, this task is used to call any executable. Instead of hard-coding the location of the InstallUtil.exe we can get it through the framework function get-framework-directory(). The <arg> node is used to pass arguments to the executable and in our case the service that has to be installed is passed as argument.

Target to uninstall service

	
<target name="uninstall" description="uninstalls the service" depends="stop">
	<echo message="Uninstalling the service..." />	
	<exec program="${framework::get-framework-directory('net-4.0')}\InstallUtil.exe" if="${service::is-installed(service.name,'.')}">
		<arg value="/u" />
		<arg value="${build.dir}/${project::get-name()}.exe" />      
	</exec>
</target>

Listing 4. Target to uninstall service

Before uninstall the service we have to stop it, so the target depends on another target named stop. If you are trying to uninstall a service that not exists InstallUtil.exe throws error and to avoid that we are checking if the service exists using the service function is-installed() provided by NAntContrib.

Targets to stop and start services.

	
<target name="stop" description="stops the service">
	<echo message="Stopping the service..." />	
	<servicecontroller action="Stop" service="${service.name}" if="${service::is-installed(service.name,'.') and service::is-running(service.name,'.')}" />
</target>
<target name="start" description="starts the service">
	<echo message="Starting the service..." />	
	<servicecontroller action="Start" service="${service.name}" if="${service::is-installed(service.name,'.') and service::is-stopped(service.name,'.')}" />
</target>

Listing 5. Target to stop & start services

we have used the NAnt's <serviceController> task to start or stop the service.

Complete script (simpleservice.build)

<?xml version="1.0"?>
<project name="SimpleService" default="all">
	
	<target name="all" />
	
	<property name="build.debug" value="false" />
  	<property name="build.dir" value="build" />
	<property name="service.name" value="SimpleService" />
	
	<target name="clean" description="remove all build products">
		<echo message="Deleting the build folder..." />
		<delete dir="${build.dir}" if="${directory::exists(build.dir)}" />
  	</target>
  	
  	<target name="init" depends="clean" description="creates the build directory">
		<echo message="Creating the build folder..." />
		<mkdir dir="${build.dir}" />
  	</target>
	
	<target name="compile" depends="init" description="compiles the application">
		<echo message="Compiling the source files..." />
		<csc target="exe" output="${build.dir}\${project::get-name()}.exe" debug="${build.debug}">
			<sources>
				<include name="SimpleService\**\*.cs" />	
				<exclude name="SimpleService\**\AssemblyInfo.cs" />
			</sources>
		</csc>
	</target>
	
	<target name="stop" description="stops the service">
		<echo message="Stopping the service..." />	
		<servicecontroller action="Stop" service="${service.name}" if="${service::is-installed(service.name,'.') and service::is-running(service.name,'.')}" />
	</target>
	
	<target name="uninstall" description="uninstalls the service" depends="stop">
		<echo message="Uninstalling the service..." />	
		<exec program="${framework::get-framework-directory('net-4.0')}\InstallUtil.exe" if="${service::is-installed(service.name,'.')}">
			<arg value="/u" />
			<arg value="${build.dir}/${project::get-name()}.exe" />      
		</exec>
	</target>
 
	<target name="install" description="installs the service" depends="uninstall, compile">
		<echo message="Installing the service..." />	
		<exec program="${framework::get-framework-directory('net-4.0')}\InstallUtil.exe">
			<arg value="${build.dir}/${project::get-name()}.exe" />      
		</exec>
	</target>
	
	<target name="start" description="starts the service">
		<echo message="Starting the service..." />	
		<servicecontroller action="Start" service="${service.name}" if="${service::is-installed(service.name,'.') and service::is-stopped(service.name,'.')}" />
	</target>
 
</project>

Listing 6. Complete script

To run the script we have to just pass the corresponding target name to nant.exe.

"C:\Program Files\nant-0.91\bin\nant.exe" -buildfile:simpleservice.build install
"C:\Program Files\nant-0.91\bin\nant.exe" -buildfile:simpleservice.build uninstall
"C:\Program Files\nant-0.91\bin\nant.exe" -buildfile:simpleservice.build start
"C:\Program Files\nant-0.91\bin\nant.exe" -buildfile:simpleservice.build stop

Conclusion

Automating builds is quite there for a long time and extensively used by many companies but many developers don't take the advantages of that. NAnt is a nice build tool that is very easy to learn and capable of doing some powerful things which would be otherwise time consuming and daunting. In this article we have seen how much NAnt scripts helps in saving time when developing windows services.

Download HelloWorld

Download SimpleService

blog comments powered by Disqus