C# 使用Topshelf设置服务启动参数

C# 使用Topshelf设置服务启动参数,c#,topshelf,C#,Topshelf,我有一个具有多个实例的服务,每个实例的参数不同,目前我正在手动(准确地说是另一个代码)将这些参数设置为注册表中服务的映像路径(例如,HKEY\U LOCAL\u MACHINE\SYSTEM\CurrentControlSet\services\MyService$i00)。因此,我们的服务安装分两步完成 我非常有兴趣将这些步骤合并到Topshelf安装中,例如 MyService.exe install -instance "i00" -config "C:\i00Config.json"

我有一个具有多个实例的服务,每个实例的参数不同,目前我正在手动(准确地说是另一个代码)将这些参数设置为注册表中服务的映像路径(例如,
HKEY\U LOCAL\u MACHINE\SYSTEM\CurrentControlSet\services\MyService$i00
)。因此,我们的服务安装分两步完成

我非常有兴趣将这些步骤合并到Topshelf安装中,例如

MyService.exe install -instance "i00" -config "C:\i00Config.json"
初试 我从TopShelf尝试了
AddCommandLineDefinition
,但它似乎只在安装过程中起作用,并通过控制台运行,而不是服务本身(不会向服务映像路径添加任何内容)

第二次尝试 我试着看看是否有可能在Topshelf的
后安装
中做到这一点,而没有任何运气。下面是一个测试代码,看看它是否能正常工作(但不幸的是,Topshelf在
AfterInstall
调用后会覆盖注册表)

HostFactory.Run(x=>
{
x、 UseNLog();
x、 服务(sc=>
{
sc.ConstructUsing(hs=>newmyservice(hs));
sc.何时开始((s,h)=>s.开始(h));
sc.停止时((s,h)=>s.停止(h));
});
x、 安装后(s=>
{
使用(var system=Registry.LocalMachine.OpenSubKey(“系统”))
使用(var controlSet=system.OpenSubKey(“CurrentControlSet”))
使用(var services=controlSet.OpenSubKey(“服务”))
使用(var service=services.OpenSubKey)(string.IsNullOrEmpty(s.InstanceName)
?s.ServiceName
:s.ServiceName+“$”+s.InstanceName,true))
{
if(服务==null)
返回;
var imagePath=service.GetValue(“imagePath”)作为字符串;
if(string.IsNullOrEmpty(imagePath))
返回;
var appendix=string.Format(“-{0}\”{1}\”,“config”,“C:\i00config.json”);//仅用于测试是否可能
imagePath=imagePath+附录;
service.SetValue(“ImagePath”,ImagePath);
}
});
x、 SetServiceName(“MyService”);
x、 SetDisplayName(“我的服务”);
x、 SetDescription(“我的服务样本”);
x、 StartAutomatically();
x、 RunAsLocalSystem();
x、 启用ServiceRecovery(r=>
{
r、 OnCrashOnly();
r、 RestartService(1);//首先
r、 RestartService(1);//秒
r、 RestartService(1);//子序列
r、 设置重置周期(0);
});
});

我找不到任何关于如何使用TopShelf进行此操作的相关信息,所以问题是,是否可以使用TopShelf进行此操作?

要回答您的问题,不,使用TopShelf是不可能的。我很高兴你知道了如何管理ImagePath。但这是问题的症结所在,在邮件列表()中曾经有过一些关于这个主题的讨论,过去也有过相关的问题

最大的问题是,在将自定义参数应用于ImagePath时,管理行为预期是不直观的。例如,使用自定义命令行参数调用start时会发生什么情况?我很乐意实施这项计划或接受公关,如果我们得到的东西不会让我感到困惑,只是想一想,更不用说尝试使用它了。现在,我强烈建议您使用配置,而不是命令行参数来管理这一点,即使这意味着在磁盘上复制代码

好的,如前所述,似乎这个问题没有内置功能或简单的解决方法。因此,我基于一个定制的环境构建器为Topshelf编写了一个小小的扩展(大部分代码都是从Topshelf项目本身借来的)

