Click here to Skip to main content
Click here to Skip to main content

Windows Services Can Install Themselves

By , 22 Nov 2007
 

Introduction

Using the InstallUtil.exe utility that ships with the .NET SDK can be a real pain. It's rarely in the PATH so you probably have to hunt down the utility when you are working on QA and production servers as I do. Installing a Windows Service should be easier. In this short article, I'll show you a way to make your Windows Services install themselves without needing InstallUtil.exe at all.

Assumptions

Let's assume that your service project has a service installer, a service process installer and a class derived from System.Configuration.Install.Installer already. If not, check out Mahmoud Nasr's excellent article on Windows Service development, then come back here.

Key Information

Thanks to Reflector for .NET by Lutz Roeder, it's easy to discover how the InstallUtil.exe utility does its job. After some setup, the InstallUtil.exe tool jumps to a method called InstallHelper in the ManagedInstallerClass in the System.Configuration.Install namespace. And what's really interesting is that the command line arguments passed to InstallUtil.exe as an array of strings are then passed directly to this helper method.

Well, this made me think, "If all InstallUtil.exe does is call the ManagedInstallerClass' InstallHelper method, why can't my service executable do the same thing to install itself on command?" The little class below makes it simple to do just that.

Using the Code

Create a new CS file in your service executable project containing the following code. You may also need to add a reference to the System.Configuration.Install.dll from the Global Assembly Cache if you don't already have one.

using System.Reflection;
using System.Configuration.Install;

namespace gotnet.biz.Utilities.WindowsServices
{
    public static class SelfInstaller
    {
        private static readonly string _exePath =
            Assembly.GetExecutingAssembly().Location;
        public static bool InstallMe()
        {
            try
            {
                ManagedInstallerClass.InstallHelper(
                    new string[] { _exePath } );
            }
            catch
            {
                return false;
            }
            return true;
        }

        public static bool UninstallMe()
        {
            try
            {
                ManagedInstallerClass.InstallHelper(
                    new string[] { "/u", _exePath } );
            }
            catch
            {
                return false;
            }
            return true;
        }
    }
}

Now you need to come up with some sort of convention for knowing when to invoke the installer. Below is just an example of how you might handle this in your Main() method, the entry point to your service. I like the convention of using -i or -install parameters to install the service and -u or -uninstall to uninstall it. I also like to use -c or -console to mean starting the application in a console rather than as a service. However, that's a topic for a different article.

using gotnet.biz.Utilities.WindowsServices;

namespace MyService.WinHost
{
    static class Program
    {
        public static void Main( string[] args )
        {
            if (args != null && args.Length == 1 && args[0].Length > 1
                && (args[0][0] == '-' || args[0][0] == '/'))
            {
                switch (args[0].Substring( 1 ).ToLower())
                {
                    default:
                        break;
                    case "install":
                    case "i":
                        SelfInstaller.InstallMe();
                        break;
                    case "uninstall":
                    case "u":
                        SelfInstaller.UninstallMe();
                        break;
                    case "console":
                    case "c":
                        MyConsoleHost.Launch();
                        break;
                }
            }
            else
                MyWinServiceHost.Launch();
        }
    }
}

Now, assuming my executable is named MyWinSvcHost.exe, I can invoke the installer by running:

C:\> MyWinSvcHost.exe -install

Or to uninstall my service, I would use this:

C:\> MyWinSvcHost.exe -uninstall

Other Ideas

This little bit of code called the SelfInstaller is full of possibilities. You could pass parameters to the InstallMe method to pass on to the ServiceProcessInstaller in your program, for example. Perhaps the domain name, user name and password used to start your service could be passed all the way from your Main() method to the ServiceProcessInstaller. Cool, right? I thought you would like that.

History

  • 22 Nov, 2007 - Initial publication

License

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

About the Author

W. Kevin Hazzard
Web Developer
United States United States
Member
After 16 years as an ardent C++ aficionado, Kevin switched to C# in 2001. Recently, Kevin's been dabbling in dynamically typed languages. Kevin is the Software Architect for Snagajob.com, the #1 source for hourly and part-time employment on the web.
 
Kevin loves welding, riding motorcycles and spending time with his family. Kevin has also been an adjunct professor teaching software engineering topics at a college in his hometown of Richmond, Virginia since 2000. Check out Kevin's technical blog at www.gotnet.biz for more goodies.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralService TemplatememberSteve Foster (Havok Multimedia)26 Sep '10 - 11:09 
Based off of this excellent article and the many additions made in the comments, I created a 'template' windows service file, that I thought might be useful for others. The code assumes that you have setup the default assembly attributes, and have some type of 'bootstrap' class that has a default constructor and implements IDisposable. Most of the configuration is contained in the WindowsServiceConfiguration. Thanks again for the great article.
 
