将控制台应用程序转换为服务?

时间:2022-10-26 18:09:27

I'm looking for different ways with strengths/weaknesses for converting a console application we are using long term in to a windows service. We use something called java service wrapper for ActiveMQ, and I believe people have told me you can wrap anything with it. That's not saying that you should wrap anything with it though; we've had our issues with this setup.

我正在寻找不同的优势/劣势,将我们长期使用的控制台应用程序转换为Windows服务。我们为ActiveMQ使用了一个叫做java服务包装器的东西,我相信人们告诉我你可以用它包装任何东西。这并不是说你应该用它包装任何东西;我们遇到了这个问题。

The console app is a .NET console application that by default logs a lot of info to the console, though this is configurable.

控制台应用程序是一个.NET控制台应用程序,默认情况下会将大量信息记录到控制台,尽管这是可配置的。

Any reccomendations?

Should we just rebuild it in Visual Studio as a service? Use a wrapper? Which one?

我们应该在Visual Studio中将其重建为服务吗?使用包装?哪一个?

5 个解决方案

#1


I would be tempted to create an empty windows service project, and just grab the bits that deal with a service; it isn't a lot - a few references and some of the code in Main. You can actually have your existing console work both as a service and as a console - either by checking the args to Main and using (for example) a "-console" switch, or I believe you can check Environment.UserInteractive.

我很想创建一个空的Windows服务项目,只需抓住处理服务的位;它不是很多 - 一些参考和Main中的一些代码。实际上,您可以将现有控制台作为服务和控制台工作 - 通过将args检查为Main并使用(例如)“-console”开关,或者我相信您可以检查Environment.UserInteractive。

If it is in "console" mode, run your code as you do now; if it is in service mode, run the code you grabbed from the template project.

如果它处于“控制台”模式,请像现在一样运行代码;如果它处于服务模式,请运行您从模板项目中获取的代码。

For info, you can also have the same exe work as the installer/uninstaller for the service! I do this with "-install" / "-uninstall" switches. For an example, see here.

有关信息,您还可以使用与服务的安装程序/卸载程序相同的exe工作!我使用“-install”/“-uninstall”开关执行此操作。有关示例,请参见此处。

#2


Vici WinService will turn a console app into a self-installing Windows Service. It's open-source and you can download the source code. Even if you don't want to use the library, you can still get some ideas from it.

Vici WinService会将控制台应用程序转换为自安装Windows服务。它是开源的,您可以下载源代码。即使您不想使用该库,您仍然可以从中获得一些想法。

#3


Some thoughts:

Create windows service with VS 2005

使用VS 2005创建Windows服务

install .Net Service

安装.Net服务

I wrote some couple of years ago a Perl based set of executables ( theads ) etc. , which seems to have similar requirements to yours ...

几年前我写了一套基于Perl的可执行文件(theads)等,它们似乎与你的要求相似......

Some stuff to keep in mind:

要注意的一些事情:

  • do have debuggin switch ( you should have one when something goes really wrong )
  • 有调试开关(当出现问题时你应该有一个)

  • output to both console and files ( try log4net for example )
  • 输出到控制台和文件(例如尝试log4net)

  • build your logging with future regex parsing in mind
  • 使用未来的正则表达式解析来构建您的日志记录

  • if there are some inderdependant processes , learn how to kill them , stop and restart them
  • 如果有一些inderdependant进程,学习如何杀死它们,停止并重新启动它们

  • if there are some inderdependant processes try to communicate to them
  • 如果有一些inderdependant进程尝试与他们沟通

Here is a small Console example to output to db, file and console with log4net

