Arpalesa Blogs

Categorieën

Op datum

Uitgelicht

Self installing windows service

Self installing windows service

Windows Services zijn ideaal. Het "nadeel" is alleen wel dat je ze standaard met InstallUtil moet installeren. En die heb je nou net weer niet altijd op je PC of Server geinstalleerd staan, staat niet in het path of is van een 32 of juist 64 versie. Vandaar deze stap voor stap uitleg om een windows service te schrijven met eigen installer zodat je de executable gewoon met /i of /install als service kan laten installeren (en met /u of /uninstall natuurlijk weer kan laten verwijderen). Dit is nog de oude variant, dus geen WCF versie. Omdat het altijd weer even zoeken is en het voor heel veel software steeds toch wel weer handig is om achtergrondtaken uit te voeren hier dus de opzet:

1. Een windows service maak je aan als "Console Application" project. Op die manier wordt het een executable file die je met parameters aan kan roepen en dit stelt je ook in staat naar de console (prompt) te schrijven:

2. Hier voer je een windows service aan toe die je de naam geeft van je service:

3. Dubbelklik op de toegevoegde service, rechtermuisklik in het design scherm en kies “Add Installer”:

4. Er is nu een “ProjectInstaller.cs” toegevoegd. Dubbelklik die. Op het design scherm daarvan zie je nu de componenten: “serviceProcessInstaller1” en “serviceInstaller1”. Klik op beide en stel de properties na wens in (zoals naam van de service, omschrijving, start type, standaard gebruiker waaronder deze komt te draaien, delayed start, etc..):

5. Voeg nu deze code toe aan de ProjectInstaller.cs:

      public static void Install(bool undo, string[] args)
        {
            try
            {
                Console.WriteLine(undo ? "Bezig met de-installeren..." :
"Bezig met installeren...");
                using (AssemblyInstaller inst = new
AssemblyInstaller(typeof(Program).Assembly, args))
                {
                    IDictionary state = new Hashtable();
                    inst.UseNewContext = true;
                    try
                    {
                        if (undo)
                        {
                            inst.Uninstall(state);
                        }
                        else
                        {
                            inst.Install(state);
                            inst.Commit(state);
                        }
                    }
                    catch
                    {
                        try
                        {
                            inst.Rollback(state);
                        }
                        catch { }
                        throw;
                    }
                }
            }
            catch (Exception err)
            {
                Console.Error.WriteLine("Er is een fout opgetreden: " + err.Message);
            }
        }

6. Voeg vervolgens een ServiceEntryPoint classe toe aan het project. Deze classe gaat de daadwerkelijke functionaliteit van de service uitvoeren:

7. Plak de volgende code in deze serviceentrypoint classe en pas die na wens aan. In DoRun() zet je de code die de service elke interval moet uitvoeren. Aan de configfile kan je een IntervalSeconds toevoegen waarin je het aantal seconde op kan geven wanneer de service opnieuw moet draaien. Dit gaat uit van een service die elke periode iets uitvoert. Als je een filemonitor of service wil maken die op andere events gaat reageren dan aboneer je op die events in de StartService en dispose je dat in de StopService.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SuperWindowsService
{
    public class ServiceEntryPoint
    {
        protected static System.Timers.Timer t = null;
        public static bool IsInRun = false;

        public static void DoRun()
        {
            // Perform single service action run
        }

        ///


        /// Initialize the timer, will start service logic every 60 seconds
        /// but can be overwritten by setting the IntervalSeconds appsetting.
        ///

        public static void StartService()
        {
            if(t == null)
            {
                t = new System.Timers.Timer(60000);
                try {
                    t.Interval = int.Parse(System.Configuration.ConfigurationManager.AppSettings["IntervalSeconds"]) * 1000;
                }
                catch
                {
                    // Don't care, will default to 60 seconds.
                    t.Interval = 60000;
                }
                t.Elapsed += T_Elapsed;
                t.Start();
            }
        }

        ///


        /// Stop the service (will not terminate active run)
        ///

        public static void StopService()
        {
            if(t!= null)
            {
                t.Stop();
                t.Dispose();
            }
        }

        ///


        /// Starts a new service instance but only if the previous instance finished.
        ///

        ///
        ///
        private static void T_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (!IsInRun)
            {
                IsInRun = true;
                try
                {
                    DoRun();
                }
                catch
                {
                    // Todo: LOG. Always try/catch the service run to prevent it
                    // from failing the service itself.
                }
                IsInRun = false;
            }
        }
    }
}