To use it, just create a Console Project, replace the Program.cs file with this code, setup the attributes in the AssemblyInfo.cs file, and point the THREAD field to a class to run.
You will need to add references to System.Configuration, System.Configuration.Install, and System.ServiceProcess.
 
 
#region Imports
 
using System;
using System.ComponentModel;
using System.Configuration.Install;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ServiceProcess;
 
#endregion
 
namespace MyNamespace
{
    public static class WindowsServiceConfiguration
    {
        #region Constants
 
        /// <summary>
        /// If SERVICE_USERNAME is left blank then use this account to run the service. 
        /// </summary>
        public static readonly ServiceAccount SERVICE_ACCOUNT = ServiceAccount.LocalSystem;
 
        /// <summary>
        /// The service description, defaulted to the Assembly Description attribute.
        /// </summary>
        public static readonly String SERVICE_DESCRIPTION = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof (AssemblyDescriptionAttribute), true).Cast<AssemblyDescriptionAttribute>().First().Description;
 
        /// <summary>
        /// The service name, defaulted to the Assembly Title attribute.
        /// </summary>
        public static readonly String SERVICE_NAME = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof (AssemblyTitleAttribute), true).Cast<AssemblyTitleAttribute>().First().Title;
 
        public static readonly ServiceStartMode START_MODE = ServiceStartMode.Automatic;
 
        /// <summary>
        /// The thread to start and stop. Must have a default constructor and implement IDisposable.
        /// Most likely this class will create a System.Thread in it's constructor and launch it.
        /// It could also do something like start an IoC container, Web Server, etc.
        /// </summary>
        public static readonly Type THREAD = typeof (Container);
 
        #endregion
 
        #region User Account Info
 
        // if SERVICE_USERNAME isn't set, default to SERVICE_ACCOUNT as user
        public static readonly String SERVICE_DOMAIN;
        public static readonly String SERVICE_PASSWORD;
        public static readonly String SERVICE_USERNAME;
 
        #endregion
    }
 
    public class Program
    {
        #region Constants
 
        public static readonly Assembly ASM = Assembly.GetExecutingAssembly();
        public static readonly String NL = Environment.NewLine;
        public static readonly String SERVICE_ASSEMBLY_VERSION = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof (AssemblyFileVersionAttribute), true).Cast<AssemblyFileVersionAttribute>().First().Version;
        public static readonly String SERVICE_COPYRIGHT = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof (AssemblyCopyrightAttribute), true).Cast<AssemblyCopyrightAttribute>().First().Copyright;
        public static readonly String SERVICE_VERSION = Assembly.GetExecutingAssembly().GetName().Version.ToString();
 
        #endregion
 
        public static int Main(string[] args)
        {
            // Started by user
            if (Environment.UserInteractive)
            {
                // Show version information
                Console.Write(
                    NL + WindowsServiceConfiguration.SERVICE_NAME + " " +
                    // If the version isn't the same as the file version, display both.
                    (SERVICE_VERSION == SERVICE_ASSEMBLY_VERSION ? SERVICE_VERSION : SERVICE_VERSION + " " + "[" + SERVICE_ASSEMBLY_VERSION + "]") +
                    NL + SERVICE_COPYRIGHT);
 
                // Parsing command line
                if (args != null && args.Length >= 1)
                {
                    if (args[0].ToLower() == "/i") return MainInstaller(true);
                    if (args[0].ToLower() == "/u") return MainInstaller(false);
                    if (args[0].ToLower() == "/c") return LaunchConsole();
                }
 
                // Show usage
                Console.WriteLine(
                    NL + NL + "Usage: " + Path.GetFileName(ASM.Location) + " [/i | /u | /c]" +
                    NL + NL + "Where:" +
                    NL + "\t/i - install service." +
                    NL + "\t/u - uninstall service." +
                    NL + "\t/c - run in console.");
            }
            else
            {
                // Started by SCM
                ServiceBase.Run(new WindowsService());
            }
 
            return 0;
        }
 
        private static int MainInstaller(bool shouldInstall)
        {
            String[] args;
            if (shouldInstall)
            {
                // InstallHelper with /u switch set adds CRLF here, but with /i switch set it does not. So...
                Console.WriteLine();
                args = new[] {"/LogFile=", ASM.Location};
            }
            else
            {
                args = new[] {"/u", "/LogFile=", ASM.Location};
            }
 
            try
            {
                ManagedInstallerClass.InstallHelper(args);
                return 0;
            }
            catch (Exception e)
            {
                Console.WriteLine(" --- ERROR --- " + NL + e);
                return -1;
            }
        }
 
        public static int LaunchConsole()
        {
            //Console.SetBufferSize(1000, 1000);
            Console.WriteLine(NL + NL + "Starting... Press 'Q' or 'Esc' to exit");
 
            try
            {
                using (CreateThread())
                {
                    while (true)
                    {
                        var key = Console.ReadKey(true).Key;
                        if (key == ConsoleKey.Q) break;
                        if (key == ConsoleKey.Escape) break;
                    }
                    Console.WriteLine(NL + "Exiting" + NL);
                }
                return 0;
            }
            catch (Exception e)
            {
                Console.WriteLine(" --- ERROR --- " + NL + e);
                return -1;
            }
        }
 
        public static IDisposable CreateThread()
        {
            return (IDisposable) Activator.CreateInstance(WindowsServiceConfiguration.THREAD);
        }
    }
 
    public class WindowsService : ServiceBase
    {
        #region Constructors
 
        public WindowsService()
        {
            ServiceName = WindowsServiceConfiguration.SERVICE_NAME;
        }
 
        #endregion
 
        #region Fields
 
        private IDisposable thread;
 
        #endregion
 
        #region Service Methods
 
        protected override void OnStart(string[] args)
        {
            if (thread != null) return;
            thread = Program.CreateThread();
        }
 
        protected override void OnStop()
        {
            if (thread != null)
            {
                thread.Dispose();
                thread = null;
            }
        }
 
        #endregion
    }
 
    [RunInstaller(true)]
    public class WindowsServiceInstaller : Installer
    {
        #region Constructors
 
        public WindowsServiceInstaller()
        {
            if (WindowsServiceConfiguration.SERVICE_USERNAME == null)
            {
                processInstaller.Account = WindowsServiceConfiguration.SERVICE_ACCOUNT;
            }
            else
            {
                if (WindowsServiceConfiguration.SERVICE_DOMAIN == null)
                {
                    processInstaller.Username = WindowsServiceConfiguration.SERVICE_USERNAME;
                }
                else
                {
                    processInstaller.Username = WindowsServiceConfiguration.SERVICE_DOMAIN + "\\" + WindowsServiceConfiguration.SERVICE_USERNAME;
                }
 
                processInstaller.Password = WindowsServiceConfiguration.SERVICE_PASSWORD;
            }
 
            serviceInstaller.ServiceName = WindowsServiceConfiguration.SERVICE_NAME;
            serviceInstaller.Description = WindowsServiceConfiguration.SERVICE_DESCRIPTION;
            serviceInstaller.DisplayName = WindowsServiceConfiguration.SERVICE_NAME;
            serviceInstaller.StartType = WindowsServiceConfiguration.START_MODE;
 
            var installer = new Installer[] {processInstaller, serviceInstaller};
 
            Installers.AddRange(installer);
        }
 
        #endregion
 
        #region Fields
 
        private readonly ServiceProcessInstaller processInstaller = new ServiceProcessInstaller();
        private readonly ServiceInstaller serviceInstaller = new ServiceInstaller();
 
        #endregion
    }
}
 

