Installing multiple instances of the same windows service on a server
Asked Answered
G

10

105

So we've produced a windows service to feed data to our client application and everything is going great. The client has come up with a fun configuration request that requires two instances of this service running on the same server and configured to point at separate databases.

So far I haven't been able to get this to happen and was hoping my fellow stackoverflow members might be able to give some hints as to why.

Current setup:

I've set up the project that contains the windows service, we'll call it AppService from now on, and the ProjectInstaller.cs file that handles custom installation steps to set the service name based on a key in the App.config like so:

this.serviceInstaller1.ServiceName = Util.ServiceName;
this.serviceInstaller1.DisplayName = Util.ServiceName;
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;

In this case Util is just a static class tha tloads the service name from the config file.

From here forward I have tried two different ways to get both services installed and both have failed in an identical way.

The first way was to simply install the first copy of the service, copy the installed directory and renamed it, and then ran the following command after modifying the app config to change the desired service name:

InstallUtil.exe /i AppService.exe

When that didn't work I tried to create a second installer project, edited the config file and built the second installer. When I ran the installer it worked fine but the service did not show up in services.msc so I ran the previous command against the second installed code base.

Both times i received the following output from InstallUtil (relevant parts only):

Running a transacted installation.

Beginning the Install phase of the installation.

Installing service App Service Two... Service App Service Two has been successfully installed. Creating EventLog source App Service Two in log Application...

An exception occurred during the Install phase. System.NullReferenceException: Object reference not set to an instance of an object.

The Rollback phase of the installation is beginning.

Restoring event log to previous state for source App Service Two. Service App Service Two is being removed from the system... Service App Service Two was successfully removed from the system.

The Rollback phase completed successfully.

The transacted install has completed. The installation failed, and the rollback has been performed.

Sorry for the long winded post, wanted to make sure there is enough relevant information. The piece that so far has me stumped is that it states that the installation of the service completes successfully and its only after it goes to create the EventLog source that the NullReferenceException seems to get thrown. So if anyone knows what I'm doing wrong or has a better approach it would be much appreciated.

Grantinaid answered 14/8, 2009 at 18:24 Comment(0)
F
85

Have you tried the sc / service controller util? Type

sc create

at a command line, and it will give you the help entry. I think I've done this in the past for Subversion and used this article as a reference:

http://svn.apache.org/repos/asf/subversion/trunk/notes/windows-service.txt

Fusilier answered 14/8, 2009 at 18:38 Comment(2)
I found this page to be useful: http://journalofasoftwaredev.wordpress.com/2008/07/16/multiple-instances-of-same-windows-service/. You can insert code into the installer to get the service name that you want when you run installutil.Dun
Link to wordpress blog has been changed to: journalofasoftwaredev.wordpress.com/2008/07Illuminator
O
23
  sc create [servicename] binpath= [path to your exe]

This solution worked for me.

Outplay answered 14/6, 2013 at 7:24 Comment(2)
just to point out; [path to your exe] has to be full path and don't forget the space after binpath=Clow
This does indeed allow a service to be installed multiple times. However, all the information provided by the service installer. F.e. description, logon type etc. is ignoredUnregenerate
M
20

You can run multiple versions of the same service by doing the following:

1) Copy the Service executable and config to its own folder.

2) Copy Install.Exe to the service executable folder (from .net framework folder)

3) Create a config file called Install.exe.config in the service executable folder with the following contents (unique service names):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="ServiceName" value="The Service Name"/>
    <add key="DisplayName" value="The Service Display Name"/>
  </appSettings>
</configuration>

4) Create a batch file to install the service with the following contents:

REM Install
InstallUtil.exe YourService.exe
pause

5) While your there, create an uninstall batch file

REM Uninstall
InstallUtil.exe -u YourService.exe
pause

EDIT:

Note sure if I missed something, here is the ServiceInstaller Class (adjust as required):

using System.Configuration;

