Instalar várias instâncias do mesmo serviço Windows em um servidor

96

Então, nós produzimos um serviço do Windows para alimentar nossos aplicativos cliente e tudo está indo muito bem. O cliente apresentou uma solicitação de configuração divertida que requer duas instâncias desse serviço em execução no mesmo servidor e configuradas para apontar para bancos de dados separados.

Até agora, não consegui fazer isso acontecer e esperava que meus colegas membros do stackoverflow pudessem dar algumas dicas do porquê.

Configuração atual:

Eu configurei o projeto que contém o serviço do Windows, vamos chamá-lo de AppService de agora em diante, e o arquivo ProjectInstaller.cs que lida com as etapas de instalação personalizadas para definir o nome do serviço com base em uma chave no App.config assim :

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

Nesse caso, Util é apenas uma classe estática que carrega o nome do serviço do arquivo de configuração.

Daqui em diante, tentei duas maneiras diferentes de instalar os dois serviços e ambos falharam de maneira idêntica.

A primeira maneira era simplesmente instalar a primeira cópia do serviço, copiar o diretório instalado e renomeá-lo e, em seguida, executar o seguinte comando após modificar a configuração do aplicativo para alterar o nome do serviço desejado:

InstallUtil.exe /i AppService.exe

Quando isso não funcionou, tentei criar um segundo projeto de instalador, editei o arquivo de configuração e construí o segundo instalador. Quando executei o instalador, ele funcionou bem, mas o serviço não apareceu em services.msc, então executei o comando anterior na segunda base de código instalada.

Ambas as vezes, recebi a seguinte saída do InstallUtil (somente peças relevantes):

Executando uma instalação transacionada.

Iniciando a fase de instalação da instalação.

Instalando o serviço App Service Two ... O Service App Service Two foi instalado com êxito. Criando EventLog fonte App Service Two no aplicativo de log ...

Ocorreu uma exceção durante a fase de instalação. System.NullReferenceException: Referência de objeto não definida para uma instância de um objeto.

A fase de reversão da instalação está começando.

Restaurando o log de eventos ao estado anterior para o App Service Two de origem. Aplicativo de serviço O serviço dois está sendo removido do sistema ... Aplicativo de serviço O serviço dois foi removido com êxito do sistema.

A fase de reversão foi concluída com sucesso.

A instalação transacionada foi concluída. A instalação falhou e o rollback foi executado.

Desculpe pela postagem prolixa, queria ter certeza de que há informações relevantes o suficiente. A parte que até agora me deixou perplexo é que ele afirma que a instalação do serviço foi concluída com êxito e só depois de criar a origem do EventLog é que a NullReferenceException parece ser lançada. Portanto, se alguém souber o que estou fazendo de errado ou tiver uma abordagem melhor, ficaria muito grato.

Switters
fonte

Respostas:

81

Você tentou o utilitário do controlador sc / service? Tipo

sc create

em uma linha de comando, e ele lhe dará a entrada de ajuda. Acho que já fiz isso no passado para o Subversion e usei este artigo como referência:

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

Jamesaharvey
fonte
5
Eu encontrei esta página para ser útil: http://journalofasoftwaredev.wordpress.com/2008/07/16/multiple-instances-of-same-windows-service/. Você pode inserir o código no instalador para obter o nome do serviço que deseja ao executar o installutil.
Vivian River
9
Link para o blog wordpress foi alterado para: journalofasoftwaredev.wordpress.com/2008/07
STLDev
21
  sc create [servicename] binpath= [path to your exe]

Essa solução funcionou para mim.

Rajesh Kumar
fonte
5
apenas para apontar; [path to your exe]tem que ser o caminho completo e não se esqueça do espaço depoisbinpath=
mkb
2
Isso realmente permite que um serviço seja instalado várias vezes. No entanto, todas as informações fornecidas pelo instalador do serviço. A descrição Fe, tipo de logon, etc. é ignorado
Noel Widmer
20

Você pode executar várias versões do mesmo serviço, fazendo o seguinte:

1) Copie o executável e a configuração do serviço para sua própria pasta.

2) Copie Install.Exe para a pasta executável do serviço (da pasta .net framework)

3) Crie um arquivo de configuração chamado Install.exe.config na pasta executável do serviço com o seguinte conteúdo (nomes de serviço exclusivos):

<?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) Crie um arquivo em lote para instalar o serviço com o seguinte conteúdo:

