2008-10-14 97 views
41

我目前正在为可以在控制台中运行的服务编写一个小引导代码。它主要归结为调用OnStart()方法,而不是使用ServiceBase来启动和停止服务(因为如果它不作为服务安装并使调试成为一场噩梦,它不会运行应用程序)。我作为服务运行

现在我正在使用Debugger.IsAttached来确定我是否应该使用ServiceBase.Run或[service] .OnStart,但我知道这不是最好的主意,因为有时最终用户想要在控制台(实时查看输出等)。

有关如何确定Windows服务控制器是否启动“我”,或者用户是否在控制台中启动“我”的任何想法?可能是Environment.IsUserInteractive不是答案。我想过使用命令行参数,但这看起来很“脏”。

我总是可以看到围绕ServiceBase.Run的try-catch语句,但看起来很脏。编辑:尝试捕捉不起作用。

我有一个解决办法:把它在这里为所有其他有关堆垛机:

public void Run() 
    { 
     if (Debugger.IsAttached || Environment.GetCommandLineArgs().Contains<string>("-console")) 
     { 
      RunAllServices(); 
     } 
     else 
     { 
      try 
      { 
       string temp = Console.Title; 
       ServiceBase.Run((ServiceBase[])ComponentsToRun); 
      } 
      catch 
      { 
       RunAllServices(); 
      } 
     } 
    } // void Run 

    private void RunAllServices() 
    { 
     foreach (ConsoleService component in ComponentsToRun) 
     { 
      component.Start(); 
     } 
     WaitForCTRLC(); 
     foreach (ConsoleService component in ComponentsToRun) 
     { 
      component.Stop(); 
     } 
    } 

编辑:有StackOverflow上的另一个问题,其中家伙与Environment.CurrentDirectory为“C问题:\ Windows \ System32“看起来像可能是答案。我今天会测试。

+0

谢谢DOR加入您的解决方案,应该是一个有益的参考。 – Ash 2008-10-16 09:57:22

+2

不是因为它在上面提供的链接中指明了,所以IsUserInteractive将不会为控制台应用程序返回false,至少不是一般的。我为此使用它,从来没有任何问题。 – 2010-03-06 09:22:55

+0

