Search
Thursday, December 13, 2018 ..:: Home ::.. Register  Login
   Calendar  
     
  
   Search  
     
  
   Blogroll  
     
  
   Disclosure  
All blog entries are the opinions of the author and do not necessarily reflect the opinions of their employer. All the code presented is for explanation and demonstration purposes only. Any damages incurred to your site and/or data are not the responsibility of the author. Every effort is taken to ensure the code properly compiles, however sometimes there are some hiccups and you might be required to do your own debugging.
     
  
   TechTidBits (Blog)  

Ever wanted to create a Windows Service but didn't know where to start? Well, here's a great place!

Jun 2

Written by:
Tuesday, June 02, 2009 8:39 PM  RssIcon

Do you have a process you KNOW SHOULD be in a Windows Service but think they're just too complicated?  You think they're for Windows Servers only or you've had/heard of administrative problems in the past?  Well, read on for a detailed set of directions to take you from where you are now to a completed and working Windows Service!

Do you have a computer in your office that looks something like this?  What a 1980s way to solve a problem!  Man, you'd think it was a 286 or 386SX and the company was too ch........a little harsh?  Ya, I think so too. 

Don't turn off this computer (Warning, this is a long post, yes SB, SG, SI, even for me, this is long! LOL  BUT at the end you WILL have a working, installable and uninstallable Windows Service!!!!!!!)

Do you know of a computer with yellow stickies over the power button that says "DO NOT TURN OFF!"  What about the stickies on the monitor?  And I'll bet someone's NOT listened, turned off the computer and your weekly/montly/annual reports didn't run, eh?  Sound familiar?  If any of this does, then chances are you are a very good candidate for Windows Services!

I strongly encourage you to whip through this quick presentation on Windows Services.  It quickly goes through and tells you the high level pieces you'll need to know about Windows Services.  If you think you know enough already, but find yourself going....hhhhmmm WTF is this geek talking about?......chances are you missed out on some good tidbits of info in the presentation.  So just come back and click on the link to catch up.  Easy enough eh?


Ok, now that you know about ServiceBase, onStart(), threading, headless apps, how to start/stop WinSvcs, and high level debugging WinSvcs, let's move onto building our own Windows Service!

High level overview:

  1. get your business logic working in a console application first, YES this is the first step!
  2. add new project using Windows Service template, let Visual Studio do a lot of the heavy lifting
  3. integrate your bus logic into the WinSvc
  4. add and use some utility bat files to start/stop your WinSvc
  5. debug/test
  6. add setup project
  7. debug/test your setup project (and uninstall it too to properly test)
  8. remove those yellow stickies on the LCD and power button and rest assured your logic is always going to run

Business Logic + Tester (Console App)

Warning Note

You might have already fired up Visual Studio by now, if you have and are running XP, no worries.  If you're using Vista or Windows 7, I'm afraid I have to ask you to quit and reopen VS but as the Administrator this time.  Why?  Cause the way you run/debug Services from VS requires you to be running VS in Administrator mode for Windows to properly enumerate the running programs/windows so you can pick the current running VS to debug with.  This will become important later on when we're debugging (step 5 from the list above).

Run Visual Studio As Administrator.png

Once you have VS open, create a  new solution called 'CheckupWinSvc' and at the same time, create a Utilities class library project.  The latter is going to be our business logic.  Yes, I see the irony in populating these things backwards, hey, I didn't create the UI for the New Project dlg.

Create new solution

Rename the Utilities default created Class1.cs to something like WebUtilities.cs. Implement your business logic here.  For now, we're just going to get the HTTP status code, simple, but let's us focus on creating the Windows  Service task at hand.  All we're doing is taking in a web address and passing back an HttpStatusCode.

 public class WebUtilities
  {
    #region Methods

    public static HttpStatusCode GetWebsiteStatus( string websiteAddress )
    {
      return GetWebsiteStatus( new Uri( websiteAddress ) );
    }

    ///
    /// get status of website given a URL
    /// bad addresses or uncaught exceptions return back 404s
    /// 
    ///
    ///
    public static HttpStatusCode GetWebsiteStatus( Uri websiteAddress )
    {
      WebRequest request = WebRequest.Create( websiteAddress );
      HttpWebResponse response = null;
      HttpStatusCode statusCode = HttpStatusCode.NotFound;

      try
      {
        response = (HttpWebResponse)request.GetResponse();
        statusCode = response.StatusCode;
      }
      catch( Exception )
      {
        statusCode = HttpStatusCode.NotFound;
      }

      return statusCode;
    }
    #endregion
  }

