【问题标题】:How to create an installer for a .net Windows Service using Visual Studio如何使用 Visual Studio 为 .net Windows 服务创建安装程序
【发布时间】:2012-02-19 17:18:10
【问题描述】:

如何为使用 Visual Studio 创建的 Windows 服务创建安装程序?

【问题讨论】:

标签: c# .net visual-studio windows-services windows-installer


【解决方案1】:

在服务项目中执行以下操作:

  1. 在解决方案资源管理器中双击您的服务 .cs 文件。它应该会调出一个全灰的屏幕,并谈论从工具箱中拖动东西。
  2. 然后右键单击灰色区域并选择添加安装程序。这会将安装程序项目文件添加到您的项目中。
  3. 然后您将在 ProjectInstaller.cs 的设计视图中有 2 个组件(serviceProcessInstaller1 和 serviceInstaller1)。然后,您应该根据需要设置属性,例如服务名称和运行用户。

现在您需要创建一个设置项目。最好的办法是使用设置向导。

  1. 右键单击您的解决方案并添加一个新项目:添加 > 新项目 > 设置和部署项目 > 设置向导

    一个。对于不同版本的 Visual Studio,这可能会略有不同。 湾。 Visual Studio 2010 它位于:安装模板 > 其他项目类型 > 设置和部署 > Visual Studio 安装程序

  2. 在第二步中选择“为 Windows 应用程序创建设置”。

  3. 在第三步中,选择“Primary output from...”

  4. 点击完成。

接下来编辑您的安装程序以确保包含正确的输出。

  1. 在解决方案资源管理器中右键单击设置项目。
  2. 选择视图 > 自定义操作。 (在 VS2008 中可能是 View > Editor > Custom Actions)
  3. 右键单击自定义操作树中的安装操作并选择“添加自定义操作...”
  4. 在“选择项目中的项目”对话框中,选择应用程序文件夹并单击确定。
  5. 单击“确定”以选择“主要输出来自...”选项。应该创建一个新节点。
  6. 对提交、回滚和卸载操作重复第 4 - 5 步。

您可以通过右键单击解决方案中的安装程序项目并选择属性来编辑安装程序输出名称。将“输出文件名:”更改为您想要的任何内容。通过选择安装程序项目并查看属性窗口,您可以编辑Product NameTitleManufacturer 等...

接下来构建您的安装程序,它将生成一个 MSI 和一个 setup.exe。选择您想使用的任何一个来部署您的服务。

【讨论】:

  • @El Ronnoco,早在我发布之前我就有了答案。我想在这里记录它,因为我总是必须每 6 到 12 个月查找一次(而且它并不那么容易找到)所以现在我可以很容易地为每个人搜索它,我可以自己快速找到它 :)
  • 不幸的是,这也是错误的答案。是的,我知道您会在书籍和 MSDN 中找到这一点,但在这种情况下,Microsoft 的一个小组没有与 Microsoft 的另一个小组交谈,并且针对已经解决的问题提出了一个较差的解决方案。请参阅blog.iswix.com/2006/07/msi-vs-net.html 了解更多信息。
  • @Christopher Painter 自 2k5 以来我一直在使用 MS 安装程序,它从来没有出现过问题。无论您是否同意并认为它是“反模式”都不是这个问题的重点,而是我如何用 y 做 x,而不是如何用 b 做 a。当我发布问题时,它是出于文档目的。
  • 那么你已经幸运了 6 年了,你只是不知道而已。您可能想阅读:robmensching.com/blog/posts/2007/4/19/…
  • 对于 VS 2019 你必须先下载这个marketplace.visualstudio.com/…
【解决方案2】:

我按照 Kelsey 的第一组步骤将安装程序类添加到我的服务项目中,但我没有创建 MSI 或 setup.exe 安装程序,而是让服务自行安装/卸载。这是我的一项服务中的一些示例代码,您可以将其用作起点。

