|
|||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThe usual way to install a Windows service is to use an MSI install file. In fact, using an MSI is only one of several ways to install a Windows service. In some particular situations, a Windows service may be needed to install and uninstall many times. Someone might even think of a way to "install it with a programming language" instead of clicking on MSI or running the This article will explain how to install a Windows service in a smart way. In addition, the installer class has been extended to support both the usual installation way with MSI or BackgroundI came up with the idea to deliver this solution when we made an automated integration testing system in which there are executables and Windows services that have to be started before doing testing. The process of installing, uninstalling, starting and stopping executables and Windows services must be automatic. The only way to do that is for our testing system to be able to install and uninstall Windows services instead of using MSI or I tried to look for any available C# or even VB.NET coding class from the Internet that could help me solve this problem, but that was useless. Luckily, I found an interesting article submitted by gtamir. What he did was actually just change the installer class and create a BAT file, replacing the DynamicInstallerThe .NET SDK InstallUtil.exe utility invokes the
using System;
using System.Collections;
using System.Configuration.Install;
using System.ServiceProcess;
using System.ComponentModel;
[RunInstallerAttribute(true)]
public class ProjectInstaller : Installer{
private ServiceInstaller serviceInstaller1;
private ServiceInstaller serviceInstaller2;
private ServiceProcessInstaller processInstaller;
public MyProjectInstaller(){
// Instantiate installers for process and services.
processInstaller = new ServiceProcessInstaller();
serviceInstaller1 = new ServiceInstaller();
serviceInstaller2 = new ServiceInstaller();
// The services run under the system account.
processInstaller.Account = ServiceAccount.LocalSystem;
// The services are started manually.
serviceInstaller1.StartType = ServiceStartMode.Manual;
serviceInstaller2.StartType = ServiceStartMode.Manual;
// ServiceName must equal those on ServiceBase derived classes.
serviceInstaller1.ServiceName = "Hello-World Service 1";
serviceInstaller2.ServiceName = "Hello-World Service 2";
// Add installers to collection. Order is not important.
Installers.Add(serviceInstaller1);
Installers.Add(serviceInstaller2);
Installers.Add(processInstaller);
}
}
Since the
Therefore
Because using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
using System.ServiceProcess;
using Microsoft.Win32;
namespace ServiceInstaller
{
/// <summary>
/// This is a custom project installer.
/// Applies a unique name to the service using the /name switch
/// Sets user name and password using the /user and /password switches
/// Allows the use of a local account using the /account switch
/// </summary>
[RunInstaller(true)]
public class DynamicInstaller : Installer
{
public string ServiceName
{
get { return serviceInstaller.ServiceName; }
set { serviceInstaller.ServiceName = value; }
}
public string DisplayName
{
get { return serviceInstaller.DisplayName; }
set { serviceInstaller.DisplayName = value; }
}
public string Description
{
get { return serviceInstaller.Description; }
set { serviceInstaller.Description = value; }
}
public ServiceStartMode StartType
{
get { return serviceInstaller.StartType; }
set { serviceInstaller.StartType = value; }
}
public ServiceAccount Account
{
get { return processInstaller.Account; }
set { processInstaller.Account = value; }
}
public string ServiceUsername
{
get { return processInstaller.Username; }
set { processInstaller.Username = value; }
}
public string ServicePassword
{
get { return processInstaller.Password; }
set { processInstaller.Password = value; }
}
private ServiceProcessInstaller processInstaller;
private System.ServiceProcess.ServiceInstaller serviceInstaller;
public DynamicInstaller()
{
processInstaller = new ServiceProcessInstaller();
processInstaller.Account = ServiceAccount.LocalService;
processInstaller.Username = null;
processInstaller.Password = null;
serviceInstaller = new System.ServiceProcess.ServiceInstaller();
serviceInstaller.StartType = ServiceStartMode.Automatic;
serviceInstaller.ServiceName = "MyService";
serviceInstaller.DisplayName = "";
serviceInstaller.Description = "";
Installers.AddRange(new Installer[] {
processInstaller,
serviceInstaller});
}
#region Access parameters
/// <summary>
/// Return the value of the parameter in dictated by key
/// </summary>
/// <PARAM name="key">Context parameter key</PARAM>
/// <returns>Context parameter specified by key</returns>
public string GetContextParameter(string key)
{
string sValue = "";
try
{
sValue = this.Context.Parameters[key].ToString();
}
catch
{
sValue = "";
}
return sValue;
}
#endregion
/// <summary>
/// This method is run before the install process.
/// This method is overridden to set the following parameters:
/// service name (/name switch)
/// account type (/account switch)
/// for a user account user name (/user switch)
/// for a user account password (/password switch)
/// Note that when using a user account,
/// if the user name or password is not set,
/// the installing user is prompted for the credentials to use.
/// </summary>
/// <PARAM name="savedState"></PARAM>
protected override void OnBeforeInstall(IDictionary savedState)
{
base.OnBeforeInstall(savedState);
bool isUserAccount = false;
// Decode the command line switches
string name = GetContextParameter("name").Trim();
if (name != "")
{
serviceInstaller.ServiceName = name;
}
string desc = GetContextParameter("desc").Trim();
if (desc != "")
{
serviceInstaller.Description = desc;
}
// What type of credentials to use to run the service
string acct = GetContextParameter("account");
switch (acct.ToLower())
{
case "user":
processInstaller.Account = ServiceAccount.User;
isUserAccount = true;
break;
case "localservice":
processInstaller.Account = ServiceAccount.LocalService;
break;
case "localsystem":
processInstaller.Account = ServiceAccount.LocalSystem;
break;
case "networkservice":
processInstaller.Account = ServiceAccount.NetworkService;
break;
}
// User name and password
string username = GetContextParameter("user").Trim();
string password = GetContextParameter("password").Trim();
// Should I use a user account?
if (isUserAccount)
{
// If we need to use a user account,
// set the user name and password
if (username != "")
{
processInstaller.Username = username;
}
if (password != "")
{
processInstaller.Password = password;
}
}
}
/// <summary>
/// Uninstall based on the service name
/// </summary>
/// <PARAM name="savedState"></PARAM>
protected override void OnBeforeUninstall(IDictionary savedState)
{
base.OnBeforeUninstall(savedState);
// Set the service name based on the command line
string name = GetContextParameter("name").Trim();
if (name != "")
{
serviceInstaller.ServiceName = name;
}
}
}//end class
}
Now it becomes easy to install a Windows service with a programming language by using WindowsServiceInstallUtil.cs. This class contains many options in constructors for convenient usage. The fully customized way to install a Windows service is by passing in every piece of information, such as //
// Installing windows service to start with username and password
//
//If install with local user:
//wsInstallInfo = new WindowsServiceInstallInfo
("MyService", "desc", Directory.GetCurrentDirectory(), "MyService.exe",
// WindowsServiceAccountType.User, @".\username", @"password");
//If install with network user:
//wsInstallInfo = new WindowsServiceInstallInfo
("MyService", "desc", Directory.GetCurrentDirectory(), "MyService.exe",
// WindowsServiceAccountType.User, @"networkdomain\username", @"password");
wsInstallInfo = new WindowsServiceInstallInfo
("MyService", "desc", Directory.GetCurrentDirectory(), "MyService.exe",
WindowsServiceAccountType.User, @".\username", @"");
wsInstallUtil = new WindowsServiceInstallUtil(wsInstallInfo);
//Log to see any error
//wsInstallUtil.Install(@"C:\test.txt");
result = wsInstallUtil.Install();
Console.WriteLine("Installed : " + result);
Console.WriteLine("Press any key to continue ...");
Console.ReadKey();
//Log to see any error
//wsInstallUtil.Uninstall(@"C:\test.txt");
result = wsInstallUtil.Uninstall();
Console.WriteLine("Uninstalled : " + result);
Console.WriteLine("Press any key to continue ...");
Console.ReadKey();
Points of interestPlease bear in mind that you can still install this Windows service assembly via MSI as usual. You can even create a *.bat file to execute the @echo off
set SERVICE_HOME=<service executable directory>
set SERVICE_EXE=<service executable name>
REM the following directory is for .NET 1.1, your mileage may vary
set INSTALL_UTIL_HOME=C:\WINNT\Microsoft.NET\Framework\v1.1.4322
REM Account credentials if the service uses a user account
set USER_NAME=<user account>
set PASSWORD=<user password>
set PATH=%PATH%;%INSTALL_UTIL_HOME%
cd %SERVICE_HOME%
echo Installing Service...
installutil /name=<service name>
/account=<account type> /user=%USER_NAME% /password=%
PASSWORD% %SERVICE_EXE%
echo Done.
The variables set at the top are for convenience only. You can certainly hardcode all information directly on the @echo off
set SERVICE_HOME=<service executable directory>
set SERVICE_EXE=<service executable name>
REM the following directory is for .NET 1.1, your mileage may vary
set INSTALL_UTIL_HOME=C:\WINNT\Microsoft.NET\Framework\v1.1.4322
set PATH=%PATH%;%INSTALL_UTIL_HOME%
cd %SERVICE_HOME%
echo Uninstalling Service...
installutil /u /name=<service name> %SERVICE_EXE%
echo Done.
History
| ||||||||||||||||||||||||||||