这是一个使用log4net输出到db,file和console的小型控制台示例

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using log4net;
    using log4net.Config;
    using NUnit.Framework;

    namespace ExampleConsoleApplication
    {
        [TestFixture]
        class TestClass
        {

        //private static readonly ILog logger =
        //     LogManager.GetLogger ( typeof ( TestClass ) );

        private static readonly log4net.ILog logger = log4net.LogManager.GetLogger ( System.Reflection.MethodBase.GetCurrentMethod ().DeclaringType );

            static void Main ( string[] args )
            {

                Console.WriteLine ( " START " );
                #region LoggerUsage
                DOMConfigurator.Configure (); //tis configures the logger 
                logger.Debug ( "Here is a debug log." );
                logger.Info ( "... and an Info log." );
                logger.Warn ( "... and a warning." );
                logger.Error ( "... and an error." );
                logger.Fatal ( "... and a fatal error." );

                #endregion LoggerUsage
                TestClass objTestClass = new TestClass();
                objTestClass.TestMethodNameOK ();
                objTestClass.TestMethodNameNOK ();

                Console.WriteLine ( " END HIT A KEY TO EXIT " );
                Console.ReadLine ();
                } //eof method 

            [SetUp]
            protected void SetUp ()
            {
                //Add Here the Initialization of the objects 
            }
            [Test ( Description = "Add here the description of this test method " )]
            protected void TestMethodNameOK ()
            { 
                //build ok use case scenario here - e.g. no exception should be raced '
                //Vegetable newCarrot = pool.GetItemByPropertyValue<Vegetable> ( "WriongByPurpose", "Orange" );
                //Assert.IsInstanceOfType ( typeof ( Vegetable ), newCarrot );
                //Assert.AreSame ( newCarrot, carrot );
                //logger.Info ( " I got the newCarrot which is " + newCarrot.Color );

            } //eof method 

            [Test ( Description = "Add here the description of this test method " )]
            protected void TestMethodNameNOK ()         //e.g. the one that should raze Exception
            {
                //build ok use case scenario here - e.g. no exception should be raced '
                //Vegetable newCarrot = pool.GetItemByPropertyValue<Vegetable> ( "WriongByPurpose", "Orange" );
                //Assert.IsInstanceOfType ( typeof ( Vegetable ), newCarrot );
                //Assert.AreSame ( newCarrot, carrot );
                //logger.Info ( " I got the newCarrot which is " + newCarrot.Color );

            } //eof method 

        } //eof class 

    } //eof namespace 





    #region TheAppConfig
//    <?xml version="1.0" encoding="utf-8" ?>
//<configuration>
//  <configSections>
//    <section name="log4net"
//         type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
//  </configSections>

//  <log4net>
//    <appender name="LogFileAppender" type="log4net.Appender.FileAppender">
//      <param name="File" value="Program.log" />
//      <param name="AppendToFile" value="true" />
//      <layout type="log4net.Layout.PatternLayout">
//        <!--<param name="Header" value="======================================" />
//        <param name="Footer" value="======================================" />-->
//        <param name="ConversionPattern" value="%d [%t] %-5p - %m%n" />
//      </layout>
//    </appender>

//    <appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
//      <mapping>
//        <level value="ERROR" />
//        <foreColor value="Red" />
//      </mapping>
//      <mapping>
//        <level value="DEBUG" />
//        <foreColor value="HighIntensity" />
//      </mapping>
//      <mapping>
//        <level value="INFO" />
//        <foreColor value="Green" />
//      </mapping>
//      <mapping>
//        <level value="WARN" />
//        <foreColor value="Yellow" />
//      </mapping>
//      <mapping>
//        <level value="FATAL" />
//        <foreColor value="White" />
//        <backColor value="Red" />
//      </mapping>

//      <layout type="log4net.Layout.PatternLayout">
//        <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
//      </layout>
//    </appender>


//    <appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
//      <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.2.10.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
//      <connectionString value="data source=ysg;initial catalog=DBGA_DEV;integrated security=true;persist security info=True;" />
//      <commandText value="INSERT INTO [DBGA_DEV].[ga].[tb_Data_Log] ([Date],[Thread],[Level],[Logger],[Message]) VALUES (@log_date, @thread, @log_level, @logger, @message)" />