REM Install
InstallUtil.exe YourService.exe
pause

5) Enquanto estiver lá, crie um arquivo em lote de desinstalação

REM Uninstall
InstallUtil.exe -u YourService.exe
pause

EDITAR:

Observe se eu perdi algo, aqui está a classe ServiceInstaller (ajuste conforme necessário):

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());
            }
        }
    }
}
Mark Redman
fonte
Acho que o que você está descrevendo é mais ou menos o que fiz ao permitir que ServiceName e DisplayName fossem definidos em meus serviços app.config Tentei o que você descreveu, mas infelizmente resultou no mesmo problema listado em minha pergunta.
Switters de
Eu meio que tenho um modelo que uso, que uso há muito tempo, então talvez eu tenha esquecido alguma coisa, como é sua classe ServiceInstaller, postarei uma cópia de trabalho de uma que eu uso, me diga se isso ajuda?
Mark Redman
Nossos instaladores de serviço são quase idênticos. Eu uso uma classe estática para carregar o serviço e exibir os nomes do arquivo de configuração, mas, fora isso, eles são muito semelhantes. Meu palpite sobre por que não está funcionando para mim é que pode haver algo um pouco peculiar sobre nosso código de serviço. Muitas mãos estiveram nisso, infelizmente. Pelo que entendi, porém, sua resposta deve funcionar na maioria dos casos, obrigado pela ajuda.
Switters de
2
Grande ajuda, obrigado. Acho que o arquivo de configuração de instalação precisa ser nomeado InstallUtil.exe.confg, não Install.exe.config para o InstallUtil.exe
NullReference
Uma boa abordagem que funciona totalmente. Isso se você souber qual InstallUtil.exe deve copiar para sua pasta de instalação (eu pessoalmente tenho várias versões do framework instaladas, o que é agravado pelas cópias de 64 bits). Isso tornaria muito difícil explicar para a equipe de Helpdesk se eles fazem as instalações. Mas, para uma instalação liderada pelo desenvolvedor, é muito elegante.
timmi4sa
11

Pergunta antiga, eu sei, mas tive sorte ao usar a opção / servicename em InstallUtil.exe. No entanto, não o vejo listado na ajuda integrada.

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

Não tenho certeza de onde li sobre isso pela primeira vez, mas não vi desde então. YMMV.

Jonathon Watney
fonte
3
Retorna este erro:An exception occurred during the Install phase. System.ComponentModel.Win32Exception: The specified service already exists
mkb
@mkb Você tem outro serviço chamado "Meu serviço"?
Jonathon Watney
Sim, como na pergunta tenho um serviço, mesmo executável, mas quero instalar duas instâncias dele, cada uma com configuração diferente. Copiei e colei o exe de serviço, mas este não funcionou.
mkb
1
/ servicename = "My Service InstanceOne" e / servicename = "My Service InstanceTwo" Os nomes devem ser exclusivos.
granadaCoder
11

