32

We need to create a windows service that has the ability to self update.

Three options spring to mind,

  1. a second service that manages the retrieval, uninstallation and installation of the first service.

  2. Use of some third party framework (suggestions welcome. I believe .NET supports automatic updating for windows forms apps, but not windows services)

  3. Use of a plugin model, whereby the service is merely a shell containing the updating and running logic, and the business logic of the service is contained in a DLL that can be swapped out.

Can anyone shed some light on the solution to this problem?

Thanks

Jonas
  • 121,568
  • 97
  • 310
  • 388
Ben Aston
  • 53,718
  • 65
  • 205
  • 331

3 Answers3

6

Google have an open-source framework called Omaha which does exactly what your point 1. describes. It runs as a scheduled Windows task in the background, outside the applications it manages. Google use Omaha to auto-update their Windows applications, including Chrome. Because it comes from Google, and because it is installed on every Windows machine that runs Chrome, Omaha is extremely powerful.

There is an article online that explains in more detail how Omaha can be used to update Windows Services. It argues that Omaha is an especially good fit for Services (vs., say, GUI applications) because of its asynchronous nature.

So you could do your points 2. and 1. by using Omaha. I'm afraid I don't know how you would do 3.

Declan Nelson
  • 79
  • 1
  • 4
4

Just some thoughts I had.

1 seems problematic because you end up dealing with the situation you're trying to resolve because at some point the updater will need updating. 3 sounds good but if by "swapped out" you mean using some fancy reflection to load the dll during run time I'm not sure if performance will become an issue.

There is a fourth option where the service can spawn an update process which would allow it to update the update executable if necessary before running it. From there it's a simple matter of writing an installation app which the service will spawn just before shutting down.

Spencer Ruport
  • 34,865
  • 12
  • 85
  • 147
  • 2
    +1 We actually do have a self-updating .Net Windows service, and it does this when downloads a newer version: 1) Installs a new service if binary versions differ (could be just a configuration change though, in which case we just want to restart). 2) Spawn a helper process which: A) Stops old service, B) Starts new service, C) (if versions and not just configuration files) Remove the old service. This has been one of the trickiest areas to get right, and many bugs have been filed against this, and debugging has been a bitch :( – Hamish Grubijan Aug 03 '10 at 14:49
  • Firstly, if the new service is built against a newer .net Framework ... oh boy oh boy oh boy! We utilize some `.bat` files, it kind of works (with a restart sometimes), but it is fugly. Secondly, because debugging is a pain, LOG, LOG, LOG! The helper process should not write to the same log, so it should write to its own. `Trace.Writeln` is the easiest way to log, given that there is a proper entry in `app.config` file. Just remember to set auto-flush to true. Something this fragile better run slowly than without hints. Speaking of slowly, the helper needs to sleep for a few secs anyway. – Hamish Grubijan Aug 03 '10 at 14:53
  • @Dmitri Nesteruk, first here is the xml for logging: and you log with `Trace.Writeline`, etc. – Hamish Grubijan May 05 '11 at 23:30
  • This link is useful: http://www.csharp-examples.net/install-net-service/ So is this one: http://stackoverflow.com/questions/358700/how-to-install-a-windows-service-programmatically-in-c And this one: http://www.codeproject.com/Answers/188726/How-to-install-windows-service-programmatically.aspx#answer2 And this: http://www.dotnetmonster.com/Uwe/Forum.aspx/dotnet-csharp/2987/Installing-a-Windows-Service-Programmatically And this: http://social.msdn.microsoft.com/Forums/en-IE/csharplanguage/thread/f2ade549-e9cc-437a-b95d-a8a2bba87761 – Hamish Grubijan May 05 '11 at 23:41
  • This is how you call the updater: string strNewVersion = Path.Combine(_application.Client.BaseDir, _server.AvailableVersion + "\\service.exe"); var inst = new AssemblyInstaller(strNewVersion, new string[] { }); installer.Installers.Add(inst); installer.Install(new System.Collections.Hashtable()); var psii = new ProcessStartInfo(); psii.Arguments = String.Format("{0} {1}", GetCurrentRunningDirVersion(), _server.AvailableVersion); psii.FileName = "UpdaterHelper.exe"; psii.UseShellExecute = false; var procc = Process.Start(psii); procc.WaitForExit(); // Add a sleep before and or after perhaps. – Hamish Grubijan May 05 '11 at 23:47
1

I use option 1. The updater process gets updated very rarely these days. It uses an XML file containing the details of where to get the files from (currently supports SVN, working on adding NuGet support) and where to put them. It also specifies which ones are services and which ones are websites and specifies the name of the service to use for each project.

The process polls the source, if there is a new version available it copies it down to a fresh version numbered directory and then updates the service. It also keeps 5 copies of each update making it easy to roll-back if there is a problem.

Here's the core piece of code for the updater which stops the existing service, copies the files over, and then restarts it.

if (isService)
{
    log.Debug("Stopping service " + project.ServiceName);

    var service = GetService(project);
    if (service != null && 
        service.Status != System.ServiceProcess.ServiceControllerStatus.Stopped && service.Status != System.ServiceProcess.ServiceControllerStatus.StopPending)
    {
        service.Stop();
    }

    service.WaitForStatus(System.ServiceProcess.ServiceControllerStatus.Stopped, new TimeSpan(0, 1, 0));
    if (service.Status == System.ServiceProcess.ServiceControllerStatus.Stopped)
        log.Debug("Service stopped");
    else
        log.Error("ERROR: Expected Stopped by Service is " + service.Status);

}

log.Debug("Copying files over");
CopyFolder(checkoutDirectory, destinationDirectory);

if (isService)
{
    log.Debug("Starting service");
    var service = GetService(project);

    // Currently it doesn't create services, you need to do that manually
    if (service != null)
    {
        service.Start();

        service.WaitForStatus(System.ServiceProcess.ServiceControllerStatus.Running, new TimeSpan(0, 1, 0));

        if (service.Status == System.ServiceProcess.ServiceControllerStatus.Running)
            log.Debug("Service running");
        else
            log.Error("Service " + service.Status);
    }
}
Ian Mercer
  • 38,490
  • 8
  • 97
  • 133