Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Windows Self-installing Named Services

0.00/5 (No votes)
19 Dec 2014 1  
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:

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:

[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:

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

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:
    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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here