Cài đặt nhiều phiên bản của cùng một dịch vụ windows trên một máy chủ


96

Vì vậy, chúng tôi đã tạo ra một dịch vụ windows để cung cấp dữ liệu cho ứng dụng khách của chúng tôi và mọi thứ đang diễn ra rất tốt. Máy khách đã đưa ra một yêu cầu cấu hình thú vị yêu cầu hai phiên bản của dịch vụ này chạy trên cùng một máy chủ và được định cấu hình để trỏ đến các cơ sở dữ liệu riêng biệt.

Cho đến nay tôi vẫn chưa thể làm điều này xảy ra và hy vọng các thành viên stackoverflow đồng nghiệp của tôi có thể đưa ra một số gợi ý về lý do tại sao.

Thiết lập hiện tại:

Tôi đã thiết lập dự án có chứa dịch vụ windows, từ giờ chúng tôi sẽ gọi nó là AppService và tệp ProjectInstaller.cs xử lý các bước cài đặt tùy chỉnh để đặt tên dịch vụ dựa trên một khóa trong App.config như vậy :

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

Trong trường hợp này, Util chỉ là một lớp tĩnh có thể tải tên dịch vụ từ tệp cấu hình.

Từ đây trở đi, tôi đã thử hai cách khác nhau để cài đặt cả hai dịch vụ và cả hai đều không thành công theo cách giống nhau.

Cách đầu tiên là chỉ cần cài đặt bản sao đầu tiên của dịch vụ, sao chép thư mục đã cài đặt và đổi tên nó, sau đó chạy lệnh sau sau khi sửa đổi cấu hình ứng dụng để thay đổi tên dịch vụ mong muốn:

InstallUtil.exe /i AppService.exe

Khi điều đó không hiệu quả, tôi đã cố gắng tạo dự án trình cài đặt thứ hai, chỉnh sửa tệp cấu hình và xây dựng trình cài đặt thứ hai. Khi tôi chạy trình cài đặt, nó hoạt động tốt nhưng dịch vụ không hiển thị trong services.msc vì vậy tôi đã chạy lệnh trước đó đối với cơ sở mã được cài đặt thứ hai.

Cả hai lần tôi đều nhận được kết quả sau từ InstallUtil (chỉ các phần có liên quan):

Đang chạy một cài đặt đã giao dịch.

Bắt đầu giai đoạn Cài đặt của cài đặt.

Cài đặt dịch vụ App Service Two ... Service App Service Two đã được cài đặt thành công. Tạo EventLog source App Service Two in log Ứng dụng ...

Đã xảy ra ngoại lệ trong giai đoạn Cài đặt. System.NullReferenceException: Tham chiếu đối tượng không được đặt thành một thể hiện của đối tượng.

Giai đoạn khôi phục cài đặt đang bắt đầu.

Khôi phục nhật ký sự kiện về trạng thái trước đó cho Dịch vụ ứng dụng nguồn Hai. Dịch vụ Ứng dụng Dịch vụ Hai đang bị xóa khỏi hệ thống ... Ứng dụng Dịch vụ Dịch vụ Hai đã được xóa thành công khỏi hệ thống.

Giai đoạn khôi phục đã hoàn tất thành công.

Quá trình cài đặt được giao dịch đã hoàn tất. Quá trình cài đặt không thành công và quá trình khôi phục đã được thực hiện.

Xin lỗi vì bài viết dài dòng, muốn đảm bảo có đủ thông tin liên quan. Phần mà cho đến nay tôi vẫn băn khoăn là nó nói rằng quá trình cài đặt dịch vụ hoàn tất thành công và chỉ sau khi nó tạo nguồn EventLog mà NullReferenceException dường như bị ném. Vì vậy, nếu ai đó biết tôi đang làm gì sai hoặc có cách tiếp cận tốt hơn thì sẽ được đánh giá cao.

Câu trả lời:


81

Bạn đã thử sử dụng sc / service controller chưa? Kiểu

sc create

tại một dòng lệnh, và nó sẽ cung cấp cho bạn mục trợ giúp. Tôi nghĩ rằng tôi đã làm điều này trong quá khứ cho Subversion và sử dụng bài viết này như một tài liệu tham khảo:

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