GeneralRe: Service TemplatememberComplexityChaos14 Oct '10 - 6:26 
The code as is will do nothing in console mode. Would you provide an example where the OnStart event is triggered (or pseudo triggered) in console mode? Assume, please, no IOC container.
Thanks
GeneralRe: Service TemplatememberSteven D. Foster15 Oct '10 - 5:05 
The OnStart method calls the same CreateThread method as the console mode, so triggering OnStart would do (as intended) the exact same as the Console mode.
 
Replace the Program.CreateThread() method to return a timer or some other thread instead. If you need information on threads, check out something like System.Timers.Timer
 
Here is an example (I haven't tested, just going off the top of my head).
 
public static IDisposable CreateThread()
{
   // return (IDisposable) Activator.CreateInstance(WindowsServiceConfiguration.THREAD);
   var timer = new System.Timers.Timer(5000) {AutoReset = true};
   timer.Elapsed += ((sender, e) => Console.WriteLine("Hello World: " + DateTime.Now));
   timer.Start();
   return timer;
}

GeneralNice butmentorGiorgi Dalakishvili6 Mar '10 - 10:18 
When I try to start the service I am getting Error 1053: the service did not respond to the start or control request in a timely fashion. Using an ordinary installer the service works fine.
 
Any ideas?
Giorgi Dalakishvili
 
#region signature
My Articles
 
Browsing xkcd in a windows 7 way[^]
 
#endregion

Questionhow do i get the service name from another exememberjbencic19 Dec '09 - 15:56 
I am creating a deployment app to auto deploy many msi's
 
currently we use WIX msi's and the installutil to install the services
 

 
what i was trying to do was use the ManagedInstallerClass to install the services
 
problem is how can i retrieve the service's names passing the exe path?
i need to store and restore the service state
 

        public static bool InstallService(string _exePath, string svcName)
        {
            try
            {
                // TODO: get service name from the executable
                if (IsServiceInstalled(svcName)) return false;
 
                ManagedInstallerClass.InstallHelper(new string[] { _exePath });
            
                // verify results
                if (!IsServiceInstalled(svcName)) return true;
            }
            catch(Exception ex)
            {
                Logger.Log(ex);
            }
            return false;
        }

QuestionError 1053: The service did not respond to the start or control request in a timely fashion.memberkhayyam221 Sep '09 - 20:15 
When i install my service using the installUtil.exe i dont get any error and service runs successfully. but when i implement the above code in my service project i get following error message.
 
Error 1053: The service did not respond to the start or control request in a timely fashion.
 
Please help
GeneralTwo lines are confusing me please helpmemberkhayyam221 Sep '09 - 20:11 
case "console":
                    case "c":
                        MyConsoleHost.Launch();
                    break;
                }
            }
            else
            MyWinServiceHost.Launch();