Outra maneira rápida de especificar um valor personalizado para ServiceNamee DisplayNameé usar installutilparâmetros de linha de comando.

  1. Em sua ProjectInstallerclasse, substitua métodos virtuais Install(IDictionary stateSaver)eUninstall(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. Construa seu projeto
  3. Instale o serviço installutiladicionando seu nome personalizado usando o /servicenameparâmetro:

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

Observe que se você não especificar /servicenamena linha de comando, o serviço será instalado com os valores ServiceName e DisplayName especificados nas propriedades / configuração do ProjectInstaller

Andrea
fonte
2
Brilhante!! Obrigado - isso era exatamente o que era necessário e direto ao ponto.
Iofatura
7

Não tive muita sorte com os métodos acima ao usar nosso software de implantação automatizado para instalar / desinstalar frequentemente serviços do Windows lado a lado, mas acabei descobrindo o seguinte, que me permite passar um parâmetro para especificar um sufixo ao nome do serviço na linha de comando. Ele também permite que o designer funcione corretamente e pode ser facilmente adaptado para substituir o nome inteiro, se necessário.

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

Com isso em mente, posso fazer o seguinte: Se eu chamei o serviço de "Serviço incrível", posso instalar uma versão UAT do serviço da seguinte maneira:

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

Isso criará o serviço com o nome "Awesome Service - UAT". Usamos isso para executar versões DEVINT, TESTING e ACCEPTANCE do mesmo serviço em execução lado a lado em uma única máquina. Cada versão tem seu próprio conjunto de arquivos / configurações - não tentei fazer isso para instalar vários serviços apontando para o mesmo conjunto de arquivos.

NOTA: você deve usar o mesmo /ServiceSuffixparâmetro para desinstalar o serviço, então você executaria o seguinte para desinstalar:

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

tristankoffee
fonte
Isso é ótimo, mas é apenas para o instalador. Depois de ter um novo nome de instância, como o serviço do Windows saberá sobre esse novo nome? Você tem que repassar na construção do serviço Windows?
progLearner
Obrigado! O instalador definirá o nome no serviço do Windows enquanto o instala usando os valores definidos no método SetNames () acima.
tristankoffee
Claro, mas como você pode definir esse nome do mundo exterior?
progLearner
Na minha resposta está o comando usado na linha de comando para instalar (e desinstalar) o serviço no mundo exterior. O valor que você passa /ServiceSuffix="UAT"é usado pelo instalador para definir o sufixo no serviço. No meu exemplo, o valor passado é UAT. No meu cenário, eu só queria adicionar um sufixo ao nome existente do serviço, mas não há razão para você não poder adaptar isso para substituir o nome inteiramente pelo valor que é passado.
tristankoffee
Obrigado, mas isso é uma entrada de linha de comando (= entrada manual), não código. De acordo com a pergunta original: depois de ter um novo nome de instância, como o serviço do Windows saberá sobre esse novo nome? Você tem que repassar na construção do serviço Windows?
progLearner de
4

O que fiz para fazer isso funcionar foi armazenar o nome do serviço e o nome de exibição em um app.config para meu serviço. Em seguida, em minha classe do instalador, carrego o app.config como um XmlDocument e uso xpath para obter os valores e aplicá-los a ServiceInstaller.ServiceName e ServiceInstaller.DisplayName, antes de chamar InitializeComponent (). Isso pressupõe que você ainda não está definindo essas propriedades em InitializeComponent (), caso em que as configurações de seu arquivo de configuração serão ignoradas. O código a seguir é o que estou chamando do meu construtor de classe do instalador, antes de 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;
          }
      }

Não acredito que a leitura do arquivo de configuração diretamente do ConfigurationManager.AppSettings ou algo semelhante funcione como quando o instalador é executado, ele é executado no contexto do InstallUtil.exe, não do .exe do seu serviço. Você pode conseguir fazer algo com ConfigurationManager.OpenExeConfiguration, no entanto, no meu caso, isso não funcionou porque estava tentando obter uma seção de configuração personalizada que não foi carregada.

chris.house.00
fonte
Olá, Chris House! Tropecei em sua resposta porque estou construindo uma API da Web auto-hospedada baseada em OWIN em torno do planejador Quartz.NET e colocando-a em um serviço do Windows. Muito liso! Esperando que você esteja bem!
NovaJoe
Olá, Chris House! Tropecei em sua resposta porque estou construindo uma API da Web auto-hospedada baseada em OWIN em torno do planejador Quartz.NET e colocando-a em um serviço do Windows. Muito liso! Esperando que você esteja bem!
NovaJoe
4

Apenas para melhorar a resposta perfeita de @ chris.house.00 isso , você pode considerar a função a seguir para ler suas configurações de aplicativos:

 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";
            }
        }
Teoman shipahi
fonte
2

Tive uma situação semelhante, em que precisava ter um serviço anterior e um serviço atualizado rodando lado a lado no mesmo servidor. (Foi mais do que apenas uma mudança no banco de dados, foram mudanças no código também). Portanto, eu não poderia simplesmente executar o mesmo .exe duas vezes. Eu precisava de um novo .exe que foi compilado com novas DLLs, mas do mesmo projeto. Apenas alterar o nome do serviço e o nome de exibição do serviço não funcionou para mim, ainda recebo o "erro de serviço já existia" que acredito ser porque estou usando um projeto de implantação. O que finalmente funcionou para mim é que nas propriedades do projeto de implantação há uma propriedade chamada "ProductCode", que é um Guid.

insira a descrição da imagem aqui

Depois disso, reconstruir o projeto de instalação para um novo .exe ou .msi instalado com êxito.

Cmartin
fonte
1

A abordagem mais simples é baseada no nome do serviço no nome dll:

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;
}
Igor Krupitsky
fonte