开发者

How to make a Windows service with parameters?

I have written a Windows service, of which I want to have 1 instance running per customer. This is because the customers each have their own DB with identical schemas; the only difference between the Windows services is that they will each have a different parameter corresponding to the customer DB that they're designated to serve. (And I can't have one service with multiple worker threads, because the DB connection uses a static variable, which I can't fiddle with across threads.)

I found this neat little tutorial about how to make a Windows Service, but it only shows me how to set it up for a single service. I want to set up n instances of the service, each one with a display name that includes the customer name, running with the command line parameter that denotes the customer ID.

The tutorial linked above has a class called MyWindowsServiceInstaller, which installs the windows service on the local system, and I'm guessi开发者_如何学运维ng this would be a logical place to set up a foreach loop through all my customers, setting up one service for each. But I can't see anywhere on the interfaces provided that would allow me to set up a command line parameter for the new service.

How do you do it?


All I wanted was to send one parameter to the service I have created. As it turns out, all you have to do is (carefully!) edit the registry at HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ and add the parameter in ImagePath, after the quotes.

Eg. ImagePath Value Data: "C:\Program Files\myservice\myservice.exe" param1

I found the solution in this link http://social.msdn.microsoft.com/Forums/is/csharpgeneral/thread/38242afa-7e40-4c06-975e-aa97d3cc782f


Wil Peck wrote a good article about how to install multiple instances of a windows service on a single box. The basic idea is that you have to trick the installer into thinking they are different services by giving them different names.

Having said that, it seems like it would be easier (and more maintainable) to redesign your database connection code so that it can support multiple worker threads.


You basically need to install the service several times, and customise it with it's exe.config file.

Alternatively, you can have one service that runs different worker threads for each client.

Update

exe.Config is an Application Configuration File

I have no idea how to use that installer component to install several instances of the service, I wasn't aware you could.

Where we need several instances of one of our services to run on one machine, we actually only install it once, then literally copy the installed folder and change the exe name for the second instance. The second instance is then configured in it's own Application Configuration File.


As far as I known it is impossible to provide startup parameters using either ServiceInstaller, ServiceProcessInstaller or installutil. However, it is possible to provide startup parameters using some COM api's from advapi.dll (check the left menu). A complete collection of the required calls can be found here. It's a class (also) called ServiceInstaller that contains the required external methods and some utility methods.

You'd want to use the utility method InstallAndStart. It accepts a service name, a display name and a path to the executable that represents your Windows service. You can call it like this:

InstallAndStart("MyService", "My Service For User 1",
                "c:\\pathtoexe\MyService.exe user1");

If you have the following service the parameter startupParam will receive the value user1.

class Program : ServiceBase
{
    private string startupParam;

    static void Main(string[] args)
    {
        string arg = args[0];
        ServiceBase.Run(new Program(arg));
    }

    public Program(string startupParam)
    {
        this.ServiceName = "MyService";
        this.startupParam = startupParam;
    }
    ...
}


You can pass parameters to your installer using installutil, for example ServiceName and DisplayName.

ProjectInstaller.cs

public partial class ProjectInstaller : Installer
{
    protected override void OnBeforeInstall(IDictionary savedState)
    {
        SetServiceName();
        base.OnBeforeInstall(savedState);
    }

    protected override void OnBeforeUninstall(IDictionary savedState)
    {
        SetServiceName();
        base.OnBeforeUninstall(savedState);
    }

    private string AppendParameter(string path, char parameter, string value)
    {
        if (!path.StartsWith("\""))
            path = $"\"{path}\"";

        if (value.Contains(" "))
            value = $"\"{value}\"";

        return $"{path} -{parameter}{value}";
    }

    private void SetServiceName()
    {
        if (Context.Parameters.ContainsKey("ServiceName"))
            serviceInstaller.ServiceName = Context.Parameters["ServiceName"];

        if (Context.Parameters.ContainsKey("DisplayName"))
            serviceInstaller.DisplayName = Context.Parameters["DisplayName"];

        Context.Parameters["assemblypath"] = AppendParameter(Context.Parameters["assemblypath"], 's', serviceInstaller.ServiceName);
    }
}

This will append a parameter to the path stored with the service, for example:

Before: "C:\Service.exe"

After: "C:\Service.exe" -s"Instance 1"

You can then read this parameter when you start the service and pass to your services constructor.

Program.cs

static void Main(string[] args)
{
    string serviceName = args.Single(x => x.StartsWith("-s")).Substring("-s".Length);

    ServiceBase service = new Service(serviceName);
    ServiceBase.Run(service);
}

Service.cs

public partial class Service : ServiceBase
{
    public Service(string serviceName)
    {
        InitializeComponent();

        ServiceName = serviceName;
    }
}

Usage

installutil /ServiceName="Instance 1" /DisplayName="Instance 1 Service" "C:\Service.exe"
installutil /ServiceName="Instance 2" /DisplayName="Instance 2 Service" "C:\Service.exe"
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