8. Pas nu de Onstart en OnStop aan van de SuperWindowsService.cs om deze StartService en StopService aan te roepen van de ServiceEntryPoint:

        protected override void OnStart(string[] args)
        {
            ServiceEntryPoint.StartService();
        }

        protected override void OnStop()
        {
            ServiceEntryPoint.StopService();
        }

9. Pas ten slotte de Program.cs aan naar onderstaande. Maak je nu een build dan kan je met applicatienaam –i de service installeren, applicatienaam –u zal deze deinstalleren en applicatienaam –c draait deze in console modus (activeren zonder als service te registreren).

using System.ServiceProcess;
using System;

namespace SuperWindowsService
{
    class Program
    {
        static int Main(string[] args)
        {
            bool install = false;
            bool uninstall = false;
            bool console = false;
            bool rethrow = false;
            try
            {
                foreach (string arg in args)
                {
                    switch (arg.ToLower())
                    {
                        case "-i":
                        case "/i":
                        case "-install":
                        case "/install":
                            install = true;
                            break;
                        case "-u":
                        case "/u":
                        case "-uninstall":
                        case "/uninstall":
                            uninstall = true;
                            break;
                        case "-c":
                        case "/c":
                        case "-s":
                        case "/s":
                        case "-console":
                        case "/console":
                        case "-start":
                        case "/start":
                            console = true;
                            break;
                        default:
                            break;
                    }
                }
                if (!uninstall && !install && !console)
                {
                    try
                    {
                        Console.WriteLine("Onverwachte parameter. " +
                            "Eén van de volgende parameters was verwacht: " + System.Environment.NewLine +
                            "-i  Installeer de service" + System.Environment.NewLine +
                            "-u  Deinstalleer de service" + System.Environment.NewLine +
                            "-c  Start de service in console modus");
                    }
                    catch { }
                }
                if (install)
                {
                    ProjectInstaller.Install(false, args);
                    Console.WriteLine("De service is geinstalleerd...");
                }
                if (uninstall)
                {
                    ProjectInstaller.Install(true, args);
                    Console.WriteLine("De service is ge deinstalleerd...");
                }
                if (console)
                {
                    Console.WriteLine("Starten in console modus...");
                    ServiceEntryPoint.StartService();
                    Console.WriteLine("Gestart. Druk op een toets om te beëindigen...");
                    Console.ReadKey(true);
                    ServiceEntryPoint.StopService();
                    Console.WriteLine("Gestopt met uitvoering...");
                }
                else if (!(install || uninstall))
                {
                    rethrow = true; // Op deze manier ziet Windows dat er iets mis is...
                    ServiceBase[] services = { new SuperWindowsService() };
                    ServiceBase.Run(services);
                    rethrow = false;
                }
                return 0;
            }
            catch (System.Exception err)
            {
                if (rethrow) { throw; }
                Console.Error.WriteLine("Er is een fout opgetreden: " + err.Message);
                return -1;
            }
        }
    }
}

10. Dat was het. Maak hier een release build van en je hebt een executable die je als windows service kan laten installeren door hem te starten met /i of /install. Je verwijderd hem weer met /u of /uninstall en je kan hem zelfs zonder als service te installeren vanuit de command prompt opstarten met /c, /s, /console of /start.

Categories: .NET
You need to login in order to comment