public static int Main(string[] args)
{
    if (System.Environment.UserInteractive)
    {
        // we only care about the first two characters
        string arg = args[0].ToLowerInvariant().Substring(0, 2);

        switch (arg)
        {
            case "/i":  // install
                return InstallService();

            case "/u":  // uninstall
                return UninstallService();

            default:  // unknown option
                Console.WriteLine("Argument not recognized: {0}", args[0]);
                Console.WriteLine(string.Empty);
                DisplayUsage();
                return 1;
        }
    }
    else
    {
        // run as a standard service as we weren't started by a user
        ServiceBase.Run(new CSMessageQueueService());
    }

    return 0;
}

private static int InstallService()
{
    var service = new MyService();

    try
    {
        // perform specific install steps for our queue service.
        service.InstallService();

        // install the service with the Windows Service Control Manager (SCM)
        ManagedInstallerClass.InstallHelper(new string[] { Assembly.GetExecutingAssembly().Location });
    }
    catch (Exception ex)
    {
        if (ex.InnerException != null && ex.InnerException.GetType() == typeof(Win32Exception))
        {
            Win32Exception wex = (Win32Exception)ex.InnerException;
            Console.WriteLine("Error(0x{0:X}): Service already installed!", wex.ErrorCode);
            return wex.ErrorCode;
        }
        else
        {
            Console.WriteLine(ex.ToString());
            return -1;
        }
    }

    return 0;
}

private static int UninstallService()
{
    var service = new MyQueueService();

    try
    {
        // perform specific uninstall steps for our queue service
        service.UninstallService();

        // uninstall the service from the Windows Service Control Manager (SCM)
        ManagedInstallerClass.InstallHelper(new string[] { "/u", Assembly.GetExecutingAssembly().Location });
    }
    catch (Exception ex)
    {
        if (ex.InnerException.GetType() == typeof(Win32Exception))
        {
            Win32Exception wex = (Win32Exception)ex.InnerException;
            Console.WriteLine("Error(0x{0:X}): Service not installed!", wex.ErrorCode);
            return wex.ErrorCode;
        }
        else
        {
            Console.WriteLine(ex.ToString());
            return -1;
        }
    }

    return 0;
}

【讨论】:

  • 出于好奇,拥有自安装/卸载服务有什么好处?如果服务自己安装,你如何首先启动服务以便可以首先安装它?如果有一种无需安装即可启动服务的机制,为什么还要安装呢?
  • @Christopher - 我不知道。我的解决方案不能替代您用来分发软件的完整安装程序。我正在介绍另一种适用于某些情况的选项,例如我在无人值守信息亭中编写驱动嵌入式 PC 的软件。
  • 在生产机器上安装时,记得以管理员身份运行。我创建了一个 BAT 文件,它使用 /i 参数调用 EXE 文件,但它在生产环境中不起作用,即使我以管理员身份执行了 BAT 文件。我必须以管理员身份打开命令行提示符并显式调用 EXE 文件 /i(不使用 BAT 文件)。至少这发生在我的 Windows Server 2012 上。
  • RE:命令行无输出。使用 VS 2017 社区,我的新服务项目默认为输出类型:Windows Application 和启动对象:(none)。我必须将输出类型更改为Console Application 并设置我的启动对象,例如myservice.Program。如果可能有我不知道的后果,请告知。
  • 示例代码有错别字吗?为什么会有三种不同的服务(CSMessageQueueService、MyService、MyQueueService)?
【解决方案3】:

Kelsey 和 Brendan 的解决方案在 Visual Studio 2015 社区中都不适合我。

