Click here to Skip to main content
13,007,637 members (76,827 online)
Click here to Skip to main content
Add your own
alternative version


105 bookmarked
Posted 23 Dec 2013

Windows service auto-update plugin framework

, 31 Dec 2013
Rate this:
Please Sign up or sign in to vote.
Using a plugin system to auto-update functionality of a windows service without the need to re-install the service


Windows services are long running processes that operate in the background, however, just because they are out of the way does not mean they don't need to be updated. One of the problems with services is that they need admin access to install and reinstall when code needs to be updated. This article describes a framework that allows you to update the code in your windows service on demand, without the need for admin intervention. The code given works and a more integrated version is in production - the article here presents the general overview. There are many tweaks and different approaches that could be taken to implement the detail, depending on your particular requirements. Hopefully this will give your particular solution a head-start.  I encourage you to download the code, and if you have any improvements please leave a comment and send them on so everyone can benefit. The methodology presented here is very rough at the moment and leaves a lot of internal adaptation up to the developer. In a future release I will wrap the code together into a more robust self managing framework that can be installed as a package, that includes some other interesting stuff I am working on at the moment! 



If you have a windows service on a handful of sites and need to update it, its not a problem ... dial-in, hit the command line and job done. When you have a large installed user-base however things start to get a little bit more problematic. The framework design discussed here was put together to serve as the basis of a large scale self managing / remote updating eco-system of machines. The main concepts are introduced here with suggestions for your own implementation.


The main concept behind the framework is to remove all work from the service itself, and use it only as a shell to load/unload plugin assemblies that carry out any required work. This is achieved by loading the assemblies into one or more application domains that critically, are separate from the main service host application domain. The reason for the separate domains, is that while you can easily load a plugin into the current/main app-domain, you can only unload an entire domain at once, you cannot be more specific than that. If we have our plugins loaded into the same domain as the core service application, then unloading the plugins, by default, unloads the main application as well. In this framework implementation, the service host only needs to know two things - when, and how to load/unload plugins. Everything else is handled by a plugin host controller, and the plugins themselves. 




The framework operates as follows:

~ Setup ~

The service can do two things (1) create a plugin controller and keeps it at arms length using MarshalByRef, (2) receive event messages sent to it by the plugin controller.

~ Managing ~ 

The plugin controller creates 1..n application domains as needed. In the case of this demo I created a "command" domain and one called "plugins". The concept is that "command" might be used to check against a web-service for updated versions of plugins and use that to kick off a "refresh / reload" routine, and the "plugins" carry out some worker processes. Command plugins typically would encompass a scheduler object that triggers actions at certain time intervals.


~ Messaging ~

The framework is controlled by messages that flow from plugins, to the controller and up to the host service program. Messages can be simple log and notification messages, or may be action messages that tell either the controller or the service to trigger a particular action. Trigger actions could be commands like "check for new version on server", "ping home to main server", "load/unload a particular app domain". As the objective is to keep all work and logic away from the service, take care to separate work into discrete plugin packages. Not all plugins need to be loaded all the time consuming resources. By using different application domains you can facilitate load/unload on demand using a main scheduler plugin. 


Plugin definition 

With any plugin system an important part building block is a known interface definition that the plugin controller can manage. To kick things off, I created an interface that encompasses the minimum functionality I required. This included methods to flag a running process that it is to stop, and signal a self-unload event, when it completes its process run  

    // Interface each plugin must implement
    public interface IPlugin
        string PluginID(); // this should be a unique GUID for the plugin - a different one may be used for each version of the plugin.
        bool TerminateRequestReceived(); // internal flag if self-terminate request has been received
        string GetName(); // assembly friendly name
        string GetVersion();// can be used to store verison of assembly
        bool Start();// trigger assembly to start
        bool Stop(); // trigger assembly to stop
        void LogError(string Message, EventLogEntryType LogType); // failsafe - logs to eventlog on error
        string RunProcess(); // main process that gets called
        void Call_Die(); // process that gets called to kill the current plugin
        void ProcessEnded(); // gets called when main process ends, ie: web-scrape complete, etc...

        // custom event handler to be implemented, event arguments defined in child class
        event EventHandler<plugineventargs> CallbackEvent;
        PluginStatus GetStatus(); // current plugin status (running, stopped, processing...)

