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

Posted

技术标签:

【中文标题】.NET 控制台应用程序作为 Windows 服务【英文标题】:.NET console application as Windows service 【发布时间】:2011-12-07 12:34:17 【问题描述】:

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

也许有人可以建议可以快速轻松地将 c# 控制台应用程序转换为服务的类库或代码 sn-p?

【问题讨论】:

为什么不创建一个临时服务项目并复制使其成为服务的位? 你可以试试 Topshelf topshelf-project.com 你可以试试这里描述的技术:einaregilsson.com/2007/08/15/… 嗯?我不知道。关于这个。 一个非常简单的顶层替代方案:runasservice.com 【参考方案1】:

你可以使用

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

它会出现在服务列表中。我不知道,这是否正常工作。一个服务通常需要监听多个事件。

不过,有几个服务包装器可以将任何应用程序作为真正的服务运行。例如来自Win2003 Resource Kit的微软SrvAny

【讨论】:

如您所说,服务 exe 将需要与 windows 通信。 +1 链接到 SrvAny 我认为这种方法不安全。 Windows 具有用于管理服务的特殊库和实用程序,它们更有可能在不同的操作系统版本和环境中始终如一地工作。对于 .NET 应用程序,在 VS 中创建 MSI 安装程序非常容易。也可以使用 ManagedInstallerClass.InstallHelper 方法以编程方式执行安装。 不需要安装程序和其他东西:只需使用这个命令行:sc create MyServiceName binPath= "c:\path\to\service\file\exe"【参考方案2】:

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

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

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

为了改善您的服务,您还可以做另一件事,但这并不快速和/或容易,即使用 appdomains 并创建 dll 来加载/卸载。在一个中,您可以使用控制台应用程序启动一个新进程,而在另一个 dll 中,您可以只放置服务必须执行的功能。

祝你好运。

【讨论】:

【参考方案3】:

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

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

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

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

我还没有完全阅读它,但this article 似乎朝着正确的方向发展。

【讨论】:

【参考方案4】:

我听到你希望一个程序集停止重复代码的观点,但是,如果......你把它分成3 个组件。

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

【讨论】:

这就是我多年来一直这样做的方式 - 服务几乎有 Start()Stop() 方法,并且控制台应用程序有一个循环。不用像TopShelf 这样的框架,这是最好的选择 最同意这个答案。使用 3d 派对工具实现简单的解决方案使未来的维护变得不必要的复杂【参考方案5】:

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

using System.ServiceProcess

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。从技术上讲,可以在用户交互模式下运行服务,因此您可以检查命令行开关。

【讨论】:

你使用ServiceInstaller类,见msdn.microsoft.com/en-us/library/…。 这是意料之中的——您的服务将作为一个单独的进程运行(因此它会显示在任务管理器中),但该进程将由系统控制(例如,根据服务设置)。 如果您将其作为控制台应用程序运行,您将看不到服务。此代码的全部目的是使您能够将其作为控制台应用程序或作为服务运行。要作为服务运行,您需要先安装它(使用 ServiceInstaller 类 - 请参阅上面的 MSDN 链接 - 或 installuitil.exe),然后从控制面板运行服务。 ServiceInstaller 只是一个处理 Windows 服务的实用程序类(有点像 installutil.exe 或 sc.exe 实用程序)。您可以使用它来安装任何您想要的服务,操作系统并不关心您使用的项目类型。 只需在您的项目中添加一个对 System.ServiceProcess 的引用,您就可以使用上面的代码【参考方案6】:

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

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

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

Install-Package Topshelf

请参阅code samples 以开始使用。

例子:

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 为您完成这一切。

【讨论】:

嗨,我得到了这个:-“无法安装包 'Topshelf 4.0.1'。您正在尝试将此包安装到以 '.NETFramework,Version=v4.5' 为目标的项目中,但该包不包含任何与该框架兼容的程序集引用或内容文件。”这里有什么问题? 确保您的目标是完整的 .NET 4.5.2 运行时,而不是客户端配置文件。 请问您能否详细说明一下 myservice.exe 以及您要从哪个目录打开命令提示符 @Izuagbala myservice.exe 是您创建的控制台应用程序,其中包含 TopShelf 引导程序,如代码示例所示。 myservice.exe 安装为服务后能否作为控制台运行?文档不清楚:“一旦创建了控制台应用程序,开发人员就会创建单个服务类”docs.topshelf-project.com/en/latest/overview/…【参考方案7】:

首先我将控制台应用解决方案嵌入到windows服务解决方案中并引用。

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

/// <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 服务混合

【讨论】:

这基本上是 JonAlb 在上一个答案中所说的,但感谢代码示例【参考方案8】:

所以这里是完整的演练:

    创建新的控制台应用程序项目(例如 MyService) 添加两个库引用:System.ServiceProcess 和 System.Configuration.Install 添加下面打印的三个文件 构建项目并运行“InstallUtil.exe c:\path\to\MyService.exe” 现在您应该会在服务列表中看到 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 started1", DateTime.Now, Environment.NewLine));
        

        public static void Stop()
        
            File.AppendAllText(@"c:\temp\MyService.txt", String.Format("0 stopped1", 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);
        
    

【讨论】:

如果您正在为 64 位编译项目,您必须使用 64 位的 InstallUtil.exe,可在此处找到:C:\windows\Microsoft.NET\Framework64\... 版本对于 32 位 (C:\windows\Microsoft.NET\Framework) 将向您抛出 BadImageFormatException... 这很好用,请注意正如@snytek 所说,如果您使用的是base 64,请确保使用正确的目录。另外,如果您碰巧和我一样忘记将服务重命名为“MyService”以外的名称,请确保在更改代码之前卸载该服务。【参考方案9】:

我使用遵循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);

【讨论】:

【参考方案10】:

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

如果您从 Visual Studio 2019 创建工作服务,它将为您提供创建开箱即用的 Windows 服务所需的几乎所有内容,这也是您需要更改为控制台应用程序以便将其转换为Windows 服务。

以下是您需要做的更改:

安装以下 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);
        
    

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

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

【讨论】:

以上是关于.NET 控制台应用程序作为 Windows 服务的主要内容,如果未能解决你的问题,请参考以下文章

将 C# 控制台应用程序作为 Windows 服务运行

Windows 服务与 Windows 窗体在同一进程中

如何从 .NET 应用程序收集探查器数据

.NET 实现启动时重定向程序运行路径及 Windows 服务运行模式部署

Windows 7 中的用户交互服务

踩坑 Windows 服务来宿主 .NET 程序