namespace Made4Print
{
    partial class ServiceInstaller
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
        private System.ServiceProcess.ServiceInstaller FileProcessingServiceInstaller;
        private System.ServiceProcess.ServiceProcessInstaller FileProcessingServiceProcessInstaller;

        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Component Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.FileProcessingServiceInstaller = new System.ServiceProcess.ServiceInstaller();
            this.FileProcessingServiceProcessInstaller = new System.ServiceProcess.ServiceProcessInstaller();
            // 
            // FileProcessingServiceInstaller
            // 
            this.FileProcessingServiceInstaller.ServiceName = ServiceName;
            this.FileProcessingServiceInstaller.DisplayName = DisplayName;
            // 
            // FileProcessingServiceProcessInstaller
            // 
            this.FileProcessingServiceProcessInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
            this.FileProcessingServiceProcessInstaller.Password = null;
            this.FileProcessingServiceProcessInstaller.Username = null;
            // 
            // ServiceInstaller
            // 
            this.Installers.AddRange(new System.Configuration.Install.Installer[] { this.FileProcessingServiceInstaller, this.FileProcessingServiceProcessInstaller });
        }

        #endregion

        private string ServiceName
        {
            get
            {
                return (ConfigurationManager.AppSettings["ServiceName"] == null ? "Made4PrintFileProcessingService" : ConfigurationManager.AppSettings["ServiceName"].ToString());
            }
        }

        private string DisplayName
        {
            get
            {
                return (ConfigurationManager.AppSettings["DisplayName"] == null ? "Made4Print File Processing Service" : ConfigurationManager.AppSettings["DisplayName"].ToString());
            }
        }
    }
}
Mizell answered 14/8, 2009 at 18:37 Comment(15)
I think what you are describing is more or less what I've done by allowing the ServiceName and DisplayName to be set from my services app.config I did attempt what you describe but unfortunately it resulted in the same issue listed in my question.Grantinaid
I kind of have a template I use, which I have used for ages, so maybe I missed something, what does your ServiceInstaller Class look like, will post a working copy of one I use, let me know i fthis helps?Mizell
Our service installers are actually nearly identical. I use a static class to load the service and display names from the config file but other than that they are very similar. My guess as to why it isn't working for me is that there may be something a bit peculiar about our service code. A lot of hands have been on it unfortunately. From what I understand though, your answer should work in a majority of cases thanks for the help.Grantinaid
Huge help thanks. I think the install config file needs to be named InstallUtil.exe.confg not Install.exe.config for the InstallUtil.exeBoche
A nice approach that totally works. That is if you know which InstallUtil.exe to copy to your installation folder (I personally have tons of framework versions installed which is exacerbated by the 64-bit copies). This would make it pretty difficult to explain to Helpdesk team if they do the installations. But for a developer-lead installation it is very elegant.Teleplay
I had to add this code to the project installer class, but it worked without issue.Clam
ConfigurationManager.AppSettings["ServiceName"] always returns null for me even if I set them under app.config. Any idea why? If I debug project it reads it but if I run from serviceutil it always sets service name as default value. (hardcoded one)Mazman
@MarkRedman Uninstalling and reinstalling windows service causes an error as "Error:1001 The specified service already exists." How to resolve this?Singspiel
@BalagurunathanMarimuthu You will need to uninstall the service first, before re-installing it again.Mizell
Are you sure it's removed from the Services console? you can also try: sc delete <Service_Name> and see if that works?Mizell
@MarkRedman Yes, I have checked in Service console, sc delete, regedit, installation path, etc., but, it not working...Singspiel
@BalagurunathanMarimuthu: Not being funny, but have you tried rebooting? sometimes services can get disabled until reboot? Might help?Mizell
@MarkRedman It works after sometimes without made any changes in the system. Thanks for your solution.Singspiel
@MarkRedman Is it possible to install windows service as multiple instance of same window service through installer package? Here, I need to create two different desktop shortcut with two different name.. and, above approach doesn't make any entry in "programs & Features". Hence, my windows service also has windows application which needs to be a copy for each instance of service installed.Singspiel
@BalagurunathanMarimuthu Yes you can install the same service multiple times, you will need to change the service name and description.. ideally you would have these in different folders depending on your configuration, check that there is no conflict in what your service is doing.Mizell
H
15

Another quick way to specify a custom value for ServiceName and DisplayName is using installutil command line parameters.

  1. In your ProjectInstaller class override virtual methods Install(IDictionary stateSaver) and Uninstall(IDictionary savedState)

    public override void Install(System.Collections.IDictionary stateSaver)
    {
        GetCustomServiceName();
        base.Install(stateSaver);
    }
    
    public override void Uninstall(System.Collections.IDictionary savedState)
    {
        GetCustomServiceName();
        base.Uninstall(savedState);
    }
    
    //Retrieve custom service name from installutil command line parameters
    private void GetCustomServiceName()
    {
        string customServiceName = Context.Parameters["servicename"];
        if (!string.IsNullOrEmpty(customServiceName))
        {
            serviceInstaller1.ServiceName = customServiceName;
            serviceInstaller1.DisplayName = customServiceName;
        }
    }
    
  2. Build your project
  3. Install the service with installutil adding your custom name using /servicename parameter:

    installutil.exe /servicename="CustomServiceName" "c:\pathToService\SrvcExecutable.exe"
    