When we send messages over a remoting boundary, we need to serialize the messages. For this implementation I chose to create a custom EventArgs class to send with my event messages. 

    // event arguments defined, usage: ResultMessage is for any error trapping messages, result bool is fail/success
    // "MessageType" used to tell plugin parent if it needs to record a message or take an action etc.
    public class PluginEventArgs : EventArgs
        public PluginEventMessageType MessageType;
        public string ResultMessage;
        public bool ResultValue;
        public string MessageID;
        public string executingDomain;
        public string pluginName;
        public string pluginID;
        public PluginEventAction EventAction;
        public CallbackEventType CallbackType; 
        public PluginEventArgs(PluginEventMessageType messageType = PluginEventMessageType.Message, string resultMessage = "",PluginEventAction eventAction = (new PluginEventAction()), bool resultValue = true)
            // default empty values allows us to send back default event response
            this.MessageType = messageType; // define message type that is bring sent
            this.ResultMessage = resultMessage; // used to send any string messages
            this.ResultValue = resultValue;
            this.EventAction = eventAction; // if the event type = "Action" then this carries the action to take
            this.executingDomain = AppDomain.CurrentDomain.FriendlyName;
            this.pluginName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
            //this.pluginID = ((IPlugin)System.Reflection.Assembly.GetExecutingAssembly()).PluginID();


There are a number of supporting types and classes as you can see - I don't wish to copy the entire code into the article so if you wish to see the detail please download the attached code and go through it in visual studio.

Plugin manager  

The plugin manager contains two main classes. The PluginHost, and the Controller. All are wrapped as remote objects using MarshalByRefObject. 

Plugin host

The host keeps the controller and plugins an arms length away from the main application. It defines and sets up the different app-domains, then calls the controller to load and manage the plugins themselves.

    public class PluginHost : MarshalByRefObject
        private const string DOMAIN_NAME_COMMAND = "DOM_COMMAND";
        private const string DOMAIN_NAME_PLUGINS = "DOM_PLUGINS";

        private AppDomain domainCommand;
        private AppDomain domainPlugins;
        private PluginController controller_command;
        private PluginController controller_plugin;

        public event EventHandler<plugineventargs> PluginCallback;

Loading into a domain

        public void LoadDomain(PluginAssemblyType controllerToLoad)
            switch (controllerToLoad)
                case PluginAssemblyType.Command:
                        controller_command = (PluginController)domainCommand.CreateInstanceAndUnwrap((typeof(PluginController)).Assembly.FullName, (typeof(PluginController)).FullName);
                        controller_command.Callback += Plugins_Callback;
                case PluginAssemblyType.Plugin:
                        controller_plugin = (PluginController)domainPlugins.CreateInstanceAndUnwrap((typeof(PluginController)).Assembly.FullName, (typeof(PluginController)).FullName);
                        controller_plugin.Callback += Plugins_Callback;

Plugin controller

The plugin controller is closest to the plugins themselves. It is the first port of call for the message flow, and takes care of controlling message flow between plugins, and from the plugins back up to the service application program.

void OnCallback(PluginEventArgs e)
    // raise own callback to be hooked by service/application
    // pass through callback messages received if relevant
    if (e.MessageType == PluginEventMessageType.Action)
        else if (e.EventAction.ActionToTake == PluginActionType.Unload) // since the plugin manager manages plugins, we intercept this type of message and dont pass it on
        if (Callback != null) // should ONLY happen is not type action and only message
            Callback(this, e);


For this demo example, the plugins are being kept very simple. All but one has the same code. They have a timer, and onInterval, print a message to the console. If they receive a shutdown message, they shut-down immediately, unless they are in the middle of a process, in which case they will complete that process and then signal they are ready for unloading.

        public bool Stop()
            if (_Status == PluginStatus.Running) // process running - cannot die yet, instead, flag to die at next opportunity
                _terminateRequestReceived = true;
                DoCallback(new PluginEventArgs(PluginEventMessageType.Message, "Stop called but process is running from: " + _pluginName)); 
                if (counter != null)
                _terminateRequestReceived = true;
                DoCallback(new PluginEventArgs(PluginEventMessageType.Message, "Stop called from: " + _pluginName));
            return true;


        // OnTimer event, process start raised, sleep to simulate doing some work, then process end raised
        public void OnCounterElapsed(Object sender, EventArgs e)
            _Status = PluginStatus.Processing;
            DoCallback(new PluginEventArgs(PluginEventMessageType.Message, "Counter elapsed from: " + _pluginName));
            if (_terminateRequestReceived)
                DoCallback(new PluginEventArgs(PluginEventMessageType.Message, "Acting on terminate signal: " + _pluginName));
                _Status = PluginStatus.Stopped;
                _Status = PluginStatus.Running; // nb: in normal plugin, this gets set after all processes complete - may be after scrapes etc.

