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

16
我开发了一个C#应用程序,输出类型为控制台应用程序。我想将此应用程序作为服务运行。当我从Visual Studio或者双击.exe文件运行时,Environment.UserInteractive始终为true。

以下是我的代码:
 static void Main(string[] args)
        {
            // Get the version of the current application.
            Assembly assem = Assembly.GetExecutingAssembly();
            AssemblyName assemName = assem.GetName();
            Version ver = assemName.Version;
            // Console.WriteLine("{0}, Version {1}", assemName.Name, ver.ToString());

            Console.WriteLine("{0} version {1}", assemName.Name, ver.ToString());

            TouchService touchService = new TouchService();


            if (Environment.UserInteractive)
            {
                bool show_help = false;
                bool install_service = false;
                bool uninstall_service = false;
                string servicename = "";

                OptionSet p = new OptionSet()                  
                  .Add("h|?|help", delegate(string v) { show_help = v != null; })
                  .Add("s|servicename=", "name of installed service", delegate(string v) { servicename = v; })
                  .Add("i|install", "install program as a Windows Service. A valid servicename is needed.", delegate(string v) { install_service = v != null; })
                  .Add("u|uninstall", "uninstall program from Windows Services. A valid servicename is needed.", delegate(string v) { uninstall_service = v != null; });

                List<string> extra;
                try
                {
                    extra = p.Parse(args);
                }
                catch (OptionException e)
                {
                    Console.Write("TouchServer: ");
                    Console.WriteLine(e.Message);
                    Console.WriteLine("Try `TouchServer --help' for more information.");
                    return;
                }

                if (show_help)
                {
                    ShowHelp(p);
                    return;
                }

                else if (install_service)
                {
                    IntegratedServiceInstaller Inst = new IntegratedServiceInstaller();
                    Inst.Install(servicename, null, "Provides XML data over HTTP for Touch clients",                                                              
                                 System.ServiceProcess.ServiceAccount.NetworkService,
                                 System.ServiceProcess.ServiceStartMode.Manual);

                    return;
                }

                else if (uninstall_service)
                {
                    IntegratedServiceInstaller Inst = new IntegratedServiceInstaller();
                    Inst.Uninstall(servicename);
                    return;
                }

                // start and run the server,
                // and receive commands from the console
                else
                {

                    touchService.OnStart(args);                   
                    while(true)
                    {
                        Console.Write("TouchServer>");                        
                        string commandLine = Console.ReadLine().ToLower();

                        if (commandLine == "exit" || commandLine == "x")
                        {
                            break;
                        }
                        if (commandLine == "quit" || commandLine == "q")
                        {
                            break;
                        }

                        else if(commandLine == "version" || commandLine == "v")
                        {
                            Console.WriteLine("{0} version {1}", assem.GetName().Name, assem.GetName().Version.ToString());
                        }

                        else if (commandLine == "list" || commandLine == "l")
                        {
                            TouchServer.showURLs = (TouchServer.showURLs == false) ? true : false; 
                            Console.WriteLine("List URLs: {0}", (TouchServer.showURLs ? "active" : "inactive"));
                        }

                        else if (commandLine == "status" || commandLine == "s")
                        {
                            Console.WriteLine("{0,-20} {1,8}", "Name", "Sessions");                            
                            Console.WriteLine("----------------------------");
                            foreach (Site site in TouchServer.siteCollection.All)
                            {
                                Console.WriteLine("{0,-20} {1,8}", site.Name, site.AllSessions.Length);
                            }
                            Console.WriteLine();
                        }
                    }

                    touchService.OnStop();
                }
            }
            **else
            {
                ServiceBase[] ServicesToRun;
                ServicesToRun = new ServiceBase[] 
                { 
                    new TouchService() 
                };
                ServiceBase.Run(ServicesToRun);
            }**

请问如何将它作为一个服务运行?谢谢! Sangita


1
删除控制台中与服务安装相关的所有内容,以及其他检查交互性等内容。我想这不是你的代码,否则你可能不会在掌握了你的代码中写的如此高级的任务后,在这里回答如此简单的问题 ;) - Alan Turing
4个回答

17

使用 文件->新建项目->Visual C#->Windows->Windows 服务

然后将主要代码添加到 OnStart() 和 OnStop() 事件处理程序中,并将其安装为服务:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;

namespace MyWindowsService
{
    public partial class Service1 : ServiceBase
    {
        public Service1()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
        }

        protected override void OnStop()
        {
        }
    }
}

7
以下是我使用的解决方案,它在Visual Studio 2012和.NET 4.5下表现出色。当我以控制台模式运行时,它能完美工作;当我使用installutil.exe将其作为服务运行时,它也能完美工作。