//      <parameter>
//        <parameterName value="@log_date" />
//        <dbType value="DateTime" />
//        <layout type="log4net.Layout.PatternLayout" value="%date{yyyy'.'MM'.'dd HH':'mm':'ss'.'fff}" />
//      </parameter>
//      <parameter>
//        <parameterName value="@thread" />
//        <dbType value="String" />
//        <size value="255" />
//        <layout type="log4net.Layout.PatternLayout" value="%thread" />
//      </parameter>
//      <parameter>
//        <parameterName value="@domainName" />
//        <dbType value="String" />
//        <size value="255" />
//        <layout type="log4net.Layout.PatternLayout" value="%user" />
//      </parameter>
//      <parameter>
//        <parameterName value="@log_level" />
//        <dbType value="String" />
//        <size value="50" />
//        <layout type="log4net.Layout.PatternLayout" value="%level" />
//      </parameter>
//      <parameter>
//        <parameterName value="@logger" />
//        <dbType value="String" />
//        <size value="255" />
//        <layout type="log4net.Layout.PatternLayout" value="%logger" />
//      </parameter>
//      <parameter>
//        <parameterName value="@message" />
//        <dbType value="String" />
//        <size value="4000" />
//        <layout type="log4net.Layout.PatternLayout" value="%message" />
//      </parameter>
//    </appender>
//    <root>
//      <level value="ALL" />
//      <appender-ref ref="LogFileAppender" />
//      <appender-ref ref="AdoNetAppender" />
//      <appender-ref ref="ColoredConsoleAppender" />
//    </root>
//  </log4net>
//</configuration>
    #endregion TheAppconfig

    //this is the xml added replace here your log4net and Nunit paths
    //<Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL">
    //  <SpecificVersion>False</SpecificVersion>
    //  <HintPath>..\..\..\Log4Net\log4net-1.2.10\bin\net\2.0\release\log4net.dll</HintPath>
    //</Reference>
    //<Reference Include="nunit.framework, Version=2.4.8.0, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL" />

#4


What is your long-term usage scenario? A windows service might suffice...but Windows Server 2008/IIS7 provide some intriguing new ways of hosting and activating "services" via Windows Activation Services. A Windows Service will always be running, and may require some special coding. With WAS, you can write your host as a normal WCF service, and have it activated on demand when requests come in, and deactivated when it is not used. Other options also exist...such as MSMQ hosting and instantiation, etc.

您的长期使用情况是什么? Windows服务可能就足够了......但是Windows Server 2008 / IIS7通过Windows激活服务提供了一些有趣的新方法来托管和激活“服务”。 Windows服务将始终运行,可能需要一些特殊编码。使用WAS,您可以将主机编写为普通的WCF服务,并在请求进入时按需激活,在不使用时停用。还存在其他选项......例如MSMQ托管和实例化等。

#5


I ran into this same issue, I ended up writing my own wrapper, it's only good for the simplest situations but it does have it's perks. You can find the tool here: http://runasservice.com. Some of the perks include the fact that you can code your application as a console application which is easy to test and run in the IDE. Setting it up a service involves one simple command so you don't have to edit your application. Also you can install the service multiple times with different names, which you might want to do if you want to run each with different parameters.

我遇到了同样的问题,我最终编写了自己的包装器,它只适用于最简单的情况,但确实有它的特权。你可以在这里找到这个工具:http://runasservice.com。一些额外的好处包括您可以将应用程序编码为控制台应用程序,该应用程序易于在IDE中进行测试和运行。设置服务涉及一个简单的命令,因此您无需编辑应用程序。此外,您可以使用不同的名称多次安装服务,如果要使用不同的参数运行每个名称,可能需要执行此操作。

Like I said though it's only covers the simplest scenarios, applications that are already essentially services. That is they run continuously. I'm sure there are a lot of other services out there that give you a lot more options.