5
Tôi tìm thấy trang này có ích: http://journalofasoftwaredev.wordpress.com/2008/07/16/multiple-instances-of-same-windows-service/. Bạn có thể chèn mã vào trình cài đặt để lấy tên dịch vụ mà bạn muốn khi chạy installutil.
Vivian River

9
Liên kết đến blog wordpress đã được đổi thành: journalofasoftwaredev.wordpress.com/2008/07
STLDev

21
  sc create [servicename] binpath= [path to your exe]

Giải pháp này đã làm việc cho tôi.


5
chỉ để chỉ ra; [path to your exe]đã trở thành đường dẫn đầy đủ và đừng quên không gian saubinpath=
MKB

2
Điều này thực sự cho phép một dịch vụ được cài đặt nhiều lần. Tuy nhiên, tất cả các thông tin được cung cấp bởi trình cài đặt dịch vụ. Mô tả Fe, gõ đăng nhập vv được bỏ qua
Noel Widmer

20

Bạn có thể chạy nhiều phiên bản của cùng một dịch vụ bằng cách thực hiện như sau:

1) Sao chép dịch vụ thực thi và cấu hình vào thư mục riêng của nó.

2) Sao chép Install.Exe vào thư mục thực thi dịch vụ (từ thư mục khung .net)

3) Tạo tệp cấu hình có tên Install.exe.config trong thư mục thực thi dịch vụ với nội dung sau (tên dịch vụ duy nhất):

<?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) Tạo một tệp hàng loạt để cài đặt dịch vụ với nội dung sau:

REM Install
InstallUtil.exe YourService.exe
pause

5) Trong khi bạn ở đó, hãy tạo một tệp loạt gỡ cài đặt

REM Uninstall
InstallUtil.exe -u YourService.exe
pause

BIÊN TẬP:

Lưu ý rằng nếu tôi bỏ lỡ điều gì đó, đây là Lớp ServiceInstaller (điều chỉnh theo yêu cầu):

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

Tôi nghĩ những gì bạn đang mô tả ít nhiều giống với những gì tôi đã làm bằng cách cho phép đặt ServiceName và DisplayName từ ứng dụng dịch vụ của tôi.
Switters

Tôi có một mẫu mà tôi sử dụng, tôi đã sử dụng từ lâu, vì vậy có thể tôi đã bỏ sót điều gì đó.
Mark Redman

Các trình cài đặt dịch vụ của chúng tôi thực sự gần giống nhau. Tôi sử dụng một lớp tĩnh để tải dịch vụ và tên hiển thị từ tệp cấu hình nhưng khác với việc chúng rất giống nhau. Dự đoán của tôi về lý do tại sao nó không hoạt động với tôi là có thể có điều gì đó hơi kỳ lạ về mã dịch vụ của chúng tôi. Rất nhiều bàn tay đã không may. Mặc dù vậy, từ những gì tôi hiểu, câu trả lời của bạn sẽ hiệu quả trong đa số trường hợp, cảm ơn sự giúp đỡ.
Switters

2
Cảm ơn rất nhiều giúp đỡ. Tôi nghĩ rằng tệp cấu hình cài đặt cần được đặt tên là InstallUtil.exe.confg chứ không phải Install.exe.config cho InstallUtil.exe
NullReference

Một cách tiếp cận tốt mà hoàn toàn hiệu quả. Đó là nếu bạn biết InstallUtil.exe nào để sao chép vào thư mục cài đặt của mình (Cá nhân tôi có rất nhiều phiên bản khung đã được cài đặt và các bản sao 64-bit ngày càng trầm trọng hơn). Điều này sẽ gây khó khăn cho việc giải thích với nhóm Helpdesk nếu họ thực hiện cài đặt. Nhưng đối với cài đặt do nhà phát triển dẫn đầu, nó rất thanh lịch.
timmi4sa

11

Câu hỏi cũ, tôi biết, nhưng tôi đã gặp may khi sử dụng tùy chọn / servicename trên InstallUtil.exe. Tuy nhiên, tôi không thấy nó được liệt kê trong phần trợ giúp tích hợp.

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