主文件

文件名为MainPayload.cs。您可以忽略其他所有文件,并将您的长时间运行代码插入到此文件中。请注意CancellationTokenSource,这样您的服务就可以在服务停止时快速退出。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
//using Gurock.SmartInspect; // Only used if we are logging using SmartInspect (see www.SmartInspect.com).
namespace Demos___Service_Plus_Console
{
    /// <summary>
    /// Main entry point for both console and Windows service.
    /// </summary>
    public class MainPayload
    {
        private readonly CancellationTokenSource _cancellationTokenSource;

        /// <summary>
        /// Constructor; do not block in this call; it is for setup only.
        /// </summary>
        public MainPayload(CancellationTokenSource cancellationTokenSource)
        {
            // Do not block in this call; it is for setup only.
            _cancellationTokenSource = cancellationTokenSource;
        }   
        /// <summary>
        /// Long running task here.
        /// </summary>
        public void Run()
        {
            while (_cancellationTokenSource.IsCancellationRequested == false)
            {
                //SiAuto.Main.LogMessage(".");
                Console.WriteLine(".");

                // This will break every N seconds, or immediately if on cancellation token.
                _cancellationTokenSource.Token.WaitHandle.WaitOne(TimeSpan.FromSeconds(1));
            }
            //SiAuto.Main.LogMessage("Exited Run().");
            Console.WriteLine("Exited Run().");
            Thread.Sleep(500); // If we remove this line, then we will miss the final few writes to the console.
        }
    }
}

支持文件

文件EntryPoint.cs。这是控制台应用程序和服务的入口点。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
//using Gurock.SmartInspect; // Only used if we are logging using SmartInspect (see www.SmartInspect.com).
namespace Demos___Service_Plus_Console
{
    internal static class EntryPoint
    {
        // Run in console mode.
        private static readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
        private static Task _task;

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        public static void Main(string[] args)
        {
            //SiAuto.Si.Connections = "pipe(reconnect=\"true\", reconnect.interval=\"10\", backlog.enabled=\"true\", backlog.flushon=\"debug\", backlog.keepopen=\"true\")";
            //SiAuto.Si.Enabled = true;

            if (Environment.UserInteractive)
            {
                // Make sure that we can write to the console.
                StreamWriter standardOutput = new StreamWriter(Console.OpenStandardOutput()) {AutoFlush = true};
                Console.SetOut(standardOutput);

                // If Ctrl-C is pressed in the console, we get to here.
                Console.CancelKeyPress += new ConsoleCancelEventHandler(myHandler);

                MainPayload myMain = new MainPayload(_cancellationTokenSource); // Pass the token into the task.
                _task = Task.Run(() => myMain.Run());

                // Wait for the payload task to finish.
                while (_cancellationTokenSource.IsCancellationRequested == false)
                {
                    // This will break every N seconds, or immediately if cancellation token is pinged.
                    _cancellationTokenSource.Token.WaitHandle.WaitOne(TimeSpan.FromSeconds(10));
                }
            }
            else
            {
                // Run as Windows Service.
                var ServicesToRun = new ServiceBase[]
                    {
                        new ServiceController()
                    };
                ServiceBase.Run(ServicesToRun);
            }

            _task.Wait(TimeSpan.FromSeconds(10)); // Delay for console to write its final output.
        }

        static void myHandler(object sender, ConsoleCancelEventArgs args)
        {
            _cancellationTokenSource.Cancel();
            //SiAuto.Main.LogMessage("CtrlC pressed.");
            Console.WriteLine("CtrlC pressed.");
        }
    }
}

文件ProjectInstaller.cs。这是服务的安装程序。

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
using System.Threading.Tasks;

namespace Demos___Service_Plus_Console
{
    [RunInstaller(true)]
    public partial class ProjectInstaller : System.Configuration.Install.Installer
    {
        public ProjectInstaller()
        {
            InitializeComponent();
        }

        private void serviceInstaller1_AfterInstall(object sender, InstallEventArgs e)
        {

        }

        private void serviceProcessInstaller1_AfterInstall(object sender, InstallEventArgs e)
        {

        }
    }
}

文件 ServiceController.cs。这个文件包含了服务的Start()Stop()方法。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Demos___Service_Plus_Console
{
    /// <summary>
    /// When running in service mode.
    /// </summary>
    public partial class ServiceController : ServiceBase
    {
        public ServiceController()
        {
            InitializeComponent();
        }

        readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

        // Initialize payload.
        private MainPayload myMain;

        protected override void OnStart(string[] args)
        {
            myMain = new MainPayload(cancellationTokenSource); // Pass the token into the task.
            Task.Run(() => myMain.Run());
        }

        protected override void OnStop()
        {
            cancellationTokenSource.Cancel();
        }
    }
}