GeneralConfused Stepsmemberkhayyam221 Sep '09 - 7:50 
hi all,
 
i am reviewing this code for 3 to 4 days continously but didnt get an idea that
 
1. what do you mean by service executable. Should i put my below code in windows service project
 
using System.Reflection;
using System.Configuration.Install;
 
namespace gotnet.biz.Utilities.WindowsServices
{
    public static class SelfInstaller
    {
        private static readonly string _exePath =
            Assembly.GetExecutingAssembly().Location;
        public static bool InstallMe()
        {
            try
            {
                ManagedInstallerClass.InstallHelper(
                    new string[] { _exePath } );
            }
            catch
            {
                return false;
            }
            return true;
        }
 
        public static bool UninstallMe()
        {
            try
            {
                ManagedInstallerClass.InstallHelper(
                    new string[] { "/u", _exePath } );
            }
            catch
            {
                return false;
            }
            return true;
        }
    }
}
 
2. The main() code is given later in this article, again should i put this in my windows service project
if i put that in service project then should i delete the prevoius code
 
ServiceBase[] ServicesToRun;
                ServicesToRun = new ServiceBase[] 
                        { 
                            new WindowsService1.Service1() 
                        };
                ServiceBase.Run(ServicesToRun);
 

plz help me i very upset
GeneralDifferent SolutionmemberDavid Hoyt13 Aug '09 - 6:56 
I had my own solution back in 2005 for this: Self installing .NET service using the Win32 API[^]
 
It uses the native API and the latest versions (available through CodePlex as part of a greater project: http://www.codeplex.com/aspnetSuite[^]) support UAC and 32/64 bit support on Windows, Vista, and beyond. It will auto-detect if it's not installed and attempt to install it. You can also provide command line options to install, uninstall, etc. It also provides install/uninstall/start/stop/etc. utils for any service on the system (each w/ UAC support, if needed) and will automatically degrade to a typical console-based application if running in user interactive mode. It also provides more control than the .net framework-provided one in terms of what messages you can respond to (e.g. system shutdown, power states, etc.), your error handling capabilities, etc.
 
You can write an entire service w/ all of that functionality w/ ~2 methods.
Generalno console outputmemberrapid2k211 Aug '09 - 15:52 
After trying your suggestions inside a windows service I'm programming, there is no console output. Surely it has something to do with the type of the project (window service instead of console application), but I don't know what to change in the project properties to get something. Of course, if I start with a console appl I get no service Smile | :) Confused | :confused:
 
pd.- by console output I mean everything that gets out from system.console.writeline(), for example.
 
Thanks in advance for any suggestion...
GeneralRe: no console outputmemberErik Burger29 Jul '10 - 1:50 
Royally late but yes, it does have to do with the project type. If you go to Properties, Application and change the Output type to 'Console Application' you'll get the output you're missing.
Reverse Alchemy - http://www.reversealchemy.net

GeneralAwesome! GUI and CLI!memberBit-Smacker3 Aug '09 - 9:39 
I combined the enhancements by Aleksei Nickolayev and Ashley van Gerven, and now have my services accepting command line switches and offering to install or uninstall if launched interactively. This is the best! No more batch files!
 
Thanks for a great article, and thanks to the other users for the great enhancement ideas!
 