The "command / control" plugin simulates requesting that the service update itself (hey, finally, the reason we came to this party!) ....

// OnTimer event, process start raised, sleep to simulate doing some work, then process end raised
public void OnCounterElapsed(Object sender, EventArgs e)
    _Status = PluginStatus.Processing;
    DoCallback(new PluginEventArgs(PluginEventMessageType.Message, "Counter elapsed from: " + _pluginName));
    if (_terminateRequestReceived)
        DoCallback(new PluginEventArgs(PluginEventMessageType.Message, "Counter elapsed, terminate received, stopping process...  from: " + _pluginName));

    // TEST FOR DIE...
    DoCallback(new PluginEventArgs(PluginEventMessageType.Message, "*** Sending UPDATE SERVICE WITH INSTALLER COMMAND ***"));
    PluginEventAction actionCommand = new PluginEventAction();
    actionCommand.ActionToTake = PluginActionType.TerminateAndUnloadPlugins; // TEST !!!! ... this should ONLY be used to signal the HOST/CONTROLLER to flag a DIE....
    DoCallback(new PluginEventArgs(PluginEventMessageType.Action, null, actionCommand));
    DoCallback(new PluginEventArgs(PluginEventMessageType.Message, "*** Sending UPDATE SERVICE WITH INSTALLER COMMAND - COMPLETE ***"));
    // end test

A critical "gotcha" snippet of code overrides the MarshalByRef "InitializeLifetimeService" method. By default, a remote object will die after a short space of time. By overriding this you ensure your object stays live as long as you wish.

public override object InitializeLifetimeService()
    return null;

Service program

When we start the service, we hook the plugin manager event callback.

public void Start()
    if (pluginHost == null)
        pluginHost = new PluginHost();
        pluginHost.PluginCallback += Plugins_Callback;

When an unload event bubbles up, we can shell out to an MSI installer that we run in silent mode, and use it to update the plugins themselves. The MSI installer is simply a means of wrapping things nicely in a package. The objective is to run the msi in silent mode, therefore requiring no user interaction. You could also use nuget etc and I will investigate this in a further iteration. 