Please note that if you do not specify /servicename in the command line the service will be installed with ServiceName and DisplayName values specified in ProjectInstaller properties/config

Helsa answered 22/12, 2017 at 8:25 Comment(1)
Brilliant!! Thank you - this was exactly what was needed and to the point.Visualize
K
11

Old question, I know, but I've had luck using the /servicename option on InstallUtil.exe. I don't see it listed in the built-in help though.

InstallUtil.exe /servicename="My Service" MyService.exe

I'm not entirely sure where I first read about this but I haven't seen it since. YMMV.

Kreisler answered 22/4, 2013 at 20:25 Comment(4)
Returns this error: An exception occurred during the Install phase. System.ComponentModel.Win32Exception: The specified service already existsClow
@Clow Do you have a another service called "My Service"?Kreisler
Yes, as in the question I have one service, same executable, but I want to install two instances of it, each with different config. I copy-paste the service exe but this one didn't work.Clow
/servicename="My Service InstanceOne" and /servicename="My Service InstanceTwo" The names have to be unique.Shag
I
7

I didn't have much luck with the above methods when using our automated deployment software to frequently install/uninstall side-by-side windows services, but I eventually came up with the following which allows me to pass in a parameter to specify a suffix to the service name on the command line. It also allows the designer to function properly and could easily be adapted to override the entire name if necessary.

public partial class ProjectInstaller : System.Configuration.Install.Installer
{
  protected override void OnBeforeInstall(IDictionary savedState)
  {
    base.OnBeforeInstall(savedState);
    SetNames();
  }

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

  private void SetNames()
  {
    this.serviceInstaller1.DisplayName = AddSuffix(this.serviceInstaller1.DisplayName);
    this.serviceInstaller1.ServiceName = AddSuffix(this.serviceInstaller1.ServiceName);
  }

  private string AddSuffix(string originalName)
  {
    if (!String.IsNullOrWhiteSpace(this.Context.Parameters["ServiceSuffix"]))
      return originalName + " - " + this.Context.Parameters["ServiceSuffix"];
    else
      return originalName;
  }
}

With this in mind, I can do the following: If I've called the service "Awesome Service" then I can install a UAT verison of the service as follows:

InstallUtil.exe /ServiceSuffix="UAT" MyService.exe

This will create the service with the name "Awesome Service - UAT". We've used this to run DEVINT, TESTING and ACCEPTANCE versions of the same service running side-by-side on a single machine. Each version has its own set of files/configs - I haven't tried this to install multiple services pointing at the same set of files.

NOTE: you have to use the same /ServiceSuffix parameter to uninstall the service, so you'd execute the following to uninstall:

InstallUtil.exe /u /ServiceSuffix="UAT" MyService.exe

Inanition answered 19/5, 2016 at 17:57 Comment(9)
That is great, but that is just for the installer. Once you have a new instance name, how will the Windows service know about this new name? Do you have to pass it on at construction of the Windows service?Unlimber
Thanks! The installer will set the name on the Windows Service while it's installing it using the values set in the SetNames() method above.Inanition
Sure, but how can you set this name from the outside world?Unlimber
In my answer is the command used on the command line to install (and uninstall) the service in the outside world. The value you pass into /ServiceSuffix="UAT" is used by the installer to set the suffix on the service. In my example, the value passed in is UAT. In my scenario I just wanted to add a suffix onto the existing name of the service, but there's no reason you couldn't adapt this to replace the name entirely with the value that's passed in.Inanition
Thanks, but that is a command line input (= manual input), not code. As per original question: Once you have a new instance name, how will the Windows service know about this new name? Do you have to pass it on at construction of the Windows service?Unlimber
I'm not sure I understand what problem it is you're trying to solve. The original question was about how to install the same windows service twice on the same machine - the OP was having a problem when installing the same service from the command line from two different locations - this was failing because both services would have the same name which Windows didn't like. My answer demonstrates how to fix that by passing in a parameter into the installer from the command line to set different names. It sounds like you're trying to do something else and I'm not clear what - could you explain?Inanition
Thanks @tristankoffee. It is just that you answer is incomplete in the sense that passing a name to the installer does not make the service work with that instance name by magic :-) I had to pass that instance name to my Windows service constructor (withing the installer) so that the system actually knows about and is able to refer/use that specific service instance. Otherwise, it won't work. Unless you have another idea on how to do it, which is what I asked for since I was not sure it was the best way to do it :-)Unlimber
@Unlimber - unfortunately I wrote this answer almost 4 years ago and don't have access to the original code and can't remember how you would go about hooking up the service name with the installer, so I'm sorry I can't help you with that. Likely, as the OP referenced the installer code already, I removed the superfluous code for brevity. Good luck with your project.Inanition
I understand and appreciate you tried, thank you anyway :-) +1 on you original answerUnlimber
T
4