Next, we'll add a new console project to our solution to get our business logic.  Hey, don't shoot the messanger for going backwards, I didn't design the UI.

Add tester project

Warning NoteAt this point, you'll probably notice the Tester program comes before the Utilities.  If you're like me and want people to see the projects in a certain order, here's a little tidbit about VS you may not know about.  VS' project names do not have to match their directories names.  That's right, you can go up to the Utilities project and rename it to something like "1-Utilities" and then rename Tester to "2-Tester".  Although you can't override the order of the projects in VS (they're alphabetical), you can "coax" it into submission.

Next, we're going to call our business logic in the WebUtilities class to ensure we did a good job.  We will create a StringCollection of websites to check, notice how some are good URLs and some are bogus (meant to always fail, hahah LOL!).  Next we have a private method which talks to our WebUtilities class.

 class Program
  {
    static void Main( string[] args )
    {
      StringCollection websitesToCheck = new StringCollection();
      websitesToCheck.Add( "http://ottawacodecamp.ca/Pages/Default.aspx" );
      websitesToCheck.Add( "http://www.LeafsWillWinTheStanleyCup.ca" );
      websitesToCheck.Add( "http://www.ottawacommunity.net/" );
      websitesToCheck.Add( "http://www.PCHenry.com" );
      websitesToCheck.Add( "http://www.OracleWillMakeJavaNum1.com" );

      foreach( string website in websitesToCheck )
      {
        GetStatus( website );
      }

      Console.WriteLine( "FIN" );
#if DEBUG
      //this is a snazzy trick to keep the console window open 
      //while you "paruse" what it says
      Console.ReadLine();
#endif
    }

    private static void GetStatus( string validWebsite )
    {
      Console.Write( "Checking website: " + validWebsite );

      HttpStatusCode status = WebUtilities.GetWebsiteStatus( validWebsite );

      if( status == HttpStatusCode.OK )
      {
        Console.WriteLine( " is ok." );
      }
      else
      {
        Console.WriteLine( " is !!!! NOT !!!! available (" + status + ")." );
      }
    }
  }

Once you enter all this, you'll find one problem when you try to compile it! Can you spot it? Nope, it's not with our syntax (well, maybe your's but I'm assuming that's not it, haha), not with our setup, VS is working JUUUST fine.....so what's the problem? Here's a hint, how does csc.exe know about our 1-Utilities class library? That's right, it doesn't. You probably already know how to fix this, buuuuuuuuut just in case your new to VS and "Adding References", check this out. The reason you want to add a "Projects" reference is so if (and you will LOL) change your WebUtilities/Business Logic, you want your code to always reference the latest'n'greatest (and if needed/changed/modified, recompile it). Adding a project references does all this management for you, yup, for free! SCORE!

Add project reference to Utilities

The astute will notice there's OOOOONE more things missing, you have to add the missing namespaces. You'll need to add them for the StringCollection and HttpStatusCode, you know about the CTRL+. right? Oh man, if you don't, ARE YOU IN FOR A PLEASANT SURPRISE?!?!?!?!?!?!? When/if you see the red underline, or very small rectangle just below the last letter of WebUtilities, hit the CTRL+. (period) and VS will ask you to either add the right using statement up above or put the absolute namespace right there for you! FANTASTIC!!!!!!!!!!!!

NOOOOOOOW you can compile and test out your business logic. Make sure your Tester project is the startup project and test it out. If it's working here's what you'll see (or a close faximile I hope LOL). Notice the second and last URLs are not found, iiiiiiiinteresting they are, EH?! LOL And yes, those will never happen! Ya ya, never's a long time, but come on??????? The Leafs and The Cup???? Java being number one?????? I have a better chance of working for Microsoft than THOSE things happening!

Running the Tester program

Anyways, back to work. Why go through ALLLLLL that work? We haven't even touched Windows Services yet! WTF?! Well, the key is, Window Services is about YOUR business logic (the stuff we put into our WebUtilities). If you don't get that right, there's nothing a properly created Windows Service can do to MAKE it right. And more over, debugging Windows Services is hard to do cause they are headless, and as such, will only serve to make your life HUGELY difficult, UNLESS you have a pretty good idea your business logic is nearly rock solid BEFORE you introduce Windows Services. Ok, so now we KNOW our stuff works correctly, let's introduce Windows Services. (ya ya, about time, I know haha).

Creating Your Windows Service

Add a new project called CheckupWinSvc to your solution and make it a Windows Service.

Add Windows Service project to your solution

hhhmm What just happened? You're staring at a blank grey screen saying something "To add components to your class...Toolbox...properties...methods...", blah blah blah, WTF? Don't worry, you're doing great. Pay attention to the solution and your newly added CheckupWinSvc project (maybe rename that to 3-CheckupWinSvc). Rename the Service1.cs file to CheckupWinSvc.cs. Notice the icon is a bit different from what you're used to? That's your first clue we're not in Kansas anymore Toto! HAHA CheckupWinSvc

We need to add an installer next. This is different from an MSI Setup project we'll create later, this installer is key to Windows to run our windows service. To do this we're going to goto the CheckupWinSvc design surface, right mouse click and select the Add Installer. The installer is going to communicate with Windows and install our windows service into the Services Control Manager (Control Panel, Services) so we can control it via that interface/UI.

Servcie Controller in the toolbox Servcie Controller on the designer surface

If you're wondering when we're going to get to the code, I haven't forgotten, we're just setting up the architecture right now, so when we get to the code, all "this" stuff will be taken care of.

We need to add an installer next.  This is different from an MSI Setup project we'll create later, this installer is key to Windows to run windows service. To do this we're going to goto the CheckupWinSvc design surface, right mouse click and select the Add Installer.  The installer is going to communicate with Windows and install our windows service into the Services Control Manager (Control Panel, Services).

Add Installer from Context Menu

You will see another designer surface and a new ProjectInstaller.cs file added to your CheckupWinSvc project, this is good. On the designer you will rename the serviceProcessInstaller1 to ChekcupWinSvsServiceProcessInstaller and the serviceInstaller1 to CheckupWinSvcServiceInstaller. You will end up with this.

Project Installer properties and how it looks in the Solution Explorer

After you've changed the names of the ProcessInstaller and the ServiceInstaller, it's time to change their properties. Please make your's match the diagram above. Don't forget to change the ServiceProcessInstaller's Account to LocalSystem and the ServiceInstaller's ServiceName to Checkup. You might want/need to tweak your settings later BUT these will work for now and get you up and running. If you're curious (like me? haha) and want to know WHERE THE HECK DO ALL THOSE string changes get used at runtime?! Check this out! As you can see the strings are shown in many different places. The top one is Services in Control Panel, the middle right is the Event Viewer and the bottom one is the console (which I hope you already figured out?! HAHA).

Where do all those properties, descriptions and names go?

I would like to note, please ensure/remember the ServiceName on the ServiceInstaller!  Why?  Cause that's the name you're going to use in the next step for debugging/starting/stopping our soon to be tested service.

Integrate Your Business Logic - Put Your Bus Logic Into Play

Ok, now that we're done some basic setup (there's a bit more, but for now, this is ok), let's focus on some code and getting our Service to do something.  First, we'll concentrate on our constructor.

public CheckupWinSvc()
    {
      InitializeComponent();

#if DEBUG		//sets up this WinSvs and VS for debugging, when you see the dlg popup then you can pick your VS or a new one to debug with
      System.Diagnostics.Debugger.Launch();
      //Debugger.Break();
#endif
      //make sure unhandled exceptions are taken care of properly (yes I understand logging COULD be a problem)
      //aka last chance exception handler
      AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler( OnUnhandledExceptions );

      canStart = true;
    }

Notice there is a problem cause we're missing a local private canStart variable? Let's fix that by putting this at the top, they are local variables to the class.

    Thread workerThread = null;
    Boolean canStart = false;		//in case something wrong happens in initialize, do not start the main worker thread

Now of course you're going to have to add a new using statement for the Thread class, but I'm assuming you know how to do that.

Let's explain what's happening here. The #if DEBUG and #endif tell VS to only do THAT when in debug mode, and THAT is to fire up the debugger. This makes sure you can always debug using VS when you want to (debug build) but not when you make a release build for your client/boss. The UnhandledException event that's having a local handler added to make sure ANY uncaught exceptions are going to be handled (and hopefully logged someplace) for further scrutiny. Without, who knows what would happen with them, they certainly won't be logged in our user/IT friendly manner! The local canStart variable is a quick'n'easy way to ensure we can indeed start up our Service. IF we were trying to connect to a DB in our Constructor and SQL Server was down for maintenance and the we couldn't talk to it, we might likely not want to "successfully start our Service, instead logging it, possibly even emailing someone. This local flag will help us in this task.

Next we'll have to add some code for the uncaught exception call.

    private void OnUnhandledExceptions( object sender, UnhandledExceptionEventArgs e )
    {
      //it's possible this is coming from outside library/code (like C++) and therefore could not be an Exception
      Exception unCaughtException = e.ExceptionObject as Exception;
      if( unCaughtException == null )
      {
        unCaughtException = new Exception( "Uncaught exception caught without details." );
      }

      Logger.log.Fatal( "Uncaught Exception", unCaughtException );
    }

Logger is a custom class we'll create in just a second. We COULD use the Windows Event Viewer, however, I have found MUCH more success with using a logging framework like Log4net. Using Log4net affords you MANY different options from dynamic reloading (ala web.config), dynamic reconfiguration for release to debugging and seeing log files, rolling log files, emails, even logging to a db! It's simply an easy to use and beautiful logging tool. Very much worthwhile having in your toolbox. To use it, just download the DLL, include/reference it in your project, make a couple of code changes (AssemblyInfo.cs and App.config) and add the static Logger class.  What I have done is create a Dependencies folder and included the log4net.dll directly in there, you can pick what ever works best for you.  The Logger class simply looks like this.

  class Logger
  {
    public static readonly ILog log = LogManager.GetLogger( typeof( Logger ) );
  }

To get log files, you'll need to add the following line to the CheckupWinSvc project's AssemblyInfo.cs file. This has to be edited by hand and not by the Project Properties dialog.

[assembly: log4net.Config.XmlConfigurator( Watch = true )]

This tells log4net to dynamically reload the configuration file if/when changes are made. Next we need to add some lines to the App.Config file. This file is not there by default, so goto add a new item and select the Application Configuration File.

Add Application Configuration File to project

Add this in the middle of the lone configuration tag.

  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,Log4net" />
  </configSections>

  <log4net>
    <root>
      <level value="ALL" />
      <appender-ref ref="FileAppender" />
    </root>
    <appender name="FileAppender" type="log4net.Appender.FileAppender">
      <file type="log4net.Util.PatternString" value="WinSvs-%date{yyyyMMdd}.log" />
      <appendToFile value="true" />
      <staticLogFileName value="true" />
      <param name="StaticLogFileName" value="false" />
      <layout type="log4net.Layout.PatternLayout">
        <!--
          <conversionPattern value="%date [%thread] %-5level %logger - %message [%location]%newline" />
         -->
        <conversionPattern value="%-5level - %message%newline" />
      </layout>
    </appender>
  </log4net>
  

Once you get to this point, hit F6, or Build Solution, resolve any compilation errors. We're not in a running state but at least at a point where you should have compilable code.

Now we get some juicy meat! We're going to play with the OnStart method! This is one half of the magic of Windows Services. Just make sure your logic completes <=30s otherwise the Service Control Manager is going to timeout and you'll get error messages saying your service didn't start.  That last part is important when you're debugging, when you're debugging, you'll want to skip through all this stuff to your "meat method" (the one doing the main looping through your logic) and not stay in the OnStart method.

    ///
    /// must return back to the OS within 30s otherwise Service Control Manager will time out
    /// peform minimal amount of work here!
    /// 
    ///
    protected override void OnStart( string[] args )
    {
      if( canStart )
      {
        base.OnStart( args );

        try
        {
          Logger.log.Info( Environment.NewLine + Environment.NewLine +
            "----------------------------------------" +
            "OnStart: starting the windows service (" + DateTime.Now.ToShortDateString() + "; " + DateTime.Now.ToShortTimeString() + ")" );

          //start separate thread to do actual work
          if( workerThread == null )
          {
            workerThread = new Thread( new ThreadStart( CheckWebsiteStatus ) );
            workerThread.Start();
          }

          Logger.log.Info( "OnStart: completed, onto regular running mode" );
        }
        catch( Exception ex )
        {
          Logger.log.Error( "Fatal error OnStart", ex );
          this.Stop();
        }
      }
      else
      {
        if( this.CanStop )    //make sure you have the property that says you're able to stop
        {
          this.Stop();
        }
      }
    }

You'll see up above, we're checking our canStart variable if we have any previous problems, then don't start. If that's OK, then we log some starting info, then you'll see some code that's familiar from our console tests right? Right? RIGHT?! HAHA

The reason for the canStart variable name?  There's a property called CanStop (and CanPause as well), so I thought I'd keep the names similar but different enough (camelCase vs PascalCase) to know it's home grown (my own).

What's with the starting of that new thread? That's one of the magic pieces of our Service! We are going to new up a new ThreadStart object, start it up and then this OnStart can continue and quickly end leaving our core business logic running in the soon to be defined CheckWebsiteStatus.  Here is the CheckWebsiteStatus and utility GetStatus methods.

    ///    
    /// This is where the bulk of the work is done for the Windows Servcie
    /// 
    protected void CheckWebsiteStatus()
    {
      try
      {
#if DEBUG		//sets up this WinSvs and VS for debugging, when you see the dlg popup then you can pick your VS or a new one to debug with
        System.Diagnostics.Debugger.Launch();
#endif
        StringCollection websitesToCheck = new StringCollection();
        websitesToCheck.Add( "http://ottawacodecamp.ca/Pages/Default.aspx" );
        websitesToCheck.Add( "http://www.LeafsWillWinTheStanleyCup.ca" );
        websitesToCheck.Add( "http://www.ottawacommunity.net/" );
        websitesToCheck.Add( "http://www.PCHenry.com" );
        websitesToCheck.Add( "http://www.OracleWillMakeJavaNum1.com" );

        for( int i = 0; ; i++ )		//yup, infinite loop is desired
        {
          Logger.log.Info( string.Format( "-----------------" + Environment.NewLine + "Next iteration through the website checks({0}).", i++ ) );

          foreach( string website in websitesToCheck )
          {
            GetStatus( website );
          }

          //PHEW! that was a lot of work, I need to rest, just let me rest my eyes for a second
          Thread.Sleep( 5000 );
        }
      }
      catch( ThreadAbortException ex )
      {
        // Another thread has signalled that this worker thread must terminate.  
        // Typically, this occurs when the main service thread receives a service stop command.
        Logger.log.Info( "Time to shutdown, did the Leafs win the cup yet?" );
      }
      catch( System.Data.SqlClient.SqlException ex )
      {
        Logger.log.Error( "Error executing SQL, please stop using PL/SQL!", ex );
      }
      catch( Exception ex )
      {
        Logger.log.Error( "Ok, something bad happened in main worker thread", ex );
        this.Stop();
      }
    }

    private static void GetStatus( string validWebsite )
    {
      Logger.log.Info( String.Format( "Checking website: " + validWebsite ) );
      HttpStatusCode status = WebUtilities.GetWebsiteStatus( validWebsite );

      if( status == HttpStatusCode.OK )
      {
        Logger.log.Info( " is ok." );
      }
      else
      {
        Logger.log.Info( " is !!!! NOT !!!! available (" + status + ")." );
      }
    }

The only thing Windows Services special to note here is if there is an Exception, we force a this.Stop() to occur.  No sense continuing to run if we have an uncaught exception we didn't expect to happen.  Oh, and to get this to compile properly, don't forget to add a reference to the Utilities (or maybe 1-Utilities) project so you'll get access to the WebUtilities.GetWebsiteStatus() method.

Next we'll code up the OnStop method.

    protected override void OnStop()
    {
      base.OnStop();

      try
      {
        Logger.log.Info( "OnStop: stopping the windows service" );

        if( workerThread != null )
        {
          workerThread.Abort();
          workerThread = null;
        }
      }
      catch( Exception ex )
      {
        Logger.log.Error( "Fatal error OnStop", ex );
      }
    }
  

The only magic here is checking to see if the workerThread is chugging away (!= null). If so, then abort it.

Well, that's it for coding up your Windows Service! Yup, believe it or not, as far as the code is concerned, you're done! Well, besides, the references, adding the right namespaces to the using statements, and compile errors, DOH!  But for the most part, we're done, oh wait, testing, ya that's right eh?  We have to make sure this thing works eh?!  Cool, let's do that now.  Oh wait, before we get to testing, just do one more F6 to build the project, make sure you have binaries in the bin/Debug directory before the next step.

Create Some Batch Files To Start/Stop - F5 Don't Work Here, Have To Use Batch Files

If you're read/seen my presentation, you know you cannot just hit F5 to run/debug Windows Services, they just don't work that way (well, technically you can do this for the constructor but not after that).  To help with this, you need to start/stop your services manually with command/batch files.  If this sounds an awful lot like C++/UNIX compiles with make files, yup, you'll be RIGHT AT HOME!  If you're a VB developer, hey, feel privileged you don't have to do this type of thing day in/day out! :>

Add a new directory to your CheckupWinSvc project for development support batch files, I called mine DevSupp.  Next we're going to put in six batch files, yup 6 and here they are.

Batch Filename Description Batch File Contents
CmdWin.bat Used by the other files and setups the paths and environment variables properly.

Note: This is one of the two places to setup the proper paths, change yours to the appropriate dirs.
call ""C:\YourDirHere\CheckupWinSvc\DevSupp\SetupBin.bat""
cd %bin%
cd ..\..\DevSupp
%comspec% /k ""C:\Program Files\Microsoft Visual Studio 9.0\VC\vcvarsall.bat"" x86
install.bat Installs the Windows Service using the command line InstallUtil and starts it.
installutil %bin%\CheckupWinSvc.exe
net start Checkup
s.bat Starts the Service (short form cause you're going to be doing this so often).
net start Checkup
SetupBin.bat This is where you'll put the path to your BIN directory where your Service is compiled to. Windows Services are tightly coupled to the EXE you're creating/referencing in the InstallUtil.
set bin=C:\YourDirHere\CheckupWinSvc\bin\Debug
stop.bat Stops the Service, again, someting you'll do a lot of. If you find you're getting weird compile errors, double check here and make sure you're not still running the Service, cause if you are, you can't change the EXE file.
net stop Checkup
uninstall.bat Stops the service from running and then called the InstallUtil with the flag to uninstall the EXE. Don't change the name of the EXE after you install it, otherwise you'll never be able to uninstall it without remember the exact EXE name.
net stop Checkup
installutil -u %bin%\CheckupWinSvc.exe

Warning NoteIf you try to run the installutil with just the program name (minus the ".exe") you'll find you're in for some heartaches. InstallUtil requires the complete executable name, yup, including the ".exe" extension! Hence the importance of the batch file and it's contents above.

A natural question is why goto all this trouble?  Since the F5 won't work for us, AND using the Windows Services control panel tool is very cumbersome (for debugging), this gives us an exacting and fast way to start/stop our Service.  To fully utilize this batch file trick, you'll want to open the DevSupp directory using VS Open Folder In Windows Explorer.

Open Folder In Windows Explorer

Warning NoteWhen you get Windows Explorer opened up on the screen, you're going to run the CmdWin.bat file BUT as the Administrator (remember this trick from starting VS?).  Why?  If you're using XP, you're ok, there's no UAC (User Access Control) to get hung up on.  BUT!  If you're using Vista or Windows 7 and you have UAC turned on, this step is going to be CRUCIAL!  You are doing to HAVE to run the CmdWin.bat as an Administrator, otherwise you won't be able to get InstallUtil to work at all, no matter how much you try, type, hack, fight, curse, etc!  I tried, I ran out of Tylenol before figuring out UAC was hosing me!  This batch file, Windows Explorer dance is the easiest, most direct and fastest way to get to the right place to Run as Administrator option.

Run as Administrator

Once you do this, you're going to have a DOS prompt setup at the right place to install, start/stop and eventually uninstall your Service.  If you find your DOS prompt is dropping you into the C:\Windows\system32 dir, it's possible the paths are incorrect in the CmdWin.bat file or SetupBin.bat or something else is going wrong (but chances are it's the file paths).  If that's the case you can always manually navigate to the bin\Debug directory to issue the s.bat and stop.bat commands.  Ultimately you'll want to install the Service and start and stop it.  If you can't get the batch file working, you'll want to google these things for additional help but in my experience it's usually a path problem (at this point at least).

Command window to start testing

Right now, it would be prudent to recompile your solution and make sure we have no probems before we continue.

Debug and Test - Make Sure Things Work

Now that we have some working code, let's try installing and starting it! COOL! To do that, make sure you do the steps above with the batch files. How will I know if you don't? AAAHHHHHH the very next step! If you find you have the following screen (with errors), then you know you're one of the crew that HAS to do the "Run as Administrator" trick.

 You didn't run with Run as Administrator setting

Instead, if/when you do things correctly, you will see the following.

You didn't run with Run as Administrator setting

Around this point, you should see something like the following dialog pop up titled Visual Studio Just-In-Time Debugger!

Debugging and breaking into VS

Warning NoteThis is normal and highly desirable! haha  You WANT this to come up.  It's cause of the Debugger.Break(); statement this comes up.  IF you didn't follow one of my first instructions and didn't fire up VS with Run as Administrator, you won't see your current running copy of VS listed in the Possible Debuggers list.  I suggest you close down VS and restart.  Why?  Since VS is running two copies of your code, I pretty much guarantee you'll get confused on which code is editable and which is being debugged!  Just make life easier on yourself and restart VS in admin mode.

At this point, your Windows Service is installed AND running!  Yup, it's running!  After a few log lines execute, you should/will be able to go look for your log files and see how are things are going.

Look for log files

Now, to stop your Service, you just type stop at the command prompt.  After you've made some changes and recompiled and want to start again, just type s.  Notice it's not start?  That's cause you're probably going to be doing this over and over again!  You might even want to rename the stop.bat to p.bat (but that's up to you! haha).

If you get errors with compiling but didn't really change anything, one of two things happened.  Either you DID change something and completely forgot cause it's indeed a real compile error, OR (and this is highly likely too) you forgot to "stop.bat" your running Service and VS is telling you the build failed cause of some security/permissions or write access cause Windows has a lock on the DLL that VS is trying to write to.  Long story short, use your stop.bat command to stop, compile then s.bat to restart, get used to that cycle for development with Windows Servcies.

At this point, you have (or should have haha) a fully functional Windows Service.  Congrats! :>  But you're not done yet!  You realy should provide your users with some type of system level documentation.  No, no, no, I'm not suggesting some long drawn out Shakespearean text!  haha no no  Just a simple html file for reference on how to start/stop and other useful infomation.  As simple as this sounds, there are some "gotchas" I'm here to share with you to help make this step easier.

First add a new HTML Page to your project and call it Readme.htm.  The important thing about this step is to look at the properties and change the

Copy If Newer Property change

If you take a look at my HTML file, please notice three things, namely the use of styles, image on the top right hand side and a favicon. 

My sample Readme.htm file

What's the big deal?  These are small touches you can add to your html file to make it look juuuust a little bit more profesional.  The secret to these things?  It's just soooooo easy to do, you'll look foolish if you DON'T do them!  All you need to do is create a Resources directory (no special magic name, I just picked that one) and include your files in there, and make sure to change VS' default (Do Not Copy) to Copy If Newer.  Once you do that, then they'll be copied over when your build your proj/solution.

My sample Readme.htm file

The other more subtle trick is to reference them with relative directories names cause you don't know where they'll be installed on any client machine (href="./Resources/Style.css").

Add Setup Project - Ready To Hit The Road

Next, we're going to add a Setup Project to our solution called, uuuhhh Setup, ya, that's it, Setup (well, I rename it to 4-Setup but that's up to you).  Do you REALLY want to tell someone else to do an "installutil.exe CheckupWinSvc.exe and have to deal with incorrect .NET framework directory problems?  There are a whole slew of other problems that could arise from this scenario.  It's not nice.  I would highly suggest you create and provide your users with a simple to use MSI setup program.  The biggest benefit to this isn't on the install (HUH? WTF did you JUST SAY? haha), no, the biggest advantage is the uninstall support you get for free with creating an MSI/Setup project! :>  HUGE bonus!

Add Setup Project

After we do this, first thing we want to do is add our primary deliverable to the setup project.  Right click on the Setup project folder and navigate down to Add and then Project Output...

Add Project Output

Afterwards, we'll select our Windows Service's main deliverable, the Primary Output.

Add Setup Project

After this, you'll notice something interesting under the Detected Dependencies folder, the log4net.dll and the Utilities.dll are brought along and automagically referenced for you!  Beautiful!  Awesome!

To make the nice icon show up in the Control Panel, first you'll have to create an icon file (which you probably have already IF you added one for your html file above haha), you can add a "File" similarly how you added the primary output (File is under Project Output on that context menu).

At this point you're ready to test your setup program.  To quickly test it out, notice there is an Install and Uninstall option right off the first right mouse click off the Setup project's node in the Solution Explorer?  You can quickly test your install and unintall via this interface!  Very cool!

Install and Uninstall right from within VS

But that's not really how your users are going to test it out right?  hahah 

Warning NoteOnce you create your Setup project, you have to know something good/bad about it.  The bad is, it's not part of the default Debug Configuration Manager setup which means you'll have to explicitily/manually create your setup/MSI files by Building/Rebuilding that setup project.  The good, since it's only done on demand, as your debugging you won't be bothered with the added time to create the setup MSI file you're not going to use while debugging.  Once you do a manual build of the setup project (or change to a Release configuration), the setup/MSI file will be copied to the Setup's Debug/Release directory (note the missing bin directory, and it's not in the same place as the compiled Service DLL is neither).

Setup project's deliverables

Warning NoteThe MSI file is the most important file.  If your users are up to date Windows users, and have the latest MS Windows Installer programs, then they can use the MSI file without problem.  However, if they are not, or you don't quite know, then be safe and distribute both the setup.exe (some setup bootstrap information) in addition to the msi file.

Test Some More - Yes some more (but the install and uninstall this time)

After installing your newly created Windows Service, check out your Add/Remove Programs/Programs and Features.  There it is!!!!  Our newly created Windows Service in lights!

Our newly created Windows Service

Ah, ok, so maybe it's no big deal to have it there, BUT, remember the biggest reason for having the install program, to help your users uninstall it!  HAHA having your icon and your company's name so predominantly featured is pretty cool ain't it?! :>  

Aaaaahhhh now that we've talked about uninstalling, why don't you go ahead and test it?!  Go back to your control panel installed programs list and it should be cleanly removed.  Sweet!  Very sweet, and professional!  SCORE!

DONE!

That's it, you're done!  Ok, so it's a bit more verbose/long than creating a simple console application, and certainly more work than a batch file.  BUT!!!!  Your business logic is going to run even after a power outage and your box/server reboots!  Now, let's see a batch file do THAT?!

Now that you're done, it's time for a coffee and get back to coding!

 

Special Notes:

What do you do if you lose track of the Service Name and don't know how to uninstall it?  What do I mean?  Say you have a Windows Service but you have completely lost the EXE to uninstall it with?  Are you hooped?  Not really, but you will need to be careful with this.  Follow (ATTENTION: Anything you do to YOUR registry is by your own hands and any damages incurred are through no fault, direction nor assistance from me and you absolve any and all guilty association from me!) HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services in the registry.  Look around in there for the Name and Description that make sense for the Service you want to unintall/delete.  Then if you can make heads or tails of the image path, then try to uninstall it using the installutil tool.  Otherwise, delete it from here and then reboot.  You've effictively manualy deleted the Windows Service.

 

 

 

Resources:

Source Code

Ottawa Code Camp 2009: Windows Services, Down and Dirty

MSDN: Windows Service Applications

MSDN: Introduction to Windows Service Applications

MSDN: ServiceBase Class

Don't Back Down: Manually Remove a Windows Service

Tags:
Categories:
Location: Blogs Parent Separator TechTidBits

2 comment(s) so far...


Re: Ever wanted to create a Windows Service but didn't know where to start? Well, here's a great place!

Thanks for the terrific post Peter. I really enjoyed your CodeCamp presentation, and picked up a few things that I hadn't seen before.

By Peter Bernier on   Wednesday, June 03, 2009 11:01 AM

Re: Ever wanted to create a Windows Service but didn't know where to start? Well, here's a great place!

Thank you very much! Much appreciated! :> I really enjoyed that day and doing this post and seeing people (well, ok, one so far :>) posting a comment makes it all worthwhile! I love this stuff! :> Thanks for sharing! :> Have a good one! :>

By phenry on   Wednesday, June 03, 2009 11:03 AM

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Add Comment   Cancel 
     
  
Copyright 1999-2012 by PCHenry.com   Terms Of Use  Privacy Statement