Sometimes there are advantages to installing a service using scripting rather than by creating a MSI installer package. The MSI Windows Installer puts your dlls in the GAC, so that they become machine-global and you have to correctly manage version numbers (or make sure dlls are backward compatible) to avoid version conflict problems. If you're still testing, this can be a huge pain.
The first step to creating your own installer (after writing your Service, obviously) is to create a batch file that calls the .NET InstallUtil.exe utility. InstallUtil invokes the ProjectInstaller module in your assembly. For installation, InstallUtil monitors all installation steps and rolls back the installation if an error occurs. For uninstalling, InstallUtil runs the Uninstall code in your ProjectInstaller module.
A sample batch file:
@echo off
echo Installing service...
echo.
%WINDIR%\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe SagemMorphoInc.SMTP.exe
echo.
pause
InstallUtil takes the following extra parameters:
| Option | Description |
|---|---|
| /h[elp] or /? | Displays command syntax and options for the tool. |
| /help assemblypath or /? assemblypath | Displays any additional options recognized by individual installers within the specified assembly. |
| /LogFile=[filename] | Specifies the name of the log file where install progress is recorded. The default is assemblyname.InstallLog. |
/AssemblyName assemblyName
| Specifies the name of an assembly. The assembly name must be surrounded by quotes and fully qualified with the version, culture, and public key token of the assembly. For example, "myAssembly, Culture=neutral, PublicKeyToken=0038abc9deabfle5, Version=2.0.0.0" is a fully qualified assembly name. |
| /LogToConsole={true|false} | If true, displays output to the console. If false (the default), suppresses output to the console. |
| /ShowCallStack | Prints the call stack to the log if an exception occurs at any point during installation. |
| /u[ninstall] | Uninstalls an assembly. Unlike other options, /u applies to all assemblies regardless of where it appears on the command line. |
If you try to run this script now, without adding a ProjectInstaller to your service, you'll see the following error message:
No public installers with the RunInstallerAttribute.Yes attribute could be found in the assembly.
Remove InstallState file because there are no installers.
Since you don't have an installer associated with your service, InstallUtil doesn't know what to do.
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.User;
this.serviceProcessInstaller1.Password = "password";
this.serviceProcessInstaller1.Username = "userName";
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalService;
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
Once you have an installer class in your service project you should be able to install your service using the InstallUtil.exe utility.
If you've added an Installer to your project, but still get an error like the one below when you run the above batch file or use InstallUtil.exe at a command line:
No public installers with the RunInstallerAttribute.Yes attribute could be found in the C:\Projects\SagemMorphoInc.SMTP.exe assembly.
Remove InstallState file because there are no installers.
One thing to check is that you're using the correct version of InstallUtil.exe. There is a copy in each version of the .NET framework, and depending on how your paths are set a previous version might be being called. If you've compiled your service using the 2.0 Framework, you won't be able to use the 1.0 or 1.4 InstallUtil utility.
Check the line in your log that looks somewhat like this:
Microsoft (R) .NET Framework Installation utility Version 1.0.3705.6018
Copyright (C) Microsoft Corporation 1998-2001. All rights reserved.
to ensure that you're using the right version of InstallUtil. If it's not correct, either set your Windows environment path to use the correct framework version for your service, or simply type the full path to the version of InstallUtil.exe that you need to use.
For a .Net 1.1 assembly...
C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\InstallUtil.exe
Or
For a .Net 2.0 assembly...
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe
Now assuming that you did not get any errors when installing, you need to start your service, since InstallUtil.exe will not start your service by default.
Open the Windows Services menu in
Control Panel -> Administrative Tools, or type services.msc into the Start -> Run box or at a DOS command window.
In the Windows Services menu, find your service in the alphabetic list, right-click, and select "Start".
If you don't know what your service
is named, check your ProjectInstaller source code for a line like the following (it's created by default when you
add an Installer to your project):
this.serviceInstaller1.ServiceName = "QueueListenerService";
The service in the code above will appear in the Windows Service menu as "QueueListenerService".
You can also start a service directly using the command net start followed by the service's name, for example
net start QueueListenerService. This can be added to your batch install file after you install the service.
If, when you start your service, you get a pop-up saying that your service was "started and then stopped. Some services stop automatically when they have no work to do, for example Performance Logs and Alert services", first ensure that your service isn't suppose to stop automatically.

If your service isn't suppose to automatically stop, then most likely you are getting an exception in your
OnStart event code. Windows services do not handle exceptions
well during initialization. It is best to avoid any complex code in your startup routine.
Also, I have found it very helpful to have a logging method in my services to help troubleshoot problems or
track the activity of the service.
Here is an example of a log file method.
// Writes specified text to the log file.
private void WriteToLog( string TextToLog ) {
System.IO.StreamWriter sw = null;
// Define log file path and name.
string CurrentLogFilePath = AppDomain.CurrentDomain.BaseDirectory
+ @"\Log.txt";
sw = new System.IO.StreamWriter( CurrentLogFilePath, true );
// Write data to log file.
sw.WriteLine( TextToLog );
sw.Flush();
sw.Close();
}
Here is an example of how to use the WriteToLog function. Note that even though the "LoadSettings" procedure is enclosed in a Try/Catch block, the service will still fail to start if an exception occurs in this routine. Like I said before, Windows services do not tolerate exceptions during their initialization. At least this way the error will be logged.
protected override void OnStart( string[] args ) {
// Record service startup to log file.
WriteToLog( "========= " + DateTime.Now + " =========" );
WriteToLog( "Service is starting." );
try {
// Load settings from config file.
LoadSettings();
WriteToLog("Settings loaded from configuration file.");
} catch ( Exception ex ) {
WriteToLog( "ERROR: " + ex.Message
+ Environment.NewLine + ex.StackTrace );
}
}