就像我说的那样,它只涵盖了最简单的场景,应用程序本质上就是服务。那就是他们不断奔跑。我相信还有很多其他服务可以为您提供更多选择。

Personally I don't think it's particularly difficult to convert a console application, but it can be a hassle to test. At the end though it depends on how much control you want. If it's a really important service for your company, then I would say convert it.

就个人而言,我认为转换控制台应用程序并不是特别困难,但测试起来可能很麻烦。最后,虽然这取决于你想要多少控制。如果这对贵公司来说是一项非常重要的服务,那么我会说转换它。

#1


I would be tempted to create an empty windows service project, and just grab the bits that deal with a service; it isn't a lot - a few references and some of the code in Main. You can actually have your existing console work both as a service and as a console - either by checking the args to Main and using (for example) a "-console" switch, or I believe you can check Environment.UserInteractive.

我很想创建一个空的Windows服务项目,只需抓住处理服务的位;它不是很多 - 一些参考和Main中的一些代码。实际上,您可以将现有控制台作为服务和控制台工作 - 通过将args检查为Main并使用(例如)“-console”开关,或者我相信您可以检查Environment.UserInteractive。

If it is in "console" mode, run your code as you do now; if it is in service mode, run the code you grabbed from the template project.

如果它处于“控制台”模式,请像现在一样运行代码;如果它处于服务模式,请运行您从模板项目中获取的代码。

For info, you can also have the same exe work as the installer/uninstaller for the service! I do this with "-install" / "-uninstall" switches. For an example, see here.

有关信息,您还可以使用与服务的安装程序/卸载程序相同的exe工作!我使用“-install”/“-uninstall”开关执行此操作。有关示例,请参见此处。

#2


Vici WinService will turn a console app into a self-installing Windows Service. It's open-source and you can download the source code. Even if you don't want to use the library, you can still get some ideas from it.

Vici WinService会将控制台应用程序转换为自安装Windows服务。它是开源的,您可以下载源代码。即使您不想使用该库,您仍然可以从中获得一些想法。

#3


Some thoughts:

Create windows service with VS 2005

使用VS 2005创建Windows服务

install .Net Service

安装.Net服务

I wrote some couple of years ago a Perl based set of executables ( theads ) etc. , which seems to have similar requirements to yours ...

几年前我写了一套基于Perl的可执行文件(theads)等,它们似乎与你的要求相似......

Some stuff to keep in mind:

要注意的一些事情:

  • do have debuggin switch ( you should have one when something goes really wrong )
  • 有调试开关(当出现问题时你应该有一个)

  • output to both console and files ( try log4net for example )
  • 输出到控制台和文件(例如尝试log4net)

  • build your logging with future regex parsing in mind
  • 使用未来的正则表达式解析来构建您的日志记录

  • if there are some inderdependant processes , learn how to kill them , stop and restart them
  • 如果有一些inderdependant进程,学习如何杀死它们,停止并重新启动它们

  • if there are some inderdependant processes try to communicate to them
  • 如果有一些inderdependant进程尝试与他们沟通

Here is a small Console example to output to db, file and console with log4net