如何安装为Windows服务

要将此安装为Windows服务,请使用installutil.exe ServiceName.exe。 要卸载,请使用installutil.exe ServiceName.exe -u。 这意味着您必须打开一个Visual Studio 2012 x32命令提示符,该提示符位于开始菜单Visual Studio工具下。 如果您以32位模式编译,请使用32位命令提示符,如果您以64位模式编译,请打开64位命令提示符(这意味着路径已正确设置,因为installutil.exe具有根据其是32位还是64位而不同的版本)。

打开服务面板,然后查找名为ServiceController的新服务。 当您启动它时,如果您开启了日志记录框架,则会看到每秒钟记录一次日志的消息。

我是如何创建此代码的

看起来像是很多代码,但它都基于Visual Studio 2012中的Windows服务项目模板。 我创建了一个新的Windows服务,然后使用Environment.UserInteracive在控制台或服务之间切换。 我添加了CancellationTokens以确保如果停止了服务,则任务也将快速停止(即使它处于延迟状态)。

唯一的技巧是右键单击“Service1.cs”页面的灰色背景,然后单击“添加安装程序”以添加安装程序。 如果不这样做,则installutil.exe将会给您一个错误。

enter image description here

您还必须右键单击serviceProcessInstaller1,然后选择属性并将帐户设置为LocalService,否则它将在安装服务时要求您提供用户名凭据

这里是额外所需的引用(使用模板创建新的Windows服务时会自动添加):

  • System.ServiceProcess
  • System.Configuration.Install

您会注意到对Gurock.SmartInspectSiAuto的引用。 如果您想观察运行服务创建的日志,则可以使用此类工具或其他工具,例如NLog或log4net。


我成功地使用了你的实现 - 谢谢,还有一个赞! - OnoSendai

5

只有当程序作为服务运行时,Environment.UserInteractive才会为false。当你双击它或从Visual Studio启动它时,它会作为普通控制台应用程序运行,并且桌面对其可用,因此Environment.UserInteractive为true。

您可以从Squiggle的代码库中继承ConsoleService类,以创建一个既可以作为控制台应用程序又可以作为Windows服务运行的应用程序。

public class ConsoleService : ServiceBase
{
    public void RunConsole(string[] args)
    {
        Trace.Listeners.Add(new ConsoleTraceListener());
        OnStart(args);
        Trace.WriteLine(this.ServiceName + " running... Press any key to stop");
        Trace.WriteLine("");
        Console.ReadKey();
        OnStop();
    }

    public static void Run<TService>(string[] args) where TService : ConsoleService, new()
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        if (Environment.UserInteractive)
        {
            try
            {
                string option = args.Length > 0 ? args[0].ToUpperInvariant() : String.Empty;
                switch (option)
                {
                    case "-I":
                    case "/I":
                        ManagedInstallerClass.InstallHelper(new string[] { Assembly.GetCallingAssembly().Location });
                        break;
                    case "-U":
                    case "/U":
                        ManagedInstallerClass.InstallHelper(new string[] { "/U", Assembly.GetCallingAssembly().Location });
                        break;
                    default:
                        new TService().RunConsole(args);
                        break;
                }
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex.Message);
            }
        }
        else
        {
            ServiceBase[] servicesToRun = new ServiceBase[] { new TService() };
            ServiceBase.Run(servicesToRun);
        }
    }

    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        if (e.ExceptionObject is Exception)
            Trace.WriteLine(((Exception)e.ExceptionObject).Message);
    }
}

+1,我觉得他特别需要的是服务行为,而不是控制台行为。 - Alan Turing

5

我刚在Visual Studio 2013中测试了这个,它可以工作。

  1. 创建新的“Windows服务”项目。
  2. 将项目输出类型更改为“控制台应用程序”

这是我的Program.cs文件的样子:

static class Program
{
  /// <summary>
  /// </summary>
  static void Main()
  {
    if (!Environment.UserInteractive)
    {
      ServiceBase[] ServicesToRun;
      ServicesToRun = new ServiceBase[] 
          { 
              new Service1() 
          };
      ServiceBase.Run(ServicesToRun);
    }
    else
    {
      Console.Write("Hit any key to continue...");
      Console.ReadKey();
    }
  }
}

您可以像这样添加服务安装:链接

您可以使用以下命令安装服务:installutil.exe /i 您的exe文件名.exe

您可以使用以下命令卸载服务:installutil.exe /u 您的exe文件名.exe


如果这个功能没有副作用,那就很简短、干净。不错! - Cameron

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