How could anyone not vote 5? Cool | :cool:
GeneralManagedInstallerClass doesn't work to mememberBizancio Spirit9 Jun '09 - 4:10 
I have a problem Frown | :( I can install a Windows service by InstallUtil but I can't do it when I try to do it with ManagerInstallerClass. It's strange, I reflected the Code (is the same that InstallUtil) and it doesn't work. Context data? This is My Code:
 
static void Main(string[] args)
{
string path = @"";
 
MainK(new string[] { path });
}
 
public static int MainK(string[] args)
{
Thread.CurrentThread.CurrentUICulture = CultureInfo.CurrentUICulture.GetConsoleFallbackUICulture();
if (((Console.OutputEncoding.CodePage != 0xfde9) && (Console.OutputEncoding.CodePage != Thread.CurrentThread.CurrentUICulture.TextInfo.OEMCodePage)) && (Console.OutputEncoding.CodePage != Thread.CurrentThread.CurrentUICulture.TextInfo.ANSICodePage))
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
}
Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), true);
try
{
ManagedInstallerClass.InstallHelper(args);
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
return -1;
}
return 0;
}
GeneralAwesome post, thanks. Always wanted to know how that was done.memberBuddy Stein1 Apr '09 - 7:11 
Awesome post, thanks. Always wanted to know how that was done.
GeneralInstallHelper Doesn't release Service1.exe file after uninstallmembersurf uk22 Jan '09 - 2:55 
Hi,
 
Excellent article! I have found non-command ways to do all things service related, apart from uninstalling...
 
I have created a helper service (Service1Updater) for my main service (Service1). Service1Updater does two things
-Checks a network folder to see if there's any updates for Service1 and installs them
-Checks to see if Service1 is running, if not then it tries to start it
 
The reason I'm using a service to do the updating:
-I don't want Service1 to fail when updating, I don't want to manually fix things
-I don't want any user interaction during updates
-I have a large cluster of computers to roll out the updates, I can't do it manually would take ages
-I want Service1 to stay alive if something unforeseen happens, so I use a helper process
 
The problem:
I use Service1Updater to copy new files -> stop Service1 -> uninstall Service1 -> delete old Service1 files -> copy new files -> install Service1 -> set Service1 properties -> start Service1.
 
Between "uninstall Service1 -> delete old Service1 files" I always get Access denied or UnauthorizedAccessException as the InstallHelper seams to still have a handle open on the old Service1. If I use a Process command then I have no problem deleteing the old files:
 
Process myProcess = new Process();
string path = @"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727";
myProcess.StartInfo.FileName = path + "\\InstallUtil.exe";
myProcess.StartInfo.Arguments = @"/u C:\Service1\Service1.exe";
myProcess.StartInfo.CreateNoWindow = true;
myProcess.Start();
 
myProcess.WaitForExit(60000);
if (!myProcess.HasExited)
    myProcess.Kill();
myProcess.Close();
 
Although this works it would be nice to have an InstallHelper solution especially as InstallUtil maybe in a different location. Does anyone have any ideas? Would I have to host the InstallHelper in a different thread/process and wait for it to exit?
 
Cheers
 
Ross
 
not suitable for idiots

GeneralRe: InstallHelper Doesn't release Service1.exe file after uninstallmemberHJG0076 Feb '09 - 1:27 
Hi Ross,
I have the same problem and didn't find any solution for this Frown | :-(
 
Regards
Jürgen
GeneralRe: InstallHelper Doesn't release Service1.exe file after uninstallmemberVeener29 Apr '09 - 13:56 
Hi Guys,
 
I ran into this problem and found a solution by running the uninstall method in another AppDomain similar to the way it is documented here.
http://www.devnewsgroups.net/group/microsoft.public.dotnet.framework/topic48232.aspx[^]
 
Not only does it put a lock on the file when you uninstall it, also, if you check to see if it is a installable service (using AssemblyInstaller.CheckIfInstallable) then you cannot delete the file either unless you use this method.
 
Enjoy...
General[Message Removed]memberKatekortez25 Oct '08 - 9:30 
Spam message removed
QuestionSetting ServiceProcessInstaller attributesmemberChris Newton28 May '08 - 2:45 
Hi, would you mind providing an example of how you would use the command line switches to set properties of the underlying ServiceProcessInstaller object.
 
Thanx...
GeneralFor the console lovers ;-)memberAleksei Nickolayev3 Mar '08 - 4:06 
First of all, THANK YOU for your article. It is really useful.
 
I have one little improvement. In case of your code user must know what switches service executable accepts. I suppose it's a good idea to show all acceptable switches to user when the service was started from console, not from SCM.
The method that I use to determine how service was started is quite simple — checking the value of Environment.UserInteractive.
 
Code:
        static void Main(string[] args)
        {
            // Started by user
            if (Environment.UserInteractive)
            {
                // Show version information
                Console.Write(Environment.NewLine + System.Reflection.Assembly.GetExecutingAssembly().FullName + ".");
 
                // Parsing command line
                if (args != null && args.Length >= 1)
                {
                    if (args[0].ToLower() == "/i")
                    {
                        // InstallHelper with /u switch set adds CRLF here, but with /i switch set it does not. So...
                        Console.WriteLine();
                        
                        installMyService();
                        return;
                    }
                    if (args[0].ToLower() == "/u")
                    {
                        uninstallMyService();
                        return;
                    }
                }
 
                // Show usage
                Console.WriteLine(Environment.NewLine + Environment.NewLine + "Usage: MyService.exe [/i | /u]" + Environment.NewLine + Environment.NewLine + "Where:");
                Console.WriteLine("       /i - install service;");
                Console.WriteLine("       /u - uninstall service.");
            }
            // Started by SCM
            else
            {
                ServiceBase.Run(new MyService());
            }
        }
 
        static void installMyService()
        {
            try
            {
                // "/LogFile=" - to suppress install log creation
                ManagedInstallerClass.InstallHelper(new string[] { "/LogFile=", Assembly.GetExecutingAssembly().Location });
            }
            catch
            {
            }
        }
 
        static void uninstallMyService()
        {
            try
            {
                // "/LogFile=" - to suppress uninstall log creation
                ManagedInstallerClass.InstallHelper(new string[] { "/u", "/LogFile=", Assembly.GetExecutingAssembly().Location });
            }
            catch
            {
            }
        }

GeneralRe: For the console lovers ;-)memberW. Kevin Hazzard6 Mar '08 - 6:51 
Very nice, Aleksei. Nice addition.
 
Thanks,
 

GeneralGreat articlemembersrinath g nath30 Dec '07 - 18:09 
Great article
GeneralInstall on first run [modified]memberAshley van Gerven21 Dec '07 - 17:55 
Firstly, very nice simple solution - thanks for sharing that tip. I decided to try it out and came up with the idea of installing the service if it's not already installed (i.e. just double click it in Explorer, and confirm whether or not to install it). Worked well for me, using this code:
 