private void Plugins_Callback(object source, PluginContract.PluginEventArgs e)
    if (e.MessageType == PluginEventMessageType.Message)
        EventLogger.LogEvent(e.ResultMessage, EventLogEntryType.Information);
        Console.WriteLine(e.executingDomain + " - " + e.pluginName + " - " + e.ResultMessage); // for debug
    else if (e.MessageType == PluginEventMessageType.Action) {
        if (e.EventAction.ActionToTake == PluginActionType.UpdateWithInstaller)
            Console.WriteLine("****  DIE DIE DIE!!!! ... all plugins should be DEAD and UNLOADED at this stage ****");
            EventLogger.LogEvent("Update with installer event received", EventLogEntryType.Information);
            // Plugin manager takes care of shutting things down before calling update so we are safe to proceed...
            if (UseInstallerVersion == 1)
                EventLogger.LogEvent("Using installer 1", EventLogEntryType.Information);
                UseInstallerVersion = 2;
                // run installer1 in silent mode - it should replace files, and tell service to re-start
            else if (UseInstallerVersion == 2)
                EventLogger.LogEvent("Using installer 2", EventLogEntryType.Information);
                // run installer2 in silent mode - it should replace files, and tell service to re-start
                UseInstallerVersion = 1;

Congratulations, you now have a self-updating windows service that once installed, can be managed remotely with little or no intervention.


22/12/2013 - version 1 published


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

United Kingdom United Kingdom
Architect/engineer, student, always learning and refactoring my wetware. Happiest tucking into a big bowl of c# or python sprinkled with a crisp topping of Javascript...started with a single ZX80 a lifetime ago, now happily explore, build and create on interweb scale...

  • 1 Apr 2017 - Awarded Microsoft MVP for 2017

  • 18 Mar 2017 - "Calculating distance using Google Maps in ASP.NET MVC" - article of the day on

  • 31 Dec 2016 - "How to improve Single Page App and Hybrid mobile performance" - article of the day on

  • 23 Oct 2016 - "Multi-user/resource web diary in C# MVC with repeat events" - article of the day on

  • 7 Jun 2016 - "Upload large files to MVC / Web API using partitioning" - article of the day on

  • 16 Sep 2015 - "Single Page Application with MVC and SammyJS" - article of the day on

  • 01 Sep 2015 - "KnockoutJS nested arrays in MVC" - article of the day on

  • 21 Aug 2015 - "Audit trail and data versioning with C# and MVC" - article of the day on

  • 6 Oct 2014 - "Full Calendar – A Complete Web Diary System for jQuery and C# MVC" - article of the day on

You may also be interested in...

Comments and Discussions

QuestionWhat to implement? Pin
Member 1026201420-Mar-17 5:50
memberMember 1026201420-Mar-17 5:50 
s40804-Mar-16 4:37
members40804-Mar-16 4:37 
AJSON4-Mar-16 5:06
mvpAJSON4-Mar-16 5:06 
QuestionDo you have the updated production version Pin
SilasPeter27-Aug-14 4:33
memberSilasPeter27-Aug-14 4:33 
QuestionLibraries in the same directory Pin
pepperz12-Mar-14 9:57
memberpepperz12-Mar-14 9:57 
AnswerRe: Libraries in the same directory Pin
AJSON12-Mar-14 10:11
memberAJSON12-Mar-14 10:11 
GeneralRe: Libraries in the same directory Pin
pepperz17-Mar-14 13:09
memberpepperz17-Mar-14 13:09 
AnswerRe: Libraries in the same directory Pin
AJSON17-Mar-14 22:20
memberAJSON17-Mar-14 22:20 
GeneralRe: Libraries in the same directory Pin
pepperz17-Mar-14 13:10
memberpepperz17-Mar-14 13:10 
QuestionDoes not work Pin
zimareff26-Feb-14 4:22
memberzimareff26-Feb-14 4:22 
Answer@zimareff Pin
AJSON26-Feb-14 3:18
memberAJSON26-Feb-14 3:18 
GeneralRe: @zimareff Pin
zimareff26-Feb-14 4:25
memberzimareff26-Feb-14 4:25 
GeneralThanks Pin
paragpatel316-Jan-14 21:12
memberparagpatel316-Jan-14 21:12 
AnswerRe: Thanks Pin
AJSON6-Jan-14 21:21
memberAJSON6-Jan-14 21:21 
QuestionPull plugins from a database Pin
Kees van Spelde31-Dec-13 4:52
memberKees van Spelde31-Dec-13 4:52 
AnswerRe: Pull plugins from a database Pin
AJSON31-Dec-13 5:01
memberAJSON31-Dec-13 5:01 
GeneralRe: Pull plugins from a database Pin
Kees van Spelde31-Dec-13 5:10
memberKees van Spelde31-Dec-13 5:10 
AnswerRe: Pull plugins from a database Pin
AJSON31-Dec-13 5:18
memberAJSON31-Dec-13 5:18 
QuestionDrop the MSI Pin
Andy Neillans30-Dec-13 20:45
memberAndy Neillans30-Dec-13 20:45 
AnswerRe: Drop the MSI Pin
AJSON30-Dec-13 21:43
memberAJSON30-Dec-13 21:43 
GeneralMy vote of 5 Pin
Hoangitk30-Dec-13 14:54
memberHoangitk30-Dec-13 14:54 
AnswerRe: My vote of 5 Pin
AJSON31-Dec-13 7:05
memberAJSON31-Dec-13 7:05 
QuestionExcellent! Pin
Drew Stoddard30-Dec-13 9:14
memberDrew Stoddard30-Dec-13 9:14 
AnswerRe: Excellent! Pin
AJSON30-Dec-13 9:35
memberAJSON30-Dec-13 9:35 
QuestionNice! Pin
Volynsky Alex24-Dec-13 8:03
memberVolynsky Alex24-Dec-13 8:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.170628.1 | Last Updated 31 Dec 2013
Article Copyright 2013 by AJSON
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid