9

I am using command line arguments to pass some configuration to the windows service (it will be few instances with different command lines).

My code looks like this:

HostFactory.Run(x =>                                 
{
    x.Service<MyHost>(s =>
    {                    
        s.ConstructUsing(name => new MyHost());
        s.WhenStarted(tc => tc.Start());             
        s.WhenStopped(tc => tc.Stop());              
    });
    x.AddCommandLineDefinition("sqlserver", v => sqlInstance = v);
}); 

When I install service I use:

myhost.exe install -sqlserver:someinstance

Unfortunately, sqlserver command line options is available only on install phase, and do not go to the parameters of the service. So when I run service, I do not get parameter value I need.

Is there any way to modify command line of the service started by TopShelf?

Mike Chaliy
  • 23,606
  • 16
  • 61
  • 102

3 Answers3

4

I have a similar requirement and unfortunately storing the command line parameters in a file was not an option.

Disclaimer: This approach is only valid for Windows

First I added an After Install Action

x.AfterInstall(
    installSettings =>
    {
         AddCommandLineParametersToStartupOptions(installSettings);
    });

In AddCommanLineParameterToStartupOptions I update the ImagePath Windows Registry entry for the service to include the command line parameters.

TopShelf adds it's parameters after this step so to avoid duplicates of servicename and instance I filter these out. You may want to filter out more than just those but in my case this was enough.

     private static void AddCommandLineParametersToStartupOptions(InstallHostSettings installSettings)
     {
            var serviceKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
                $"SYSTEM\\CurrentControlSet\\Services\\{installSettings.ServiceName}",
                true);

            if (serviceKey == null)
            {
                throw new Exception($"Could not locate Registry Key for service '{installSettings.ServiceName}'");
            }

            var arguments = Environment.GetCommandLineArgs();

            string programName = null;
            StringBuilder argumentsList = new StringBuilder();

            for (int i = 0; i < arguments.Length; i++)
            {
                if (i == 0)
                {
                    // program name is the first argument
                    programName = arguments[i];
                }
                else
                {
                    // Remove these servicename and instance arguments as TopShelf adds them as well
                    // Remove install switch
                    if (arguments[i].StartsWith("-servicename", StringComparison.InvariantCultureIgnoreCase) |
                        arguments[i].StartsWith("-instance", StringComparison.InvariantCultureIgnoreCase) |
                        arguments[i].StartsWith("install", StringComparison.InvariantCultureIgnoreCase))
                    {
                        continue;
                    }
                    argumentsList.Append(" ");
                    argumentsList.Append(arguments[i]);
                }
            }

            // Apply the arguments to the ImagePath value under the service Registry key
            var imageName = $"\"{Environment.CurrentDirectory}\\{programName}\" {argumentsList.ToString()}";
            serviceKey.SetValue("ImagePath", imageName, Microsoft.Win32.RegistryValueKind.String);
}
Amith Sewnarain
  • 635
  • 6
  • 11
  • You probably do not want write username/password (used to run service under special account) to imagepath. Here is version which allows to white-list argumetns: https://gist.github.com/sm-g/77e73efab4177794533d8e0589214715 – smg Mar 22 '18 at 15:37
2

You can't easily do it at this point. I would review this thread on the mailing list

https://groups.google.com/d/msg/topshelf-discuss/Xu4XR6wGWxw/GFzmKdn_MeYJ

Travis
  • 9,959
  • 1
  • 25
  • 46
2

You can't modify the command line passed to the service. I get around this by saving those parameters to configuration next to the exe. So a user can do this:

service.exe run /sqlserver:connectionstring

and the app saves the connectionstring to a file (either using System.IO.File or using ConfigurationManager)

Then, if the user does this:

service.exe run

Or if the service runs as a windows service, then the app just loads from config.

Daniel
  • 2,842
  • 5
  • 30
  • 46