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

172

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


2
@slayernoah,链接似乎已经失效了。你有其他的参考资料吗? - Macindows
5个回答

268
在服务项目中进行以下操作:
  1. 在解决方案资源管理器中双击服务.cs文件。这应该会带出一个全灰色的屏幕,并讲述从工具箱拖动东西的内容。
  2. 然后右键单击灰色区域并选择添加安装程序。这将向您的项目添加一个安装程序工程文件。
  3. 然后,您将在ProjectInstaller.cs的设计视图上获得2个组件(serviceProcessInstaller1和serviceInstaller1)。然后,您应该设置属性,例如服务名称和应运行为的用户等。
现在您需要创建一个安装程序项目。最好的方法是使用安装向导。
  1. 右键单击您的解决方案并添加新项目:添加 > 新建项目 > 安装和部署项目 > 安装向导

    a.不同版本的Visual Studio可能略有不同。 b.Visual Studio 2010位于:安装模板 > 其他项目类型 > 安装和部署 > Visual Studio Installer

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

  3. 在第三步中,选择“来自...的主要输出”

  4. 一路点击完成。

接下来编辑您的安装程序以确保正确的输出被包含在内。
  1. 右键单击解决方案资源管理器中的安装程序项目。
  2. 选择“查看” > “自定义操作”。(在VS2008中可能是“查看” > “编辑器” > “自定义操作”)
  3. 在自定义操作树中,右键单击安装操作并选择“添加自定义操作...”
  4. 在“项目中选择项目”对话框中,选择“应用程序文件夹”并单击确定。
  5. 单击确定以选择“来自...的主要输出”选项。新节点应该被创建。
  6. 对于提交,回滚和卸载操作,请重复4-5步骤。

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

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


40
@El Ronnoco,我在发布前就已经有了答案。我想在这里记录下来,因为我总是每6至12个月需要查找它(而且很难找到),现在我可以方便地搜索到它,让每个人都能找到,并且我自己也能快速找到它 :) - Kelsey
1
不幸的是,这也是错误的答案。是的,我知道你会在书本和MSDN中找到这个答案,但这是一个微软内部的一个小组没有与另一个小组交流,提出了一个次优解决方案的案例,而这个问题已经被解决了。更多信息请参见http://blog.iswix.com/2006/07/msi-vs-net.html。 - Christopher Painter
11
@Christopher Painter 我自2k5年以来一直在使用MS Installer,它从未出现过问题。无论您是否同意并认为它是一种“反模式”,这不是这个问题的重点,问题是如何使用y来完成x,而不是如何使用b来完成a。当我发布这个问题时,是为了文档目的。 - Kelsey
3
那么你已经有了6年的好运,只是你不知道。你可能想阅读:http://robmensching.com/blog/posts/2007/4/19/Managed-Code-CustomActions-no-support-on-the-way-and-heres - Christopher Painter
5
对于使用VS 2019,您需要下载此链接中的内容:https://marketplace.visualstudio.com/items?itemName=VisualStudioClient.MicrosoftVisualStudio2017InstallerProjects。请注意,不要改变原文的意思。 - Akbar Asghari
显示剩余4条评论

54

我按照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;
}

1
出于好奇,拥有自安装/自卸载服务的好处是什么?如果服务自行安装,那么如何首次启动服务以便首先安装它?如果有一种机制可以在不安装服务的情况下启动服务,为什么还要安装它呢? - Kiley Naro
3
@Christopher - 我并不是。我的解决方案并不能替代你用于分发软件的完整安装程序。我提供了另一种选项,适用于某些情况,比如我编写的软件驱动无人值守亭内嵌入式电脑。 - user10256
4
在生产机器上安装时,请记得以管理员身份运行它。我创建了一个批处理文件,用 /i 参数调用 EXE 文件,但在生产环境中它不起作用,即使我以管理员身份执行了批处理文件。我不得不以管理员身份打开命令行提示符,并显式地调用 EXE 文件 /i(不使用批处理文件)。至少在 Windows Server 2012 上对我是这样的。 - Francisco Goldenstein
1
回复:命令行没有输出。使用VS 2017 Community创建的新服务项目默认的输出类型为“Windows应用程序”,启动对象为“(none)”。我不得不将输出类型更改为“控制台应用程序”,并设置我的启动对象,例如“myservice.Program”。如果有我不知道的影响,请告知。 - Jonathan
1
示例代码有错别字吗?为什么有三种不同的服务(CSMessageQueueService,MyService和MyQueueService)? - Nils Guillermin
显示剩余9条评论

30

无论是 Kelsey 还是 Brendan 的解决方案,在 Visual Studio 2015 Community 中都不适用于我。

以下是我创建带有安装程序的服务的简要步骤:

  1. 运行 Visual Studio,选择 文件->新建->项目
  2. 选择 .NET Framework 4,在“搜索已安装的模板”中输入“Service”
  3. 选择“Windows 服务”。输入名称和位置。按下 确定
  4. 双击 Service1.cs,在设计器中右键单击并选择“添加安装程序”
  5. 双击 ProjectInstaller.cs。对于 serviceProcessInstaller1,打开属性选项卡并将“帐户”属性值更改为“LocalService”。对于 serviceInstaller1,请更改“ServiceName”并将“StartType”设置为“自动”。
  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:

  9. :::::::::::::::::::::::::::::::::::::::::
    :: 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
    
  10. 创建uninstall.bat文件(在倒数第二行中将/i更改为/u
  11. 要安装并启动服务,请运行install.bat,要停止并卸载,请运行uninstall.bat

16

0

InstallUtil类(ServiceInstaller)被Windows Installer社区认为是一种反模式。它是一个脆弱的、进程外的、重复造轮子的工具,忽略了Windows Installer已经内置了对服务的支持。

Visual Studio部署项目(在下一个版本的Visual Studio中也不受高度评价并且已被弃用)没有原生支持服务。但是它们可以使用合并模块。因此,我建议您查看这篇博客文章,了解如何使用Windows Installer XML创建一个可以表达服务的合并模块,并在您的VDPROJ解决方案中使用该合并模块。

使用Windows Installer XML增强InstallShield - Windows服务

IsWiX Windows服务教程

IsWiX Windows服务视频


1
在旧版的Visual Studio中,有一个部署项目,可以轻松创建安装程序。现在我必须购买第三方软件组件吗? - Alexey Obukhov
@AlexeyObukhov 你可以免费使用Wix,这也是VS本身使用的,但Wix的问题与Git的问题相同-陡峭的学习曲线。 - Alan B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接