Creating a Windows service with self-installer with VS 2015

Preface

I wrote some Windows services before with Visual Studio 2010, which provided the Visual Studio Setup Project. It had some quirks, but it worked for me. Starting with Visual Studio 2012, Microsoft did not include this project type anymore. Many complaints of the community followed, and in the end they built a VS extension, which brought back the old setup.

But there is an alternative way. Some years ago I thought that it would require to do it all yourself: writing registry entries and so on.

But it is easier:

Create Sample Service

♦ File => New => Project…

♦ Visual C# => Windows => Classic Desktop => Windows Service

♦ Name: SampleService

♦ “Create directory for solution” checked

♦ In Solution Explorer, select Service1.cs, right-click => Rename

♦ Rename file to SampleService.cs

♦ Select Yes for “… Would you also like to perform a rename …”

SampleService.cs should be shown in Design View now

♦ Click in the background of the designer view

♦ right-click => Add Installer

(serviceProcessInstaller1 and serviceInstaller1 have been added)

♦ open SampleService.cs in design view

♦ in Properties, set ServiceName to SampleService

♦ open ProjectInstaller.cs in Design View

♦ Click serviceInstaller1

♦ In the Properties window, set the ServiceName property to SampleService.

♦ Set the DisplayName property to Sample Service or something else
(This will be shown later in services console as service name)

♦ Set the Description property to something describing the service
(This will be shown later in services console as the description)

♦ Here you can also optionlly set StartType from Manual to Automatic

♦ click serviceProcessInstaller1
♦ Edit Account:
Here you can change if the service will run under a technical account (User)
or e.g. as LocalSystem

If you set it as User, a popup during setup will allow user / password to be set

♦ Add new class with name Install. And rename namespace accordingly.

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Configuration.Install;
using System.IO;
using System.Reflection;

namespace SampleService
{
    class Install
    {
        public static bool CheckAndInstall()
        {
            if (System.Environment.UserInteractive)
            {
                String[] args = Environment.GetCommandLineArgs();

                if (args.Length > 1)
                {
                    switch (args[1])
                    {
                        case "/i": // install
                        if (ManagedInstall(false))
                            Console.WriteLine("Successfully installed service");
                        return false;

                        case "/u": // uninstall
                            if (ManagedInstall(true))
                                Console.WriteLine("Successfully uninstalled service");
                            return false;

                        default: // unknown option
                            Console.WriteLine("Argument not recognized: {0}", args[1]);
                            Usage();
                            return false;
                    }
                }
                else
                {
                    Usage();
                    return false;
                }
            }

            return true;
        }

        private static bool ManagedInstall(bool uninstall)
        {
            var loc = Assembly.GetExecutingAssembly().Location;

            var args = uninstall ? new string[] { "/u", loc } : new string[] { loc };

            try
            {
                ManagedInstallerClass.InstallHelper(args);
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine();
                Console.WriteLine("===================================================");
                Console.WriteLine("Error occured during install/uninstall: " + ex.Message);
                Console.WriteLine("===================================================");
                return false;
            }
        }

        static void Usage()
        {
            var exeName = Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location);

            Console.WriteLine();
            Console.WriteLine("To install as service:");
            Console.WriteLine("{0} /i", exeName);
            Console.WriteLine();
            Console.WriteLine("Uninstall:");
            Console.WriteLine("{0} /u", exeName);
        }
    }
}

 

♦ Add this to program.cs in the beginning of main:

            if (!Install.CheckAndInstall())
                return;

 

♦ Go to Project ==> xxx Properties ==> Application and change Output Type from Windows Application to Console Application. This is necessary to see the console output.

♦ Build solution