Click here to Skip to main content
15,905,782 members
Articles / Programming Languages / C#
Tip/Trick

Windows Self-installing Named Services

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
19 Dec 2014CPOL2 min read 35K   881   16   13
Using InstallUtil.exe from command line is so boring

Introduction

After couple of times searching for an appropriate solution, I have decided to write my own. All topics about self-installing services assume that the service will be installed once without prompting to enter service name. So, uninstall performs operation with executable assemblies not with service. Second issue is that all solutions offer install and start service by the System, not by User and that doesn't suit me. I have based my solution on one of many self-installs like solutions and just modify something.

Using the Code

First of all, we must create a service. Let's do it:

C#
namespace Self_Installing_Service
{
    class Program : ServiceBase
    {
        static void Main(string[] args)
        {
            bool debugMode = false;
            if (args.Length > 0)
            {
                for (int ii = 0; ii < args.Length; ii++)
                {
                    switch (args[ii].ToUpper())
                    {
                        case "/I":
                            InstallService();
                            return;
                        case "/U":
                            UninstallService();
                            return;
                        case "/D":
                            debugMode = true;
                            break;
                        default:
                            break;
                    }
                }
            }

            if (debugMode)
            {
                Program service = new Program();
                service.OnStart(null);
                Console.WriteLine("Service Started...");
                Console.WriteLine("<press any key to exit...>");
                Console.Read();
            }
            else
            {
                System.ServiceProcess.ServiceBase.Run(new Program());
            }
        }

        /// <summary>
        /// start any threads or http listeners etc
        /// </summary>
        /// <param name="args"></param>
        protected override void OnStart( string[] args ) { }

        /// <summary>
        /// stop any threads here and wait for them to be stopped.
        /// </summary>
        protected override void OnStop() { }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
        }

        private static void InstallService()
        {
            try
            {
                ManagedInstallerClass.InstallHelper( new string[] 
            { Assembly.GetExecutingAssembly().Location } );
            }
            catch { }
        }

        private static void UninstallService()
        {
            ManagedInstallerClass.InstallHelper( new string[] 
            { "/u", Assembly.GetExecutingAssembly().Location } );
        }
    }
}

No magic with it. Second, we need to create a service installer. All that you need is create another class derived from System.Configuration.Install.Installer and add [RunInstaller(true)] attribute to it:

C#
[RunInstaller(true)]
public class CustomServiceInstaller : Installer
    {
        private ServiceProcessInstaller process;
        private ServiceInstaller service;

        public CustomServiceInstaller()
        {
            process = new ServiceProcessInstaller();
            process.Account = ServiceAccount.User;
            service = new ServiceInstaller();
            Installers.Add( process );
            Installers.Add( service );
        }
}

Great! But there are two issuses: there is no service name and no same installed service check. Begin solving, I create a form like this:

"Service name" is install service name. "Password" is the current user password. So you must be an administrator to perform service install procedure. Next, override "OnBeforeInstall" method:

C#
private void RemoveIfExists( string serviceName )
        {
            if( ServiceController.GetServices().Any
        ( s => s.ServiceName.ToLower() == serviceName.ToLower() ) )
            {
                Uninstall( null );
            }
        }

protected override void OnBeforeInstall( IDictionary savedState )
        {
            AuthForm form = new AuthForm();
            if ( form.ShowDialog() == DialogResult.OK )
            {
                process.Username = WindowsIdentity.GetCurrent().Name;
                string sname = "";
                string pwd = "";
                form.Get( out sname, out pwd );
                process.Password = pwd;
                service.ServiceName = sname;
                service.Description = Assembly.GetExecutingAssembly().GetName().Name;
                RemoveIfExists( sname );
                base.OnBeforeInstall( savedState );
            }
            else
            {
                throw new System.Exception( "Operation canceled by user" );
            }
        }

Summary

Now, we can create as many service instances as we can. And no more fear that named service already exists. Good luck. Regards.

Updates

24th December, 2014:

