C# 如何使用参数创建Windows服务?

C# 如何使用参数创建Windows服务?,c#,windows-services,C#,Windows Services,我已经编写了一个Windows服务,我希望每个客户运行一个实例。这是因为每个客户都有自己的具有相同模式的数据库;Windows服务之间的唯一区别在于,它们各自具有一个不同的参数,对应于它们指定服务的客户数据库。(我不能让一个服务有多个工作线程,因为DB连接使用了一个静态变量,我不能跨线程处理它。) 我了解了如何创建Windows服务,但它只向我展示了如何为单个服务设置它。我想设置n个服务实例,每个实例都有一个包含客户名称的显示名称,并使用表示客户ID的命令行参数运行 上面链接的教程有一个名为My

我已经编写了一个Windows服务,我希望每个客户运行一个实例。这是因为每个客户都有自己的具有相同模式的数据库;Windows服务之间的唯一区别在于,它们各自具有一个不同的参数,对应于它们指定服务的客户数据库。(我不能让一个服务有多个工作线程,因为DB连接使用了一个静态变量,我不能跨线程处理它。)

我了解了如何创建Windows服务,但它只向我展示了如何为单个服务设置它。我想设置n个服务实例,每个实例都有一个包含客户名称的显示名称,并使用表示客户ID的命令行参数运行

上面链接的教程有一个名为
MyWindowsServiceInstaller
的类,它在本地系统上安装windows服务,我猜这是一个合理的地方,可以通过我的所有客户设置
foreach
循环,为每个客户设置一个服务。但是我在提供的接口上看不到任何允许我为新服务设置命令行参数的地方


您是如何做到的?

您基本上需要多次安装该服务,并使用其exe.config文件对其进行自定义

或者,您可以有一个服务为每个客户端运行不同的工作线程

更新

installutil /ServiceName="Instance 1" /DisplayName="Instance 1 Service" "C:\Service.exe"
installutil /ServiceName="Instance 2" /DisplayName="Instance 2 Service" "C:\Service.exe"
exe.Config是一个

我不知道如何使用该安装程序组件来安装该服务的多个实例,我不知道您可以


当我们需要一个服务的多个实例在一台机器上运行时,我们实际上只安装一次,然后复制安装的文件夹并更改第二个实例的exe名称。然后,第二个实例在它自己的应用程序配置文件中进行配置。

据我所知,使用
ServiceInstaller
ServiceProcessInstaller
installutil
都无法提供启动参数。但是,可以使用来自的某些COM api提供启动参数(检查左侧菜单)。可以找到所需调用的完整集合。它是一个名为
ServiceInstaller
的类,包含所需的外部方法和一些实用方法

您需要使用实用程序方法
InstallAndStart
。它接受服务名称、显示名称和表示Windows服务的可执行文件的路径。你可以这样称呼它:

InstallAndStart("MyService", "My Service For User 1",
                "c:\\pathtoexe\MyService.exe user1");
如果您有以下服务,参数
startupParam
将接收值
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;
    }
    ...
}
关于如何在单个机箱上安装windows服务的多个实例。基本的想法是,你必须给安装程序起不同的名字,让他们认为它们是不同的服务


话虽如此,重新设计数据库连接代码以使其能够支持多个工作线程似乎更容易(也更易于维护)。

我只想向我创建的服务发送一个参数。 事实证明,您只需(小心地!)在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\编辑注册表,并在ImagePath中的引号后添加参数

例如,ImagePath值数据:“C:\Program Files\myservice\myservice.exe”参数1


我在这个链接中找到了解决方案

您可以使用
installutil
将参数传递给安装程序,例如
ServiceName
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);
    }
}
static void Main(string[] args)
{
    string serviceName = args.Single(x => x.StartsWith("-s")).Substring("-s".Length);

    ServiceBase service = new Service(serviceName);
    ServiceBase.Run(service);
}
public partial class Service : ServiceBase
{
    public Service(string serviceName)
    {
        InitializeComponent();

        ServiceName = serviceName;
    }
}
这将向服务存储的路径附加一个参数,例如:

在“C:\Service.exe”之前

在“C:\Service.exe”-s“实例1”之后

然后,您可以在启动服务并传递给服务构造函数时读取此参数

Program.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);
    }
}
static void Main(string[] args)
{
    string serviceName = args.Single(x => x.StartsWith("-s")).Substring("-s".Length);

    ServiceBase service = new Service(serviceName);
    ServiceBase.Run(service);
}
public partial class Service : ServiceBase
{
    public Service(string serviceName)
    {
        InitializeComponent();

        ServiceName = serviceName;
    }
}
Service.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);
    }
}
static void Main(string[] args)
{
    string serviceName = args.Single(x => x.StartsWith("-s")).Substring("-s".Length);

    ServiceBase service = new Service(serviceName);
    ServiceBase.Run(service);
}
public partial class Service : ServiceBase
{
    public Service(string serviceName)
    {
        InitializeComponent();

        ServiceName = serviceName;
    }
}
用法

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

在我上面链接的教程中,有一个名为MyWindowsServiceInstaller的类,它负责安装工作。它看起来应该是为所有客户设置循环的地方——但我根本看不到为每个服务设置命令行参数的地方。如何设置参数?请原谅我的无知,但是什么是exe.config?我更新了我的问题,以澄清您提到的一些要点。嗯。。。刚刚尝试过这个,但看起来installutil不允许您传递参数。。。?我是不是漏掉了什么?嗯,看来我错了。很抱歉,忘了installutil部分。我自己使用一个自定义ServiceInstaller类,它使用COM api来启动和停止服务。这个类允许传递参数,我认为installutil也会这样做。我将更新我的答案…Ronald,我有一个客户端需要Windows服务使用系统帐户在系统上执行操作,因为我的客户端没有权限。如果我想让客户端告诉Windows服务该做什么,我是否应该每次都停止和启动,通过像这样传递参数?我发现了OnCustomCommand特性,但它没有用,因为您只能传递整数。或者我应该使用数据库或注册表项根据正在运行的客户端临时设置参数?在这种情况下,我将使用WCF公开Windows服务上可以接收客户端命令的终结点。我正在删除我的答案,“有人比我更了解这件事。”二元担忧者-我希望你没有删除你的答案!这里有一些非常有希望的想法…回答未删除:我将其标记为CW,它不完整,我们在某个地方有一张备忘单,上面有“如何”说明,可以让同一服务的多个实例运行,但我找不到它,也没有时间挖掘,抱歉Shaul。+1用于链接,+1 ag