.NET控制台应用程序作为Windows服务


145

我有控制台应用程序,想作为Windows服务运行。VS2010具有项目模板,该模板允许附加控制台项目并构建Windows服务。我不希望添加单独的服务项目,如果可能的话,请将服务代码集成到控制台应用程序中,以将控制台应用程序保留为一个项目,该项目可以作为控制台应用程序运行,也可以作为Windows服务运行(例如,使用命令行从命令行运行)。

也许有人会建议类库或代码片段,这些类库或代码片段可以快速轻松地将C#控制台应用程序转换为服务?


您为什么不只创建一个临时服务项目并复制使其成为服务的位?
加布

4
您可以尝试Topshelf topshelf-project.com
Artem Koshelev 2011年

您可以尝试此处描述的技术:einaregilsson.com/2007/08/15/…– 2011

嗯?我不确定。对这个。

2
一个非常简单的顶层替代方案:runasservice.com
路易斯·佩雷斯

Answers:


185

我通常使用以下技术来与控制台应用程序或服务运行相同的应用程序:

public static class Program
{
    #region Nested classes to support running as service
    public const string ServiceName = "MyService";

    public class Service : ServiceBase
    {
        public Service()
        {
            ServiceName = Program.ServiceName;
        }

        protected override void OnStart(string[] args)
        {
            Program.Start(args);
        }

        protected override void OnStop()
        {
            Program.Stop();
        }
    }
    #endregion

    static void Main(string[] args)
    {
        if (!Environment.UserInteractive)
            // running as service
            using (var service = new Service())
                ServiceBase.Run(service);
        else
        {
            // running as console app
            Start(args);

            Console.WriteLine("Press any key to stop...");
            Console.ReadKey(true);

            Stop();
        }
    }

    private static void Start(string[] args)
    {
        // onstart code here
    }

    private static void Stop()
    {
        // onstop code here
    }
}

Environment.UserInteractive通常对于控制台应用为true,对于服务为false。从技术上讲,可以在用户交互模式下运行服务,因此您可以改为检查命令行开关。


3
您使用ServiceInstaller类,请参见msdn.microsoft.com/en-us/library/…
VladV 2014年

2
可以预期-您的服务将作为一个单独的进程运行(因此它将显示在任务管理器中),但是该进程将由系统控制(例如,根据服务设置启动,停止,重新启动)。
VladV '16

2
如果将其作为控制台应用程序运行,则不会看到服务。此代码的全部目的是使您可以将其作为控制台应用程序或服务运行。要作为服务运行,您需要先安装它(使用ServiceInstaller类-请参阅上面的MSDN链接-或installuitil.exe),然后从控制面板运行该服务。
VladV '16

2
ServiceInstaller只是用于处理Windows服务的实用程序类(有点类似于installutil.exe或sc.exe实用程序)。您可以使用它来安装任何您想作为服务的东西,操作系统并不关心您使用的项目类型。
VladV

5
只要在您的项目中添加对System.ServiceProcess的引用,您就可以使用上面的代码
danimal

59

我在TopShelf上取得了巨大的成功。

TopShelf是一个Nuget软件包,旨在使创建可作为控制台应用程序或Windows服务运行的.NET Windows应用程序变得容易。您可以快速挂起事件,例如服务的启动和停止事件,使用代码进行配置,例如设置运行帐户,配置对其他服务的依赖关系,以及配置如何从错误中恢复。

从程序包管理器控制台(Nuget):

安装包Topshelf

请参考代码示例以开始使用。

例:

HostFactory.Run(x =>                                 
{
    x.Service<TownCrier>(s =>                        
    {
       s.ConstructUsing(name=> new TownCrier());     
       s.WhenStarted(tc => tc.Start());              
       s.WhenStopped(tc => tc.Stop());               
    });
    x.RunAsLocalSystem();                            

    x.SetDescription("Sample Topshelf Host");        
    x.SetDisplayName("Stuff");                       
    x.SetServiceName("stuff");                       
}); 

TopShelf还负责服务安装,这可以节省大量时间,并从解决方案中删除样板代码。要将.exe作为服务安装,只需在命令提示符下执行以下命令:

myservice.exe install -servicename "MyService" -displayname "My Service" -description "This is my service."

您无需连接ServiceInstaller,所有这些-TopShelf会为您完成所有这些工作。


1
嗨,我得到这个:-“无法安装软件包'Topshelf 4.0.1'。您正在尝试将此软件包安装到以'.NETFramework,Version = v4.5'为目标的项目中,但是该软件包不包含任何软件包。与该框架兼容的程序集引用或内容文件。” 这是怎么了

3
确保您的目标是完整的.NET 4.5.2运行时,而不是客户端配置文件。
Saille

请你能投入更多的光在myservice.exe和从目录中你要打开命令提示符
Izuagbala

1
@Izuagbala myservice.exe是您创建的控制台应用程序,如代码示例所示,将TopShelf引导至其中。
Saille

作为服务安装后,myservice.exe可以作为控制台运行吗?文档是不明确的:“一旦控制台应用程序创建,开发人员创建一个单一的服务类” docs.topshelf-project.com/en/latest/overview/...
迈克尔Freidgeim