我将代码发布在Github上,以防其他人发现它有用,下面是扩展

根据扩展名,我的代码如下:

HostFactory.Run(x =>
    {
        x.EnableStartParameters();
        x.UseNLog();
        x.Service<MyService>(sc =>
        {
            sc.ConstructUsing(hs => new MyService(hs));
            sc.WhenStarted((s, h) => s.Start(h));
            sc.WhenStopped((s, h) => s.Stop(h));
        });

        x.WithStartParameter("config",a =>{/*we can use parameter here*/});

        x.SetServiceName("MyService");
        x.SetDisplayName("My Service");
        x.SetDescription("My Service Sample");
        x.StartAutomatically();
        x.RunAsLocalSystem();
        x.EnableServiceRecovery(r =>
        {
            r.OnCrashOnly();
            r.RestartService(1); //first
            r.RestartService(1); //second
            r.RestartService(1); //subsequents
            r.SetResetPeriod(0);
        });
    });

下面的解决方法只不过是一个注册表更新。更新操作要求安装程序具有编写扩展参数所需的权限

基本上,我们是在响应
AfterInstall()
事件。从Topshelf v4.0.3开始,从事件中调用
AppendImageArgs()
解决方法将导致您的args出现在TS args之前。如果呼叫延迟,您的参数将出现在TS参数之后

解决方案

private static void AppendImageArgs(string serviceName, IEnumerable<Tuple<string, object>> args)
{
  try
  {
    using (var service = Registry.LocalMachine.OpenSubKey($@"System\CurrentControlSet\Services\{serviceName}", true))
    {
      const string imagePath = "ImagePath";
      var value = service?.GetValue(imagePath) as string;
      if (value == null)
        return;
      foreach (var arg in args)
        if (arg.Item2 == null)
          value += $" -{arg.Item1}";
        else
          value += $" -{arg.Item1} \"{arg.Item2}\"";
      service.SetValue(imagePath, value);
    }
  }
  catch (Exception e)
  {
    Log.Error(e);
  }
}
请注意,参数出现在TS参数之后,
-displayname
&
-servicename
。在本例中,
AppendImageArgs()
调用是在TS完成安装业务后调用的


命令行参数通常可以使用Topshelf方法指定,例如
AddCommandLineDefinition()
。要强制处理参数,请调用
ApplyCommandLine()

感谢您提供的信息,不幸的是,对我来说这是不可能的(我不想打开它,但它是项目要求的一部分),因此我最后在Topshelf上写了一个小扩展,请检查它(链接在我的答案中),如果可以的话,看看它是否足够合理,我可以将它与Topshelf代码合并(它会变得更干净、更简洁),如果有帮助的话,我可以发送一个pull请求?为什么不使用具有不同作业数据映射的一个实例和多个作业?你可以根据密钥控制它们(sc.exe control 128..256),现在你只有1个配置。我认为你低估了直观的开发者。
MyService.exe install -instance "i00" -config "C:\i00Config.json"
private static void AppendImageArgs(string serviceName, IEnumerable<Tuple<string, object>> args)
{
  try
  {
    using (var service = Registry.LocalMachine.OpenSubKey($@"System\CurrentControlSet\Services\{serviceName}", true))
    {
      const string imagePath = "ImagePath";
      var value = service?.GetValue(imagePath) as string;
      if (value == null)
        return;
      foreach (var arg in args)
        if (arg.Item2 == null)
          value += $" -{arg.Item1}";
        else
          value += $" -{arg.Item1} \"{arg.Item2}\"";
      service.SetValue(imagePath, value);
    }
  }
  catch (Exception e)
  {
    Log.Error(e);
  }
}
private static void AppendImageArgs(string serviceName)
{
  var args = new[]
  {
    new Tuple<string, object>("param1", "Hello"),
    new Tuple<string, object>("param2", 1),
    new Tuple<string, object>("Color", ConsoleColor.Cyan),
  };
  AppendImageArgs(serviceName, args);
}
 -displayname "MyService Display Name" -servicename "MyServiceName" -param1 "Hello" -param2 "1" -Color "Cyan"