这是我如何使用安装程序创建服务的简要步骤:

  1. 运行 Visual Studio,转到 文件->新建->项目
  2. 选择 .NET Framework 4,在“搜索已安装的模板”中输入 “服务”
  3. 选择“Windows 服务”。键入名称和位置。按确定
  4. 双击Service1.cs,在设计器中右击并选择“添加安装程序”
  5. 双击 ProjectInstaller.cs。对于 serviceProcessInstaller1,打开“属性”选项卡并将“帐户”属性值更改为“本地服务”。对于 serviceInstaller1,更改“ServiceName”并将“StartType”设置为“Automatic”。
  6. 双击 serviceInstaller1。 Visual Studio 创建 serviceInstaller1_AfterInstall 事件。编写代码:

    private void serviceInstaller1_AfterInstall(object sender, InstallEventArgs e)
    {
        using (System.ServiceProcess.ServiceController sc = new 
        System.ServiceProcess.ServiceController(serviceInstaller1.ServiceName))
        {
            sc.Start();
        }
    }
    
  7. 构建解决方案。右键单击项目并选择“在文件资源管理器中打开文件夹”。转到 bin\Debug

  8. 使用以下脚本创建 install.bat:

    :::::::::::::::::::::::::::::::::::::::::
    :: Automatically check & get admin rights
    :::::::::::::::::::::::::::::::::::::::::
    @echo off
    CLS 
    ECHO.
    ECHO =============================
    ECHO Running Admin shell
    ECHO =============================
    
    :checkPrivileges 
    NET FILE 1>NUL 2>NUL
    if '%errorlevel%' == '0' ( goto gotPrivileges ) else ( goto getPrivileges ) 
    
    :getPrivileges 
    if '%1'=='ELEV' (shift & goto gotPrivileges)  
    ECHO. 
    ECHO **************************************
    ECHO Invoking UAC for Privilege Escalation 
    ECHO **************************************
    
    setlocal DisableDelayedExpansion
    set "batchPath=%~0"
    setlocal EnableDelayedExpansion
    ECHO Set UAC = CreateObject^("Shell.Application"^) > "%temp%\OEgetPrivileges.vbs" 
    ECHO UAC.ShellExecute "!batchPath!", "ELEV", "", "runas", 1 >> "%temp%\OEgetPrivileges.vbs" 
    "%temp%\OEgetPrivileges.vbs" 
    exit /B 
    
    :gotPrivileges 
    ::::::::::::::::::::::::::::
    :START
    ::::::::::::::::::::::::::::
    setlocal & pushd .
    
    cd /d %~dp0
    %windir%\Microsoft.NET\Framework\v4.0.30319\InstallUtil /i "WindowsService1.exe"
    pause
    
  9. 创建uninstall.bat 文件(将pen-ult 行/i 更改为/u
  10. 安装和启动服务运行install.bat,停止和卸载运行uninstall.bat

【讨论】:

    【解决方案4】:

    对于 VS2017,您需要添加“Microsoft Visual Studio 2017 安装程序项目”VS 扩展。这将为您提供额外的 Visual Studio 安装程序项目模板。 https://marketplace.visualstudio.com/items?itemName=VisualStudioProductTeam.MicrosoftVisualStudio2017InstallerProjects#overview

    要安装 Windows 服务,您可以添加一个新的安装向导类型项目并按照 Kelsey 的回答 https://stackoverflow.com/a/9021107/1040040 中的步骤进行操作

    【讨论】:

      【解决方案5】:

      InstallUtil 类 ( ServiceInstaller ) 被 Windows Installer 社区视为反模式。这是一个脆弱的、超出流程的、重新发明轮子的过程,忽略了 Windows Installer 内置了对服务的支持这一事实。

      Visual Studio 部署项目(在 Visual Studio 的下一版本中也没有受到高度重视和弃用)没有对服务的本机支持。但是他们可以使用合并模块。所以我会看一下这篇博客文章,了解如何使用 Windows Installer XML 创建一个合并模块,该模块可以表达服务,然后在您的 VDPROJ 解决方案中使用该合并模块。

      Augmenting InstallShield using Windows Installer XML - Windows Services

      IsWiX Windows Service Tutorial

      IsWiX Windows Service Video

      【讨论】:

      • 在旧的 Visual Studio 中有一个部署项目,带有易于创建的安装程序。现在我必须购买第三方软件组件?
      • @AlexeyObukhov 你可以免费使用Wix,这就是VS本身使用的,但是Wix的问题和Git的问题一样——接近垂直的学习曲线。
      猜你喜欢
      • 2012-09-17
      • 2023-01-24
      • 1970-01-01
      • 2010-09-12
      • 1970-01-01
      • 1970-01-01
      • 2011-12-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多