if (args.Length == 0)
{
	ServiceController sc = new ServiceController("AshFMS2");
	try
	{
		string s = sc.Status.ToString();  // throws exception if service does not exist
	}
	catch
	{
		if (System.Windows.Forms.MessageBox.Show("Install this service?          ", "Confirm", System.Windows.Forms.MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes)
		{
			SelfInstaller(SelfInstallOptions.Install);
			return;
		}
	}
}
else
{
	switch (args[0])
	{
		case "-i" :
		case "/i" :
			SelfInstaller(SelfInstallOptions.Install);
			return;
		case "-u" :
		case "/u" :
			SelfInstaller(SelfInstallOptions.Uninstall);
			return;
	}
}
 
BTW SelfInstaller is my method containing code based on yours.
 
------------------------------
My Latest CP article: SmartPager - a Flickr-style pager control with go-to-page popup layer.
modified on Saturday, December 22, 2007 12:01:11 AM

GeneralRe: Install on first runmemberW. Kevin Hazzard26 Dec '07 - 3:54 
Very good, Ashley. Nice extension of the concept. It would be interesting to make
your code detect the version of the installed service and upgrade it on demand.
 

GeneralRe: Install on first runmemberAshley van Gerven26 Dec '07 - 4:13 
I think if you stop the service you can replace the .exe no problem.
 
cheers
Ashley
 

"For fifty bucks I'd put my face in their soup and blow." - George Costanza

CP article: SmartPager - a Flickr-style pager control with go-to-page popup layer.


GeneralCan't start service.memberRicky27 Nov '07 - 14:09 
..When I try to start the command line installed service from the Computer Management tool,
I get a Services message box (!), quoting "Could not start the MyCLI_Service on Local Computer. Error 3: The system cannot find the path specified." Huh?
..The CLI Install appears to have gotten the correct path, so what gives?
..The Uninstall appears to work. (I have done it several times.Wink | ;-)
..The original project was setup as a Windows Service project and the solution included a setup/deployment project which got the service into the Services list of the Computer Management tool. This original version of the service could be started, paused (required a property setting change), and stopped.
 
Look sharp, be sharp, see sharp!

GeneralRe: Can't start service.memberW. Kevin Hazzard29 Nov '07 - 10:00 
I suppose I would start by looking around in the registry at HKLM\System\CurrentControlSet\Services to see how your service got registered. Is the ImagePath value OK for your service?
 

GeneralRe: Can't start service.memberRicky30 Nov '07 - 12:44 
The ImagePath appears okay. However, I am using a fake drive/path, that is, I have substituted a C: path with J: drive. I will try placing all the files in a real path on the C: drive. Will update results. Thanks for feedback. Ricky Confused | :confused:
 
Look sharp, be sharp, see sharp!

GeneralRe: Can't start service.memberRicky30 Nov '07 - 12:54 
It works!!! No substituted drive pathing allowed. *.msi files have similar problems.
Now I can breathe and thank you for a great article.
Ricky Big Grin | :-D
 
Look sharp, be sharp, see sharp!

GeneralI took a different tack [modified]memberPIEBALDconsult26 Nov '07 - 16:12 
The EXE takes command line parameters. If the first parameter is "INSTALL" then it does:
 
System.Diagnostics.Process.Start
(
    "INSTALLUTIL"
,
    "\"" + System.Windows.Forms.Application.ExecutablePath + "\""
) ;
 
With similar code for UNINSTALL. START and STOP can also be done from the command line.
 
(I chose this method when I wrote my first Windows service three years ago and haven't revisited the code since, I guess I'll have to now.)
 

 
-- modified at 9:33 Thursday 29th November, 2007
 
SWEET! Thanks!
GeneralRe: I took a different tackmembercplas14 Jan '09 - 14:04 
The whole point of this article is NOT to use InstallUtil.
GeneralYou can go further (an alternative approach)memberOleg Shilo26 Nov '07 - 15:18 
Good job.
 
I do agree with all your reasons for improving standard service installation approach. Using InstallUtil.exe in the way MS intended is a pain. I think your approach is great however you can go even further (and in "more standard" way) by directly addressing the issue of "not knowing" InstallUtil.exe path. After all you have already guessed where to get help - Reflection.
 
The following code represents complete solution for service self-installation. It uses InstallUtil.exe thus all its functionality (e.g. logging to file) is still available.
 
I've been using this approach for more than a year and I had no problems with it. It is no surprise as it is exactly the way of service installation, which is regarded as a standard service installation. I just use a couple of tricks to get path information at runtime and one of these tricks is the same as yours.
 
BTW AppDomain.ExecuteAssembly even allows you to see InstallUtil.exe console output as yours.
Also note that InstallUtil.exe is executed in the same process space as the service application so no new (external) process is started.

 
string installUtil = Path.Combine(Path.GetDirectoryName(typeof(string).Assembly.Location), 
                                  "InstallUtil.exe");

AppDomain dom = AppDomain.CreateDomain("temp");

if (args[0].ToLower() == "/i")
{
    dom.ExecuteAssembly(installUtil, null, new string[] { 
        "/LogFile="
        "/LogToConsole=false"
        Application.ExecutablePath });
}
else if (args[0].ToLower() == "/u")
{
    dom.ExecuteAssembly(installUtil, null, new string[] { 
        "/u"
        "/LogFile="
        "/LogToConsole=false"
        Application.ExecutablePath });
}

 
Cheers,
Oleg
GeneralRe: You can go further (an alternative approach)membertag200126 Nov '07 - 18:24 
launching an external exe is ALWAYS inferior to a direct API call. I'd go with the article author's approach long before these fragile "launch the command line tool" approach.
GeneralRe: You can go further (an alternative approach)memberOleg Shilo26 Nov '07 - 18:41 
>launching an external exe is ALWAYS inferior to a direct API call.
 
You have just missed the point. You ARE NOT launching an external exe. AppDomain loads the assembly in the same process space.
 
However I do recognise that you may feel that direct API call is better.
GeneralRe: You can go further (an alternative approach)memberOne Smart Motor Scooter27 Nov '07 - 6:46 
Oleg,
 
Thanks for sharing your idea as well. Personally, I have never used the AppDomain (inexperience with the namespace is the reason) however, this is a good example of the use.
GeneralRe: You can go further (an alternative approach)memberW. Kevin Hazzard29 Nov '07 - 9:55 
Thanks, Oleg. The best part about posting a new article is that I always seem to learn more than I teach. Smile | :)
 

GeneralDoesn't seem to work for memembersmesser23 Nov '07 - 20:49 
I added you suggestion to a service and when I use the -i command line argument the method
runs succesfully. However, the service never shows up in the services list and can't be started uing net start servicename.exe.
 
What am I missing?
 
This method doesn't give the service a name or tell is how to start ie manual auto
 
thanks
GeneralRe: Doesn't seem to work for mememberW. Kevin Hazzard24 Nov '07 - 4:21 
Two things to do, SMESSER:
 
1. Make sure that the output type of your service executable is a Console application.
2. When you run with the -i switch in a CMD console, you should see the output of the installer.
 
Is the installation rolling back for some reason? The ManagedInstallerClass does a transacted installation so if something goes wrong, you should see the reason in the console window. Look for the word "rollback" in the text.

 
Kevin
Kevin Hazzard's Brain Spigot
GeneralRe: Doesn't seem to work for me [modified]membersmesser24 Nov '07 - 4:36 
Update: I just reread your assumption. I thought that you were inplying that the service installer code was not needed with your method. I am sure once I add that it will work as expected.
 

First, just in case it makes a difference I am running on Vista.
I still don't see the installed service show up in the service manager.
 
Here are the contents of both log files.
 
1.Running a transacted installation.
 