这是一个使用log4net输出到db,file和console的小型控制台示例

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using log4net;
    using log4net.Config;
    using NUnit.Framework;

    namespace ExampleConsoleApplication
    {
        [TestFixture]
        class TestClass
        {

        //private static readonly ILog logger =
        //     LogManager.GetLogger ( typeof ( TestClass ) );

        private static readonly log4net.ILog logger = log4net.LogManager.GetLogger ( System.Reflection.MethodBase.GetCurrentMethod ().DeclaringType );

            static void Main ( string[] args )
            {

                Console.WriteLine ( " START " );
                #region LoggerUsage
                DOMConfigurator.Configure (); //tis configures the logger 
                logger.Debug ( "Here is a debug log." );
                logger.Info ( "... and an Info log." );
                logger.Warn ( "... and a warning." );
                logger.Error ( "... and an error." );
                logger.Fatal ( "... and a fatal error." );

                #endregion LoggerUsage
                TestClass objTestClass = new TestClass();
                objTestClass.TestMethodNameOK ();
                objTestClass.TestMethodNameNOK ();

                Console.WriteLine ( " END HIT A KEY TO EXIT " );
                Console.ReadLine ();
                } //eof method 

            [SetUp]
            protected void SetUp ()
            {
                //Add Here the Initialization of the objects 
            }
            [Test ( Description = "Add here the description of this test method " )]
            protected void TestMethodNameOK ()
            { 
                //build ok use case scenario here - e.g. no exception should be raced '
                //Vegetable newCarrot = pool.GetItemByPropertyValue<Vegetable> ( "WriongByPurpose", "Orange" );
                //Assert.IsInstanceOfType ( typeof ( Vegetable ), newCarrot );
                //Assert.AreSame ( newCarrot, carrot );
                //logger.Info ( " I got the newCarrot which is " + newCarrot.Color );

            } //eof method 

            [Test ( Description = "Add here the description of this test method " )]
            protected void TestMethodNameNOK ()         //e.g. the one that should raze Exception
            {
                //build ok use case scenario here - e.g. no exception should be raced '
                //Vegetable newCarrot = pool.GetItemByPropertyValue<Vegetable> ( "WriongByPurpose", "Orange" );
                //Assert.IsInstanceOfType ( typeof ( Vegetable ), newCarrot );
                //Assert.AreSame ( newCarrot, carrot );
                //logger.Info ( " I got the newCarrot which is " + newCarrot.Color );

            } //eof method 

        } //eof class 

    } //eof namespace 





    #region TheAppConfig
//    <?xml version="1.0" encoding="utf-8" ?>
//<configuration>
//  <configSections>
//    <section name="log4net"
//         type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
//  </configSections>

//  <log4net>
//    <appender name="LogFileAppender" type="log4net.Appender.FileAppender">
//      <param name="File" value="Program.log" />
//      <param name="AppendToFile" value="true" />
//      <layout type="log4net.Layout.PatternLayout">
//        <!--<param name="Header" value="======================================" />
//        <param name="Footer" value="======================================" />-->
//        <param name="ConversionPattern" value="%d [%t] %-5p - %m%n" />
//      </layout>
//    </appender>

//    <appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
//      <mapping>
//        <level value="ERROR" />
//        <foreColor value="Red" />
//      </mapping>
//      <mapping>
//        <level value="DEBUG" />
//        <foreColor value="HighIntensity" />
//      </mapping>
//      <mapping>
//        <level value="INFO" />
//        <foreColor value="Green" />
//      </mapping>
//      <mapping>
//        <level value="WARN" />
//        <foreColor value="Yellow" />
//      </mapping>
//      <mapping>
//        <level value="FATAL" />
//        <foreColor value="White" />
//        <backColor value="Red" />
//      </mapping>

//      <layout type="log4net.Layout.PatternLayout">
//        <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
//      </layout>
//    </appender>


//    <appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
//      <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.2.10.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
//      <connectionString value="data source=ysg;initial catalog=DBGA_DEV;integrated security=true;persist security info=True;" />
//      <commandText value="INSERT INTO [DBGA_DEV].[ga].[tb_Data_Log] ([Date],[Thread],[Level],[Logger],[Message]) VALUES (@log_date, @thread, @log_level, @logger, @message)" />