Tôi không hoàn toàn chắc chắn nơi đầu tiên tôi đọc về điều này nhưng tôi đã không nhìn thấy nó kể từ đó. YMMV.


3
Trả về lỗi này:An exception occurred during the Install phase. System.ComponentModel.Win32Exception: The specified service already exists
mkb 29/01

@mkb Bạn có một dịch vụ khác có tên "Dịch vụ của tôi" không?
Jonathon Watney

Có, như trong câu hỏi, tôi có một dịch vụ, cùng một tệp thực thi, nhưng tôi muốn cài đặt hai phiên bản của nó, mỗi phiên bản có cấu hình khác nhau. Tôi sao chép-dán exe dịch vụ nhưng cái này không hoạt động.
mkb

1
/ servicename = "My Service InstanceOne" và / servicename = "My Service InstanceTwo" Các tên phải là duy nhất.
granadaCoder

11

Một cách nhanh chóng khác để chỉ định giá trị tùy chỉnh cho ServiceNameDisplayNamelà sử dụng installutilcác tham số dòng lệnh.

  1. Trong ProjectInstallerlớp của bạn ghi đè các phương thức ảo Install(IDictionary stateSaver)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. Xây dựng dự án của bạn
  3. Cài đặt dịch vụ bằng installutilcách thêm tên tùy chỉnh của bạn bằng /servicenametham số:

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

Xin lưu ý rằng nếu bạn không chỉ định /servicenametrong dòng lệnh, dịch vụ sẽ được cài đặt với các giá trị ServiceName và DisplayName được chỉ định trong thuộc tính / cấu hình ProjectInstaller


2
Xuất sắc!! Cảm ơn bạn - đây chính xác là những gì cần thiết và quan trọng.
Iofacture

7

Tôi đã không gặp nhiều may mắn với các phương pháp trên khi sử dụng phần mềm triển khai tự động của chúng tôi để thường xuyên cài đặt / gỡ cài đặt các dịch vụ cửa sổ song song, nhưng cuối cùng tôi đã nghĩ ra điều sau cho phép tôi chuyển một tham số để chỉ định hậu tố đến tên dịch vụ trên dòng lệnh. Nó cũng cho phép nhà thiết kế hoạt động bình thường và có thể dễ dàng điều chỉnh để ghi đè toàn bộ tên nếu cần.

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

Với ý nghĩ này, tôi có thể làm như sau: Nếu tôi đã gọi dịch vụ là "Dịch vụ tuyệt vời" thì tôi có thể cài đặt một hệ thống UAT của dịch vụ như sau:

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

Điều này sẽ tạo ra dịch vụ với tên "Dịch vụ tuyệt vời - UAT". Chúng tôi đã sử dụng công cụ này để chạy các phiên bản DEVINT, TESTING và ACCEPTANCE của cùng một dịch vụ chạy song song trên một máy. Mỗi phiên bản đều có tập hợp tệp / cấu hình riêng - Tôi chưa thử điều này để cài đặt nhiều dịch vụ trỏ đến cùng một nhóm tệp.

LƯU Ý: bạn phải sử dụng cùng một /ServiceSuffixtham số để gỡ cài đặt dịch vụ, vì vậy bạn sẽ thực hiện các thao tác sau để gỡ cài đặt:

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


Điều đó thật tuyệt, nhưng điều đó chỉ dành cho trình cài đặt. Khi bạn có tên phiên bản mới, dịch vụ Windows sẽ biết về tên mới này như thế nào? Bạn có phải chuyển nó khi xây dựng dịch vụ Windows không?
progLearner

Cảm ơn! Trình cài đặt sẽ đặt tên trên Windows Service trong khi cài đặt nó bằng cách sử dụng các giá trị được đặt trong phương thức SetNames () ở trên.
tristankoffee

Chắc chắn rồi, nhưng làm thế nào bạn có thể đặt tên này từ thế giới bên ngoài?
progLearner

Trong câu trả lời của tôi là lệnh được sử dụng trên dòng lệnh để cài đặt (và gỡ cài đặt) dịch vụ ở thế giới bên ngoài. Giá trị bạn chuyển vào /ServiceSuffix="UAT"được trình cài đặt sử dụng để đặt hậu tố trên dịch vụ. Trong ví dụ của tôi, giá trị được truyền vào là UAT. Trong kịch bản của tôi, tôi chỉ muốn thêm một hậu tố vào tên hiện có của dịch vụ, nhưng không có lý do gì bạn không thể điều chỉnh điều này để thay thế hoàn toàn tên bằng giá trị được chuyển vào.
tristankoffee

