通过Topshelf设置“服务启动参数”

本文关键字:启动 参数 服务 Topshelf 设置 通过 | 更新日期: 2023-09-27 18:15:27

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

我对合并Topshelf安装中的这些步骤很感兴趣,例如

MyService.exe install -instance "i00" -config "C:'i00Config.json"

第一次尝试

我尝试了TopShelf的AddCommandLineDefinition,但它似乎只在安装和通过控制台运行时有效,而不是服务本身(不会向服务映像路径添加任何内容)。

第二次尝试

我试着看看是否有可能做到这一点与AfterInstall从Topshelf没有任何运气。这里是一个测试代码,看看它是否会工作(但不幸的是,Topshelf在AfterInstall调用后覆盖注册表)。

HostFactory.Run(x =>
        {
            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.AfterInstall(s =>
            {
                using (var system = Registry.LocalMachine.OpenSubKey("SYSTEM"))
                using (var controlSet = system.OpenSubKey("CurrentControlSet"))
                using (var services = controlSet.OpenSubKey("services"))
                using (var service = services.OpenSubKey(string.IsNullOrEmpty(s.InstanceName)
                    ? s.ServiceName
                    : s.ServiceName + "$" + s.InstanceName, true))
                {
                    if (service == null)
                        return;
                    var imagePath = service.GetValue("ImagePath") as string;
                    if (string.IsNullOrEmpty(imagePath))
                        return;
                        var appendix = string.Format(" -{0} '"{1}'"", "config", "C:'i00config.json"); //only a test to see if it is possible at all or not
                        imagePath = imagePath + appendix;

                    service.SetValue("ImagePath", imagePath);
                }
            });
            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);
            });
        });

我找不到任何有关如何使用TopShelf的相关信息,所以问题是,是否有可能使用TopShelf做到这一点?

通过Topshelf设置“服务启动参数”

好吧,正如Travis提到的,似乎没有内置功能或简单的解决方案来解决这个问题。所以我写了一个基于自定义环境生成器的Topshelf扩展(大部分代码是从Topshelf项目本身借来的)。

我在Github上发布了代码,以防其他人发现它有用,这里是Topshelf。StartParameters扩展。

基于扩展我的代码将是这样的:

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);
        });
    });

,我可以简单地设置它:

MyService.exe install -instance "i00" -config "C:'i00Config.json"

回答你的问题,不,这是不可能的Topshelf。我很高兴您弄清楚了如何管理ImagePath。但这是问题的关键,在邮件列表(https://groups.google.com/d/msg/topshelf-discuss/Xu4XR6wGWxw/8mAtyJFATq8J)上有一些关于这个主题的讨论,以及过去关于它的问题。

最大的问题是,当对ImagePath应用自定义参数时,管理行为的期望将是不直观的。例如,当使用自定义命令行参数调用start时会发生什么?如果我们得到的东西不会让我在思考时感到困惑,更不用说尝试使用了,我愿意执行这个或接受PR。现在,我强烈建议您使用配置,而不是命令行参数来管理它,即使这意味着在磁盘上复制代码。

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

基本上,我们正在响应AfterInstall()事件。从Topshelf v4.0.3开始,在事件中调用AppendImageArgs()变通方法将导致您的参数在 TS参数之前出现。如果调用被延迟,你的参数将出现在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);
  }
}

示例调用

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);
}

以及将出现在ImagePath中的结果参数:

 -displayname "MyService Display Name" -servicename "MyServiceName" -param1 "Hello" -param2 "1" -Color "Cyan"

注意参数出现在TS参数之后,-displayname &-servicename。在本例中,AppendImageArgs()调用是在TS完成其安装业务之后调用的。

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