//      <parameter>
//        <parameterName value="@log_date" />
//        <dbType value="DateTime" />
//        <layout type="log4net.Layout.PatternLayout" value="%date{yyyy'.'MM'.'dd HH':'mm':'ss'.'fff}" />
//      </parameter>
//      <parameter>
//        <parameterName value="@thread" />
//        <dbType value="String" />
//        <size value="255" />
//        <layout type="log4net.Layout.PatternLayout" value="%thread" />
//      </parameter>
//      <parameter>
//        <parameterName value="@domainName" />
//        <dbType value="String" />
//        <size value="255" />
//        <layout type="log4net.Layout.PatternLayout" value="%user" />
//      </parameter>
//      <parameter>
//        <parameterName value="@log_level" />
//        <dbType value="String" />
//        <size value="50" />
//        <layout type="log4net.Layout.PatternLayout" value="%level" />
//      </parameter>
//      <parameter>
//        <parameterName value="@logger" />
//        <dbType value="String" />
//        <size value="255" />
//        <layout type="log4net.Layout.PatternLayout" value="%logger" />
//      </parameter>
//      <parameter>
//        <parameterName value="@message" />
//        <dbType value="String" />
//        <size value="4000" />
//        <layout type="log4net.Layout.PatternLayout" value="%message" />
//      </parameter>
//    </appender>
//    <root>
//      <level value="ALL" />
//      <appender-ref ref="LogFileAppender" />
//      <appender-ref ref="AdoNetAppender" />
//      <appender-ref ref="ColoredConsoleAppender" />
//    </root>
//  </log4net>
//</configuration>
    #endregion TheAppconfig

    //this is the xml added replace here your log4net and Nunit paths
    //<Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL">
    //  <SpecificVersion>False</SpecificVersion>
    //  <HintPath>..\..\..\Log4Net\log4net-1.2.10\bin\net\2.0\release\log4net.dll</HintPath>
    //</Reference>
    //<Reference Include="nunit.framework, Version=2.4.8.0, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL" />

#4


What is your long-term usage scenario? A windows service might suffice...but Windows Server 2008/IIS7 provide some intriguing new ways of hosting and activating "services" via Windows Activation Services. A Windows Service will always be running, and may require some special coding. With WAS, you can write your host as a normal WCF service, and have it activated on demand when requests come in, and deactivated when it is not used. Other options also exist...such as MSMQ hosting and instantiation, etc.

您的长期使用情况是什么? Windows服务可能就足够了......但是Windows Server 2008 / IIS7通过Windows激活服务提供了一些有趣的新方法来托管和激活“服务”。 Windows服务将始终运行,可能需要一些特殊编码。使用WAS,您可以将主机编写为普通的WCF服务,并在请求进入时按需激活,在不使用时停用。还存在其他选项......例如MSMQ托管和实例化等。

#5


I ran into this same issue, I ended up writing my own wrapper, it's only good for the simplest situations but it does have it's perks. You can find the tool here: http://runasservice.com. Some of the perks include the fact that you can code your application as a console application which is easy to test and run in the IDE. Setting it up a service involves one simple command so you don't have to edit your application. Also you can install the service multiple times with different names, which you might want to do if you want to run each with different parameters.

我遇到了同样的问题,我最终编写了自己的包装器,它只适用于最简单的情况,但确实有它的特权。你可以在这里找到这个工具:http://runasservice.com。一些额外的好处包括您可以将应用程序编码为控制台应用程序,该应用程序易于在IDE中进行测试和运行。设置服务涉及一个简单的命令,因此您无需编辑应用程序。此外,您可以使用不同的名称多次安装服务,如果要使用不同的参数运行每个名称,可能需要执行此操作。

Like I said though it's only covers the simplest scenarios, applications that are already essentially services. That is they run continuously. I'm sure there are a lot of other services out there that give you a lot more options.

就像我说的那样,它只涵盖了最简单的场景,应用程序本质上就是服务。那就是他们不断奔跑。我相信还有很多其他服务可以为您提供更多选择。

Personally I don't think it's particularly difficult to convert a console application, but it can be a hassle to test. At the end though it depends on how much control you want. If it's a really important service for your company, then I would say convert it.

就个人而言,我认为转换控制台应用程序并不是特别困难,但测试起来可能很麻烦。最后,虽然这取决于你想要多少控制。如果这对贵公司来说是一项非常重要的服务,那么我会说转换它。