Cảm ơn, nhưng đó là đầu vào dòng lệnh (= nhập thủ công), không phải mã. Theo câu hỏi ban đầu: Khi bạn có tên phiên bản mới, dịch vụ Windows sẽ biết về tên mới này như thế nào? Bạn có phải chuyển nó khi xây dựng dịch vụ Windows không?
progLearner

4

Những gì tôi đã làm để thực hiện công việc này là lưu trữ tên dịch vụ và tên hiển thị trong app.config cho dịch vụ của tôi. Sau đó, trong lớp trình cài đặt của tôi, tôi tải app.config dưới dạng XmlDocument và sử dụng xpath để lấy các giá trị ra và áp dụng chúng cho ServiceInstaller.ServiceName và ServiceInstaller.DisplayName, trước khi gọi InitializeComponent (). Điều này giả sử bạn chưa đặt các thuộc tính này trong InitializeComponent (), trong trường hợp đó, các cài đặt từ tệp cấu hình của bạn sẽ bị bỏ qua. Đoạn mã sau là những gì tôi đang gọi từ phương thức khởi tạo lớp trình cài đặt của mình, trước 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;
          }
      }

Tôi không tin rằng việc đọc trực tiếp tệp cấu hình từ ConfigurationManager.AppSettings hoặc thứ gì đó tương tự sẽ hoạt động vì khi trình cài đặt chạy, nó chạy trong ngữ cảnh của InstallUtil.exe, không phải .exe của dịch vụ của bạn. Bạn có thể thực hiện điều gì đó với ConfigurationManager.OpenExeConfiguration, tuy nhiên trong trường hợp của tôi, điều này không hoạt động vì tôi đang cố lấy phần cấu hình tùy chỉnh chưa được tải.


Chào Chris House! Tình cờ gặp câu trả lời của bạn vì tôi đang xây dựng một API Web dựa trên OWIN tự lưu trữ xung quanh bộ lập lịch Quartz.NET và gắn nó vào Dịch vụ Windows. Khá bóng bẩy! Hy vọng bạn khỏe mạnh!
NovaJoe

Chào Chris House! Tình cờ gặp câu trả lời của bạn vì tôi đang xây dựng một API Web dựa trên OWIN tự lưu trữ xung quanh bộ lập lịch Quartz.NET và gắn nó vào Dịch vụ Windows. Khá bóng bẩy! Hy vọng bạn khỏe mạnh!
NovaJoe

4

Chỉ để cải thiện câu trả lời hoàn hảo của @ chris.house.00 này , bạn có thể xem xét chức năng sau để đọc từ cài đặt ứng dụng của mình:

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

2

Tôi đã gặp trường hợp tương tự, trong đó tôi cần có một dịch vụ trước đó và một dịch vụ cập nhật chạy song song trên cùng một máy chủ. (Nó không chỉ là một thay đổi cơ sở dữ liệu, nó còn là những thay đổi mã). Vì vậy, tôi không thể chỉ chạy cùng một tệp .exe hai lần. Tôi cần một tệp .exe mới được biên dịch với các tệp DLL mới nhưng từ cùng một dự án. Chỉ cần thay đổi tên dịch vụ và tên hiển thị của dịch vụ không hoạt động đối với tôi, tôi vẫn nhận được "dịch vụ đã tồn tại lỗi" mà tôi tin là do tôi đang sử dụng Dự án triển khai. Những gì cuối cùng đã làm việc cho tôi là trong Thuộc tính Dự án Triển khai của tôi có một thuộc tính gọi là "Mã sản phẩm" là một Hướng dẫn.

nhập mô tả hình ảnh ở đây

Sau đó, xây dựng lại Dự án thiết lập thành .exe hoặc .msi mới đã được cài đặt thành công.


1

Cách tiếp cận đơn giản nhất là dựa trên tên dịch vụ trên tên 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;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.