There is one major issue in my project. Installed services can`t be removed from system just by calling

C#
ManagedInstallerClass.InstallHelper( new string[] { "/u", Assembly.GetExecutingAssembly().Location } );

it happens because we are changing ServiceName in runtime. It`s one way to delete a service, find all services, that spawned by our assembly and call uninstall method for each of.  Thanks to Shmuli

and his solution A ServiceController Class that Contains the Path to Executable. According to it, i am made some changes:

In the service class append new method GetImagePath and modified uninstal method:

static Regex pathrx = new Regex("(?<=\").+(?=\")");

        private static string GetImagePath(string servicename  )
        {
            string registryPath = @"SYSTEM\CurrentControlSet\Services\" + servicename;
            RegistryKey keyHKLM = Registry.LocalMachine;
            RegistryKey key;
            key = keyHKLM.OpenSubKey( registryPath );
            string value = key.GetValue( "ImagePath" ).ToString();
            key.Close();
            var result = pathrx.Match(value);
            if( result.Success )
                return result.Value;
            return value;
        }

private static void UninstallService()
        {
            string binpath = Assembly.GetExecutingAssembly().Location;
            string dir = binpath.Remove( binpath.LastIndexOf( "\\" ) );
            var toBeRemoved = ServiceController.GetServices().Where( s => GetImagePath( s.ServiceName ) == binpath ).Select( x => x.ServiceName );
            CustomServiceInstaller installer = new CustomServiceInstaller();
            installer.Context = new InstallContext();
            foreach ( var sname in toBeRemoved )
            {
                try
                {
                    installer.Uninstall( sname );
                }
                catch { }
            }
        }

In the InstallerClass make overload method Uninstall, which takes a ServiceName to be uninstalled as param:

public void Uninstall( string serviceName )
        {
            service.ServiceName = serviceName;
            base.Uninstall( null );
        }

Source also updated.

History

  • 19th December, 2014: Initial version
  • In previous versions, I've forced debug mode:
    C#
    bool debugMode = true;

So in that case, installed service never starts normally and you will receive an #1053 error. Just turn in to false.

  • 24th December, 2014: Uninstall issue fixed

License

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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionGreate Pin
Aladár Horváth9-Aug-15 21:53
professionalAladár Horváth9-Aug-15 21:53 
GeneralGreat Idea! Pin
Hiko Davis22-Dec-14 21:39
Hiko Davis22-Dec-14 21:39 
GeneralRe: Great Idea! Pin
H0tHead22-Dec-14 22:56
H0tHead22-Dec-14 22:56 
QuestionNice article, thanks for that BUT download doesnt work Pin
NGzero22-Dec-14 12:15
professionalNGzero22-Dec-14 12:15 
AnswerRe: Nice article, thanks for that BUT download doesnt work Pin
H0tHead22-Dec-14 17:55
H0tHead22-Dec-14 17:55 
GeneralRe: Nice article, thanks for that BUT download doesnt work Pin
ahagel22-Dec-14 20:17
professionalahagel22-Dec-14 20:17 
GeneralRe: Nice article, thanks for that BUT download doesnt work Pin
NotBuzzAldrin8-Aug-16 13:12
NotBuzzAldrin8-Aug-16 13:12 
GeneralRe: Nice article, thanks for that BUT download doesnt work Pin
NotBuzzAldrin9-Aug-16 14:19
NotBuzzAldrin9-Aug-16 14:19 
GeneralMy vote of 5 Pin
JBoada22-Dec-14 9:09
JBoada22-Dec-14 9:09 
GeneralNice Article!!! Pin
JBoada22-Dec-14 9:07
JBoada22-Dec-14 9:07 
QuestionMissing Code Pin
Dewey19-Dec-14 13:08
Dewey19-Dec-14 13:08 
AnswerRe: Missing Code Pin
H0tHead21-Dec-14 17:44
H0tHead21-Dec-14 17:44 
What you need to see ? =) If it is not to be clear, i am add what is missing...
Questionimage missing Pin
BigTimber@home19-Dec-14 3:06
professionalBigTimber@home19-Dec-14 3:06 

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.