27

这是完整的演练:

  1. 创建新的控制台应用程序项目(例如MyService)
  2. 添加两个库引用:System.ServiceProcess和System.Configuration.Install
  3. 添加下面打印的三个文件
  4. 生成项目并运行“ InstallUtil.exe c:\ path \ to \ MyService.exe”
  5. 现在,您应该在服务列表上看到MyService(运行services.msc)

*通常可以在这里找到InstallUtil.exe:C:\ windows \ Microsoft.NET \ Framework \ v4.0.30319 \ InstallUtil.ex‌ e

Program.cs

using System;
using System.IO;
using System.ServiceProcess;

namespace MyService
{
    class Program
    {
        public const string ServiceName = "MyService";

        static void Main(string[] args)
        {
            if (Environment.UserInteractive)
            {
                // running as console app
                Start(args);

                Console.WriteLine("Press any key to stop...");
                Console.ReadKey(true);

                Stop();
            }
            else
            {
                // running as service
                using (var service = new Service())
                {
                    ServiceBase.Run(service);
                }
            }
        }

        public static void Start(string[] args)
        {
            File.AppendAllText(@"c:\temp\MyService.txt", String.Format("{0} started{1}", DateTime.Now, Environment.NewLine));
        }

        public static void Stop()
        {
            File.AppendAllText(@"c:\temp\MyService.txt", String.Format("{0} stopped{1}", DateTime.Now, Environment.NewLine));
        }
    }
}

MyService.cs

using System.ServiceProcess;

namespace MyService
{
    class Service : ServiceBase
    {
        public Service()
        {
            ServiceName = Program.ServiceName;
        }

        protected override void OnStart(string[] args)
        {
            Program.Start(args);
        }

        protected override void OnStop()
        {
            Program.Stop();
        }
    }
}

MyServiceInstaller.cs

using System.ComponentModel;
using System.Configuration.Install;
using System.ServiceProcess;

namespace MyService
{
    [RunInstaller(true)]
    public class MyServiceInstaller : Installer
    {
        public MyServiceInstaller()
        {
            var spi = new ServiceProcessInstaller();
            var si = new ServiceInstaller();

            spi.Account = ServiceAccount.LocalSystem;
            spi.Username = null;
            spi.Password = null;

            si.DisplayName = Program.ServiceName;
            si.ServiceName = Program.ServiceName;
            si.StartType = ServiceStartMode.Automatic;

            Installers.Add(spi);
            Installers.Add(si);
        }
    }
}

1
如果要为64位编译项目,则必须使用64位的InstallUtil.exe,可以在这里找到:C:\ windows \ Microsoft.NET \ Framework64 \ ... 32位的版本(C:\ windows \ Microsoft.NET \ Framework)将向您抛出BadImageFormatException ...
snytek

这非常有效,请注意,如@snytek所说,如果您使用的是base 64,请确保使用正确的目录。另外,如果您碰巧与我做同样的事情而忘记将服务重命名为“ MyService”以外的名称,请确保在对代码进行更改之前先卸载服务。
dmoore1181

3

我听到您的意思是希望一个程序集停止重复的代码,但是,如果将……分成3个程序集,这将是最简单的方法,可以减少代码重复,并在以后使它更容易以其他方式重用代码。

  1. 一个完成所有工作的库程序集。然后有两个非常苗条/简单的项目:
  2. 一个是命令行
  3. 一种是Windows服务。

1
这就是我做了很多年-服务几乎具有Start()Stop()方法与控制台应用程序有一个循环。缺少使用像TopShelf这样的框架,这是最好的选择
Basic

最同意这个答案。为简单的解决方案使用3d派对工具会使将来的维护变得不必要的复杂
tatigo

3

这是基于最新的.Net Core 3.1将控制台应用程序转换为Windows服务作为工作程序服务的更新方法。

如果从Visual Studio 2019创建辅助服务,则将为您提供开箱即用创建Windows服务所需的几乎所有内容,这也是将控制台应用程序转换为Windows Service所需的内容。

这是您需要做的更改:

安装以下NuGet软件包

Install-Package Microsoft.Extensions.Hosting.WindowsServices -Version 3.1.0
Install-Package Microsoft.Extensions.Configuration.Abstractions -Version 3.1.0

更改Program.cs以实现如下所示的实现:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ConsoleApp
{
    class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).UseWindowsService().Build().Run();
        }

        private static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<Worker>();
                });
    }
}

并添加Worker.cs,您将在其中放置将由服务操作运行的代码:

using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp
{
    public class Worker : BackgroundService
    {
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            //do some operation
        }

        public override Task StartAsync(CancellationToken cancellationToken)
        {
            return base.StartAsync(cancellationToken);
        }

        public override Task StopAsync(CancellationToken cancellationToken)
        {
            return base.StopAsync(cancellationToken);
        }
    }
}

当一切准备就绪,并且应用程序已成功构建后,可以使用sc.exe通过以下命令将控制台应用程序exe作为Windows服务安装:

sc.exe create DemoService binpath= "path/to/your/file.exe"

2

您可以使用

