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


Answers:


227

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

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

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

  1. 右键单击您的解决方案并添加一个新项目:Add> New Project> Setup and Deployment Projects> Setup Wizard

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

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

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

  4. 单击以完成。

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

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

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

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


37
@El Ronnoco,我早就发布了答案。我想在这里记录它,因为我总是必须每6到12个月查一次它(并不是那么容易找到),所以现在我可以轻松地对所有人搜索它,而且我自己也可以很快找到它:)
Kelsey 2012年

1
不幸的是,这也是错误的答案。是的,我知道您会在书本和MSDN中找到它,但是在这种情况下,Microsoft的一个小组没有与Microsoft的另一个小组进行交谈,并且提出了一个较差的解决方案来解决已经解决的问题。有关更多信息,请参见blog.iswix.com/2006/07/msi-vs-net.html
Christopher Painter 2012年

9
@Christopher Painter从2k5开始,我一直在使用MS安装程序,它从来没有出现过问题。无论您是否同意并认为它是“反模式”,这都不是这个问题的重点,它是如何用y来执行x,而不是如何使用b来执行a。当我发布问题时,它只是出于文档目的。
Kelsey 2012年

3
然后,您已经6年不知所措了。您可能需要阅读:robmensching.com/blog/posts/2007/4/19/…–
Christopher Painter

1
如果Service name contains invalid characters, is empty, or is too long (max length = 80)添加安装程序时出现错误,请再次在灰色区域中单击鼠标右键,转到“属性”并确保已设置“服务名称”值。
wolfyuk

51

我遵循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 2012年

3
@Christopher-我不知道。我的解决方案不能替代您用于分发软件的完整安装程序。我提出了适用于某些情况的另一种选择,例如我编写的软件可以在无人值守的自助服务终端中驱动嵌入式PC。

4
在生产计算机上安装它时,请记住以管理员身份运行它。我创建了一个BAT文件,该文件使用/ i参数调用EXE文件,但是即使以管理员身份执行BAT文件,该文件也无法在生产环境中运行。我必须以管理员身份打开命令行提示符,并显式调用EXE文件/ i(不使用BAT文件)。至少,这发生在我身上在Windows Server 2012中
旧金山Goldenstein

1
RE:命令行无输出。使用VS 2017社区,我的新服务项目默认为输出类型:Windows Application和启动对象:(none)。我必须将Output type更改为Console Application并设置启动对象,例如myservice.Program。如果我不知道可能存在的后果,请告知。
乔纳森

1
示例代码是否有错字?为什么会有三种不同的服务(CSMessageQueueService,MyService,MyQueueService)?
Nils Guillermin

27

Nor Kelsey和Brendan解决方案均不适用于Visual Studio 2015社区。

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

  1. 运行Visual Studio,转到 File->New->Project
  2. 选择.NET Framework 4,在“搜索安装的模板”中键入“服务”
  3. 选择“ Windows服务”。键入名称和位置。按OK
  4. 双击Service1.cs,在设计器中单击鼠标右键,然后选择“添加安装程序”
  5. 双击ProjectInstaller.cs。对于serviceProcessInstaller1,打开“属性”选项卡,然后将“帐户”属性值更改为“ LocalService”。对于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文件(将第二行更改/i/u
  10. 要安装和启动服务,请运行install.bat,要停止和卸载,请运行uninstall.bat


1

Windows Installer社区将InstallUtil类(ServiceInstaller)视为反模式。这是一个脆弱的过程,没有经过重新设计,完全忽略了Windows Installer内置对服务的支持这一事实。

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

使用Windows Installer XML增强InstallShield-Windows Services

IsWiX Windows服务教程

IsWiX Windows服务视频


1
在旧的Visual Studio中,有一个部署项目,带有易于创建的安装程序。现在我必须购买第三方软件组件吗?
Alexey Obukhov

@AlexeyObukhov您可以免费使用Wix,这是VS本身使用的,但是Wix的问题与Git的问题相同-接近垂直的学习曲线。
艾伦B
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.