What I've done to make this work is to store the service name and display name in an app.config for my service. Then in my installer class, I load the app.config as an XmlDocument and use xpath to get the values out and apply them to ServiceInstaller.ServiceName and ServiceInstaller.DisplayName, before calling InitializeComponent(). This assumes you're not already setting these properties in InitializeComponent(), in which case, the settings from your config file will be ignored. The following code is what I'm calling from my installer class constructor, before InitializeComponent():

       private void SetServiceName()
       {
          string configurationFilePath = Path.ChangeExtension(Assembly.GetExecutingAssembly().Location, "exe.config");
          XmlDocument doc = new XmlDocument();
          doc.Load(configurationFilePath);

          XmlNode serviceName = doc.SelectSingleNode("/xpath/to/your/@serviceName");
          XmlNode displayName = doc.SelectSingleNode("/xpath/to/your/@displayName");

          if (serviceName != null && !string.IsNullOrEmpty(serviceName.Value))
          {
              this.serviceInstaller.ServiceName = serviceName.Value;
          }

          if (displayName != null && !string.IsNullOrEmpty(displayName.Value))
          {
              this.serviceInstaller.DisplayName = displayName.Value;
          }
      }

I don't believe reading the configuration file directly from ConfigurationManager.AppSettings or something similar will work as when the installer runs, it's run in the context of InstallUtil.exe, not your service's .exe. You may be able to do something with ConfigurationManager.OpenExeConfiguration, however in my case, this didn't work as I was trying to get at a custom configuration section that was not loaded.

Toque answered 24/8, 2011 at 16:42 Comment(2)
Hi Chris House! Stumbled across your answer because I'm building a self-hosted OWIN-based Web API around Quartz.NET scheduler and sticking it in a Windows Service. Pretty slick! Hoping you're well!Grand
Hi Chris House! Stumbled across your answer because I'm building a self-hosted OWIN-based Web API around Quartz.NET scheduler and sticking it in a Windows Service. Pretty slick! Hoping you're well!Grand
M
4

Just to improve perfect answer of @chris.house.00 this, you can consider following function to read from your app settings:

 public void GetServiceAndDisplayName(out string serviceNameVar, out string displayNameVar)
        {
            string configurationFilePath = Path.ChangeExtension(Assembly.GetExecutingAssembly().Location, "exe.config");
            XmlDocument doc = new XmlDocument();
            doc.Load(configurationFilePath);

            XmlNode serviceName = doc.SelectSingleNode("//appSettings//add[@key='ServiceName']");
            XmlNode displayName = doc.SelectSingleNode("//appSettings//add[@key='DisplayName']");


            if (serviceName != null && (serviceName.Attributes != null && (serviceName.Attributes["value"] != null)))
            {
                serviceNameVar = serviceName.Attributes["value"].Value;
            }
            else
            {
                serviceNameVar = "Custom.Service.Name";
            }

            if (displayName != null && (displayName.Attributes != null && (displayName.Attributes["value"] != null)))
            {
                displayNameVar = displayName.Attributes["value"].Value;
            }
            else
            {
                displayNameVar = "Custom.Service.DisplayName";
            }
        }
Mazman answered 29/4, 2016 at 21:39 Comment(0)
D
2

I had a similar situation, where i to needed have a previous service, and an updated service running side by side on the same server. (It was more than just a database change, it was code changes as well). So I couldn't just run the same .exe twice. I needed a new .exe that was compiled with new DLLs but from the same project. Just changing the service name and display name of the service did not work for me, I still received the "service already existed error" which I believe is because I am using a Deployment Project. What finally did work for me is within my Deployment Project Properties there is a property called "ProductCode" which is a Guid.

enter image description here

After that, rebuilding the Setup Project to a new .exe or .msi installed successfully.

Danish answered 30/3, 2018 at 14:19 Comment(1)
This is the actual answer to the question.Gott
D
1

The simplest approach is is based the service name on the dll name:

string sAssPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
string sAssName = System.IO.Path.GetFileNameWithoutExtension(sAssPath);
if ((this.ServiceInstaller1.ServiceName != sAssName)) {
    this.ServiceInstaller1.ServiceName = sAssName;
    this.ServiceInstaller1.DisplayName = sAssName;
}
Doublure answered 29/6, 2018 at 21:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.