reg add HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run /v ServiceName /d "c:\path\to\service\file\exe"

它将出现在服务列表中。我不知道这是否正确。服务通常必须侦听几个事件。

但是,有几个服务包装程序可以将任何应用程序作为真实服务运行。例如,Win2003资源工具包中的 Microsoft SrvAny


如您所说,服务exe将需要与Windows通信。+1链接到SrvAny
Jodrell

5
我认为这种方法不安全。Windows具有特殊的库和实用程序来管理服务,它们更有可能在不同的OS版本和环境中一致地工作。对于.NET应用程序,在VS中创建MSI安装程序非常容易。使用ManagedInstallerClass.InstallHelper方法以编程方式执行安装也是可能的。
VladV

1
无需安装程序和东西:只需使用以下命令行:sc create MyServiceName binPath =“” c:\ path \ to \ service \ file \ exe“
JDC

2

首先,我将控制台应用程序解决方案嵌入Windows服务解决方案中,并对其进行引用。

然后我将控制台应用程序Program类公开

/// <summary>
/// Hybrid service/console application
/// </summary>
public class Program
{
}

然后,我在控制台应用程序中创建两个函数

    /// <summary>
    /// Used to start as a service
    /// </summary>
    public void Start()
    {
        Main();
    }

    /// <summary>
    /// Used to stop the service
    /// </summary>
    public void Stop()
    {
       if (Application.MessageLoop)
            Application.Exit();   //windows app
        else
            Environment.Exit(1);  //console app
    }

然后,在Windows服务本身中,我实例化程序并调用在OnStart和OnStop中添加的Start和Stop函数。见下文

class WinService : ServiceBase
{
    readonly Program _application = new Program();

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static void Main()
    {
        ServiceBase[] servicesToRun = { new WinService() };
        Run(servicesToRun);
    }

    /// <summary>
    /// Set things in motion so your service can do its work.
    /// </summary>
    protected override void OnStart(string[] args)
    {
        Thread thread = new Thread(() => _application.Start());
        thread.Start();
    }

    /// <summary>
    /// Stop this service.
    /// </summary>
    protected override void OnStop()
    {
        Thread thread = new Thread(() => _application.Stop());
        thread.Start();
    }
}

这种方法也可以用于Windows应用程序/ Windows Service混合


这基本上是JonAlb在上一个答案中所说的,但感谢代码示例
tatigo

0

据我所知,也许您应该定义所需的内容,但您不能同时将应用程序作为控制台或带命令行的服务运行。请记住,该服务已安装,您必须在Services Manager中启动它,您可以创建一个新应用程序,以启动服务或启动运行控制台应用程序的新进程。但是正如你写的

“将控制台应用程序保留为一个项目”

有一次,我在您的位置上,将控制台应用程序转换为服务。首先,如果要使用VS Express Edition,则需要模板。这里是您可以迈出第一步的链接:C#Windows Service,这对我很有帮助。然后使用该模板,将代码添加到所需的服务事件中。

为了改善您的服务,您还可以做另一件事,但这不是快速和/或轻松的,它是使用appdomains并创建要加载/卸载的dll。在其中一个中,您可以使用控制台应用程序启动一个新过程,在另一个dll中,您可以仅放置该服务必须执行的功能。

祝好运。


0

您需要将功能分成一个或多个类,然后通过两个存根之一启动它。控制台存根或服务存根。

显而易见,运行Windows时,构成基础结构的各种服务不会(也不能直接)向用户提供控制台窗口。服务需要以非图形方式与用户通信:通过SCM;在事件日志中,到一些日志文件等。该服务还需要通过SCM与Windows通信,否则它将关闭。

拥有一些可以与服务通信的控制台应用程序显然是可以接受的,但是该服务需要独立运行,而无需进行GUI交互。

Console存根对于调试服务行为非常有用,但是不应在“生产化”的环境中使用,毕竟,这是创建服务的目的。

我还没有完全阅读它,但是这篇文章似乎朝着正确的方向发展。


0

我使用的服务类遵循所规定的标准模式ServiceBase,并借助助手简化F5调试。这样可以在服务中定义服务数据,使它们易于查找并且易于管理。

我通常使用以下结构创建Windows应用程序。我不创建控制台应用程序;这样,每次运行该应用程序时,我的脸上都不会弹出黑框。我留在调试器中执行所有操作。我使用Debug.WriteLine消息使消息进入输出窗口,该窗口很好地停靠在应用程序终止后仍然可见。

我通常不费心添加调试代码来停止;我只是使用调试器。如果确实需要调试停止,则将项目设置为控制台应用程序,添加Stop转发器方法,然后在调用之后调用它Console.ReadKey

public class Service : ServiceBase
{
    protected override void OnStart(string[] args)
    {
        // Start logic here.
    }

    protected override void OnStop()
    {
        // Stop logic here.
    }

    static void Main(string[] args)
    {
        using (var service = new Service()) {
            if (Environment.UserInteractive) {
                service.Start();
                Thread.Sleep(Timeout.Infinite);
            } else
                Run(service);
        }
    }
    public void Start() => OnStart(null);
}
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.