[这是对C++的同样的问题](https://stackoverflow.com/questions/1974828/) – 2018-02-20 04:06:30

回答

15

如灰烬,我写在一个单独的类库装配全部实际处理的代码,然后将其通过引用Windows服务可执行文件以及控制台应用程序。

但是,有些时候知道类库是否在服务可执行文件或控制台应用程序的上下文中运行很有用。我这样做的方式是反思托管应用程序的基类。 (对不起,VB,但我想,下面可能是C#-ified很容易):

Public Class ExecutionContext 
    ''' <summary> 
    ''' Gets a value indicating whether the application is a windows service. 
    ''' </summary> 
    ''' <value> 
    ''' <c>true</c> if this instance is service; otherwise, <c>false</c>. 
    ''' </value> 
    Public Shared ReadOnly Property IsService() As Boolean 
     Get 
      ' Determining whether or not the host application is a service is 
      ' an expensive operation (it uses reflection), so we cache the 
      ' result of the first call to this method so that we don't have to 
      ' recalculate it every call. 

      ' If we have not already determined whether or not the application 
      ' is running as a service... 
      If IsNothing(_isService) Then 

       ' Get details of the host assembly. 
       Dim entryAssembly As Reflection.Assembly = Reflection.Assembly.GetEntryAssembly 

       ' Get the method that was called to enter the host assembly. 
       Dim entryPoint As System.Reflection.MethodInfo = entryAssembly.EntryPoint 

       ' If the base type of the host assembly inherits from the 
       ' "ServiceBase" class, it must be a windows service. We store 
       ' the result ready for the next caller of this method. 
       _isService = (entryPoint.ReflectedType.BaseType.FullName = "System.ServiceProcess.ServiceBase") 

      End If 

      ' Return the cached result. 
      Return CBool(_isService) 
     End Get 
    End Property 

    Private Shared _isService As Nullable(Of Boolean) = Nothing 
#End Region 
End Class 
19

我通常Windows服务作为控制台应用程序这需要的“-console”作为一个控制台运行命令行参数标志,否则它作为服务运行。要进行调试,只需将项目选项中的命令行参数设置为“-console”即可关闭!

这使得调试变得简单易用,并且意味着应用程序默认作为服务运行,这就是您想要的。

+3

这正是我如何做的。工作得很好;唯一的问题就是安全性(哪个帐户)和工作文件夹 - 这些文件更容易编码。 – 2008-10-14 06:43:30

9

乔纳森,不完全是你的问题的答案,但我刚写完一个Windows服务,并指出了调试和测试的困难。

通过简单地写全部实际处理代码在一个单独的类库组件,然后将其通过窗口服务可执行,以及一个控制台应用程序和测试工具引用解决它。

除了基本的定时器逻辑,所有更复杂的处理发生在常见的程序集中,并且可以非常容易地按需测试/运行。

+0

这是非常有用的信息,我想这是“正确”的方法。我希望你能接受两个答案:)。 – 2008-10-14 06:52:28

+0

没问题乔纳森,很高兴它是有用的。现在我试着按照这种方法(单独的应用程序逻辑汇编)来处理所有的应用程序。这样一个Windows服务可以被看作是应用程序的另一种类型的视图。我想这是模型视图控制器模式。 – Ash 2008-10-15 03:07:45

2

我发现,实现这一点的唯一方法,是检查是否一个控制台连接到进程首先,由try/catch块内访问的任何控制台对象属性(例如,名称)。

如果该服务被供应链管理开始,没有控制台,并访问属性将抛出一个System.IO.IOError。但是,由于这对于依赖特定于实现的细节(如果某些平台上的SCM或某一天决定为其启动的进程提供控制台会怎么样?)感觉有点太过分了,我总是使用命令行开关(-console)在生产中的应用...

-1

这是一个有点自插头的,但我有一个小应用程序将在您的应用程序通过反射加载的服务类型和执行他们的方式。我包括源​​代码,所以你可以稍微改变它来显示标准输出。

使用此解决方案无需更改代码。我有一个调试器。IsAttached类型的解决方案也是足够通用的,可用于任何服务。链接是在这篇文章中: .NET Windows Service Runner

+0

我实际上为他们写了一个基类,它具有Start()方法,这样我就不必求助于反射。谢谢你的提示。 – 2008-10-14 07:02:16

+0

这是为在Windows服务环境之外运行任何服务而不更改任何代码的独立方式而设计的。只需双击跑步者,选择您的服务.exe或.dll,然后单击确定。如果你为命令行运行runner,你会看到标准的IO。 – 2008-10-14 07:32:42

12

我什么工作:

  • 类做实际的服务工作是在一个单独的线程运行。
  • 此线程从OnStart()方法内启动,并从OnStop()停止。
  • 服务和控制台模式之间的决定取决于Environment.UserInteractive

示例代码:

class MyService : ServiceBase 
{ 
    private static void Main() 
    { 
     if (Environment.UserInteractive) 
     { 
      startWorkerThread(); 
      Console.WriteLine ("====== Press ENTER to stop threads ======"); 
      Console.ReadLine(); 
      stopWorkerThread() ; 
      Console.WriteLine ("====== Press ENTER to quit ======"); 
      Console.ReadLine(); 
     } 
     else 
     { 
      Run (this) ; 
     } 
    } 

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

    protected override void OnStop() 
    { 
     stopWorkerThread() ; 
    } 
} 
+0

感谢您的陀螺gyrolf,但不幸的是Environment.UserInteractive只适用于Windows窗体应用:(。 – 2008-10-17 05:29:29

+6

正如我理解文档和示例代码中,没有限制Windows窗体应用程序。控制台应用程序 – gyrolf 2008-10-17 08:38:16

3

也许如果检查过程中父是C:\ WINDOWS \ SYSTEM32 \ Services.exe的。

8

我已经修改了ProjectInstaller要追加命令行参数参数/服务,当它被安装为服务:然后

static class Program 
{ 
    static void Main(string[] args) 
    { 
     if (Array.Exists(args, delegate(string arg) { return arg == "/install"; })) 
     { 
      System.Configuration.Install.TransactedInstaller ti = null; 
      ti = new System.Configuration.Install.TransactedInstaller(); 
      ti.Installers.Add(new ProjectInstaller()); 
      ti.Context = new System.Configuration.Install.InstallContext("", null); 
      string path = System.Reflection.Assembly.GetExecutingAssembly().Location; 
      ti.Context.Parameters["assemblypath"] = path; 
      ti.Install(new System.Collections.Hashtable()); 
      return; 
     } 

     if (Array.Exists(args, delegate(string arg) { return arg == "/uninstall"; })) 
     { 
      System.Configuration.Install.TransactedInstaller ti = null; 
      ti = new System.Configuration.Install.TransactedInstaller(); 
      ti.Installers.Add(new ProjectInstaller()); 
      ti.Context = new System.Configuration.Install.InstallContext("", null); 
      string path = System.Reflection.Assembly.GetExecutingAssembly().Location; 
      ti.Context.Parameters["assemblypath"] = path; 
      ti.Uninstall(null); 
      return; 
     } 

     if (Array.Exists(args, delegate(string arg) { return arg == "/service"; })) 
     { 
      ServiceBase[] ServicesToRun; 

      ServicesToRun = new ServiceBase[] { new MyService() }; 
      ServiceBase.Run(ServicesToRun); 
     } 
     else 
     { 
      Console.ReadKey(); 
     } 
    } 
} 

的ProjectInstaller.cs被修改,以覆盖一个OnBeforeInstall()和OnBeforeUninstall ()

[RunInstaller(true)] 
public partial class ProjectInstaller : Installer 
{ 
    public ProjectInstaller() 
    { 
     InitializeComponent(); 
    } 

    protected virtual string AppendPathParameter(string path, string parameter) 
    { 
     if (path.Length > 0 && path[0] != '"') 
     { 
      path = "\"" + path + "\""; 
     } 
     path += " " + parameter; 
     return path; 
    } 

    protected override void OnBeforeInstall(System.Collections.IDictionary savedState) 
    { 
     Context.Parameters["assemblypath"] = AppendPathParameter(Context.Parameters["assemblypath"], "/service"); 
     base.OnBeforeInstall(savedState); 
    } 

    protected override void OnBeforeUninstall(System.Collections.IDictionary savedState) 
    { 
     Context.Parameters["assemblypath"] = AppendPathParameter(Context.Parameters["assemblypath"], "/service"); 
     base.OnBeforeUninstall(savedState); 
    } 
} 
24

另一个解决方法..所以可以作为WinForm的或作为Windows服务运行

var backend = new Backend(); 

if (Environment.UserInteractive) 
{ 
    backend.OnStart(); 
    Application.EnableVisualStyles(); 
    Application.SetCompatibleTextRenderingDefault(false); 
    Application.Run(new Fronend(backend)); 
    backend.OnStop(); 
} 
else 
{ 
    var ServicesToRun = new ServiceBase[] {backend}; 
    ServiceBase.Run(ServicesToRun); 
} 
4

这个线程真的很老,但我想我会把我的解决方案放在那里。很简单,为了处理这种情况,我构建了一个在服务控制台和Windows服务案例中使用的“服务线束”。如上所述,大部分逻辑都包含在一个单独的库中,但这更多用于测试和“可链接性”。

附加的代码绝不代表“最好的”方式来解决这个问题,只是我自己的方法。这里,控制台应用程序在处于“控制台模式”时调用服务线束,并且在作为服务运行时由同一应用程序的“启动服务”逻辑调用。通过做这种方式,您现在可以调用

从任何地方ServiceHost.Instance.RunningAsAService(布尔)

在你的代码检查,如果应用程序正在运行的服务或简单地作为控制台。

下面是代码:

public class ServiceHost 
{ 
    private static Logger log = LogManager.GetLogger(typeof(ServiceHost).Name); 

    private static ServiceHost mInstance = null; 
    private static object mSyncRoot = new object(); 

    #region Singleton and Static Properties 

    public static ServiceHost Instance 
    { 
     get 
     { 
      if (mInstance == null) 
      { 
       lock (mSyncRoot) 
       { 
        if (mInstance == null) 
        { 
         mInstance = new ServiceHost(); 
        } 
       } 
      } 

      return (mInstance); 
     } 
    } 

    public static Logger Log 
    { 
     get { return log; } 
    } 

    public static void Close() 
    { 
     lock (mSyncRoot) 
     { 
      if (mInstance.mEngine != null) 
       mInstance.mEngine.Dispose(); 
     } 
    } 

    #endregion 

    private ReconciliationEngine mEngine; 
    private ServiceBase windowsServiceHost; 
    private UnhandledExceptionEventHandler threadExceptionHanlder = new UnhandledExceptionEventHandler(ThreadExceptionHandler); 

    public bool HostHealthy { get; private set; } 
    public bool RunningAsService {get; private set;} 

    private ServiceHost() 
    { 
     HostHealthy = false; 
     RunningAsService = false; 
     AppDomain.CurrentDomain.UnhandledException += threadExceptionHandler; 

     try 
     { 
      mEngine = new ReconciliationEngine(); 
      HostHealthy = true; 
     } 
     catch (Exception ex) 
     { 
      log.FatalException("Could not initialize components.", ex); 
     } 
    } 

    public void StartService() 
    { 
     if (!HostHealthy) 
      throw new ApplicationException("Did not initialize components."); 

     try 
     { 
      mEngine.Start(); 
     } 
     catch (Exception ex) 
     { 
      log.FatalException("Could not start service components.", ex); 
      HostHealthy = false; 
     } 
    } 

    public void StartService(ServiceBase serviceHost) 
    { 
     if (!HostHealthy) 
      throw new ApplicationException("Did not initialize components."); 

     if (serviceHost == null) 
      throw new ArgumentNullException("serviceHost"); 

     windowsServiceHost = serviceHost; 
     RunningAsService = true; 

     try 
     { 
      mEngine.Start(); 
     } 
     catch (Exception ex) 
     { 
      log.FatalException("Could not start service components.", ex); 
      HostHealthy = false; 
     } 
    } 

    public void RestartService() 
    { 
     if (!HostHealthy) 
      throw new ApplicationException("Did not initialize components.");   

     try 
     { 
      log.Info("Stopping service components..."); 
      mEngine.Stop(); 
      mEngine.Dispose(); 

      log.Info("Starting service components..."); 
      mEngine = new ReconciliationEngine(); 
      mEngine.Start(); 
     } 
     catch (Exception ex) 
     { 
      log.FatalException("Could not restart components.", ex); 
      HostHealthy = false; 
     } 
    } 

    public void StopService() 
    { 
     try 
     { 
      if (mEngine != null) 
       mEngine.Stop(); 
     } 
     catch (Exception ex) 
     { 
      log.FatalException("Error stopping components.", ex); 
      HostHealthy = false; 
     } 
     finally 
     { 
      if (windowsServiceHost != null) 
       windowsServiceHost.Stop(); 

      if (RunningAsService) 
      { 
       AppDomain.CurrentDomain.UnhandledException -= threadExceptionHanlder; 
      } 
     } 
    } 

    private void HandleExceptionBasedOnExecution(object ex) 
    { 
     if (RunningAsService) 
     { 
      windowsServiceHost.Stop(); 
     } 
     else 
     { 
      throw (Exception)ex; 
     } 
    } 

    protected static void ThreadExceptionHandler(object sender, UnhandledExceptionEventArgs e) 
    { 
     log.FatalException("Unexpected error occurred. System is shutting down.", (Exception)e.ExceptionObject); 
     ServiceHost.Instance.HandleExceptionBasedOnExecution((Exception)e.ExceptionObject); 
    } 
} 

所有你需要做的,是取代不祥寻找ReconcilationEngine参考与任何方法boostrapping你的逻辑。然后在您的应用程序中,使用ServiceHost.Instance.Start()ServiceHost.Instance.Stop()方法,无论您是以控制台模式运行还是作为服务运行。

-1

那么有一些很旧的代码(约20年左右,而不是我,但在野外,野外网络上找到,并用C不是C#),应该给你一个想法如何做的工作:

enum enEnvironmentType 
    { 
    ENVTYPE_UNKNOWN, 
    ENVTYPE_STANDARD, 
    ENVTYPE_SERVICE_WITH_INTERACTION, 
    ENVTYPE_SERVICE_WITHOUT_INTERACTION, 
    ENVTYPE_IIS_ASP, 
    }; 

enEnvironmentType GetEnvironmentType(void) 
{ 
    HANDLE hProcessToken = NULL; 
    DWORD groupLength  = 300; 
    PTOKEN_GROUPS groupInfo = NULL; 

    SID_IDENTIFIER_AUTHORITY siaNt = SECURITY_NT_AUTHORITY; 
    PSID pInteractiveSid = NULL; 
    PSID pServiceSid = NULL; 

    DWORD dwRet = NO_ERROR; 
    DWORD ndx; 

    BOOL m_isInteractive = FALSE; 
    BOOL m_isService = FALSE; 

    // open the token 
    if (!::OpenProcessToken(::GetCurrentProcess(),TOKEN_QUERY,&hProcessToken)) 
     { 
     dwRet = ::GetLastError(); 
     goto closedown; 
     } 

    // allocate a buffer of default size 
    groupInfo = (PTOKEN_GROUPS)::LocalAlloc(0, groupLength); 
    if (groupInfo == NULL) 
     { 
     dwRet = ::GetLastError(); 
     goto closedown; 
     } 

    // try to get the info 
    if (!::GetTokenInformation(hProcessToken, TokenGroups, 
     groupInfo, groupLength, &groupLength)) 
     { 
     // if buffer was too small, allocate to proper size, otherwise error 
     if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) 
      { 
      dwRet = ::GetLastError(); 
      goto closedown; 
      } 

     ::LocalFree(groupInfo); 

     groupInfo = (PTOKEN_GROUPS)::LocalAlloc(0, groupLength); 
     if (groupInfo == NULL) 
      { 
      dwRet = ::GetLastError(); 
      goto closedown; 
      } 

     if (!GetTokenInformation(hProcessToken, TokenGroups, 
      groupInfo, groupLength, &groupLength)) 
      { 
      dwRet = ::GetLastError(); 
      goto closedown; 
      } 
     } 

    // 
    // We now know the groups associated with this token. We want 
    // to look to see if the interactive group is active in the 
    // token, and if so, we know that this is an interactive process. 
    // 
    // We also look for the "service" SID, and if it's present, 
    // we know we're a service. 
    // 
    // The service SID will be present iff the service is running in a 
    // user account (and was invoked by the service controller). 
    // 

    // create comparison sids 
    if (!AllocateAndInitializeSid(&siaNt, 
     1, 
     SECURITY_INTERACTIVE_RID, 
     0, 0, 0, 0, 0, 0, 0, 
     &pInteractiveSid)) 
     { 
     dwRet = ::GetLastError(); 
     goto closedown; 
     } 

    if (!AllocateAndInitializeSid(&siaNt, 
     1, 
     SECURITY_SERVICE_RID, 
     0, 0, 0, 0, 0, 0, 0, 
     &pServiceSid)) 
     { 
     dwRet = ::GetLastError(); 
     goto closedown; 
     } 

    // try to match sids 
    for (ndx = 0; ndx < groupInfo->GroupCount ; ndx += 1) 
     { 
     SID_AND_ATTRIBUTES sanda = groupInfo->Groups[ndx]; 
     PSID    pSid = sanda.Sid; 

     // 
     // Check to see if the group we're looking at is one of 
     // the two groups we're interested in. 
     // 

     if (::EqualSid(pSid, pInteractiveSid)) 
      { 
      // 
      // This process has the Interactive SID in its 
      // token. This means that the process is running as 
      // a console process 
      // 
      m_isInteractive = TRUE; 
      m_isService = FALSE; 
      break; 
      } 
     else if (::EqualSid(pSid, pServiceSid)) 
      { 
      // 
      // This process has the Service SID in its 
      // token. This means that the process is running as 
      // a service running in a user account (not local system). 
      // 
      m_isService = TRUE; 
      m_isInteractive = FALSE; 
      break; 
      } 
     } 

    if (!(m_isService || m_isInteractive)) 
     { 
     // 
     // Neither Interactive or Service was present in the current 
     // users token, This implies that the process is running as 
     // a service, most likely running as LocalSystem. 
     // 
     m_isService = TRUE; 
     } 


closedown: 
    if (pServiceSid) 
     ::FreeSid(pServiceSid); 

    if (pInteractiveSid) 
     ::FreeSid(pInteractiveSid); 

    if (groupInfo) 
     ::LocalFree(groupInfo); 

    if (hProcessToken) 
     ::CloseHandle(hProcessToken); 

    if (dwRet == NO_ERROR) 
     { 
     if (m_isService) 
      return(m_isInteractive ? ENVTYPE_SERVICE_WITH_INTERACTION : ENVTYPE_SERVICE_WITHOUT_INTERACTION); 
     return(ENVTYPE_STANDARD); 
     } 
     else 
     return(ENVTYPE_UNKNOWN); 
} 
1

这里是chksr的回答到.NET的转换,并且避免没有认识到交互式服务的错误:

using System.Security.Principal; 

var wi = WindowsIdentity.GetCurrent(); 
var wp = new WindowsPrincipal(wi); 
var serviceSid = new SecurityIdentifier(WellKnownSidType.ServiceSid, null); 
var localSystemSid = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null); 
var interactiveSid = new SecurityIdentifier(WellKnownSidType.InteractiveSid, null); 
// maybe check LocalServiceSid, and NetworkServiceSid also 

bool isServiceRunningAsUser = wp.IsInRole(serviceSid); 
bool isSystem = wp.IsInRole(localSystemSid); 
bool isInteractive = wp.IsInRole(interactiveSid); 

bool isAnyService = isServiceRunningAsUser || isSystem || !isInteractive;