Beginning the Install phase of the installation.
See the contents of the log file for the C:\WindowsService1\WindowsService1\bin\Debug\WindowsService1.exe assembly's progress.
The file is located at C:\WindowsService1\WindowsService1\bin\Debug\WindowsService1.InstallLog.
 
The Install phase completed successfully, and the Commit phase is beginning.
See the contents of the log file for the C:\WindowsService1\WindowsService1\bin\Debug\WindowsService1.exe assembly's progress.
The file is located at C:\WindowsService1\WindowsService1\bin\Debug\WindowsService1.InstallLog.
 
The Commit phase completed successfully.
 
The transacted install has completed.
 
2.Installing assembly 'C:\WindowsService1\WindowsService1\bin\Debug\WindowsService1.exe'.
Affected parameters are:
logtoconsole =
assemblypath = C:\WindowsService1\WindowsService1\bin\Debug\WindowsService1.exe
i =
logfile = C:\WindowsService1\WindowsService1\bin\Debug\WindowsService1.InstallLog
No public installers with the RunInstallerAttribute.Yes attribute could be found in the C:\WindowsService1\WindowsService1\bin\Debug\WindowsService1.exe assembly.
Committing assembly 'C:\WindowsService1\WindowsService1\bin\Debug\WindowsService1.exe'.
Affected parameters are:
logtoconsole =
assemblypath = C:\WindowsService1\WindowsService1\bin\Debug\WindowsService1.exe
i =
logfile = C:\WindowsService1\WindowsService1\bin\Debug\WindowsService1.InstallLog
No public installers with the RunInstallerAttribute.Yes attribute could be found in the C:\WindowsService1\WindowsService1\bin\Debug\WindowsService1.exe assembly.
Remove InstallState file because there are no installers.
 

Thanks
 

 
-- modified at 11:13 Saturday 24th November, 2007
GeneralRe: Doesn't seem to work for mememberW. Kevin Hazzard24 Nov '07 - 5:24 
Looks like you don't have a ProjectInstaller included in your project. Do you have code anywhere in the project that looks like this?
 
[RunInstaller( true )]
public partial class ProjectInstaller : Installer
{
public ProjectInstaller()
{
InitializeComponent();
}
}
 
If not, see the reference to Mr. Nasr's article in my text. He shows you how to include such an installer. This is what the InstallHelper locates in your executable to begin the installation. I ran a service install on Vista to be sure it wasn't a problem on that OS. Everything was OK for me. Hope this helps.
 

 

GeneralRe: Doesn't seem to work for memembersmesser24 Nov '07 - 6:08 
No, I don't. I realized this after re-reading the text of your article.
 
That is the problem.
 
Thanks
GeneralIndispensablemembermunenedumunenedu23 Nov '07 - 5:17 
One of those little gems that leaves me wondering , why didn't i think of Lutz Roeder'ing the damn installutil!! This changes many things, believe me.
 

I always knew that, one day I would wake up and decide to live forever or die in the attempt.
GeneralExcellent!memberT1TAN23 Nov '07 - 0:23 
Deploying services will finally stop being PITA!;P
 
Thanks for sharing with the community!Cool | :cool:
 
---
http://sprdsoft.cmar-net.org - We Sprd You Softly
Our site features contents and several images. All of this is very weird.
 
In the end, war is not about who's right, it's about who's left.

GeneralJust like Delphi does it.memberstensones22 Nov '07 - 23:33 
Excellent.
 
Nicely written article, very handy.
 
Having come to C# and .NET from Delphi, this "use a tool to install and uninstall your service" has always annoyed me. Services written in Delphi were automatically built with /install and /uninstall command line parsing to do exactly this, and frankly I missed that. Now you've shown me how to regain that functionality I will be putting that in all my services from now on.
 
Thanks Wink | ;) . (you got my 5)
GeneralExcellent article!!!memberndinges22 Nov '07 - 22:52 
i was waiting for something like this for years Wink | ;-)
GeneralGreat ArticlememberCool Dirty Boy22 Nov '07 - 21:48 
I give my 5 vote Smile | :)
GeneralGreat articlememberNikoTanghe22 Nov '07 - 21:43 
Great article !
 
just what I need... now I don't have to distribute the installutil tool anymore...
 
you got my 5
 
<< Nearly all men can stand adversity, but if you want to test a man's character, give him power. >>

GeneralExcellent! [modified]memberTheCardinal22 Nov '07 - 21:34 
Thanks for sharing this!
Smile | :)
 


GeneralGreat !!!memberGismow22 Nov '07 - 20:57 
Thanx for this article !!!!
 
I hate the InstallUtil.exe Cool | :cool:
 
Greetinx
gismow
Generalgood articlemembermargiex22 Nov '07 - 19:14 
Smile | :)

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 22 Nov 2007
Article Copyright 2007 by W. Kevin Hazzard
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid