我作为服务运行吗?

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

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

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

我总是可以看到围绕ServiceBase.Run的try-catch声明,但这似乎很脏。 编辑:尝试赶上不起作用。

我有一个解决scheme:把它放在所有其他感兴趣的堆垛机:

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”的问题看起来像可能是答案。 我今天会考

像Ash一样,我将所有实际的处理代码写入单独的类库程序集,然后由Windows服务可执行文件引用,以及控制台应用程序。

但是,在某些情况下,如果知道类库是在服务可执行文件还是控制台应用程序的上下文中运行,那么有用。 我这样做的方式是反思托pipe应用程序的基类。 (抱歉的VB,但我想像下面可以很容易地c#化):

 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 

另一个解决方法..所以可以运行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); } 

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

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

什么对我有用:

  • 执行实际服务工作的类正在单独的线程中运行。
  • 此线程从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() ; } } 

乔纳森,不完全是你的问题的答案,但我刚写完一个Windows服务,也注意到debugging和testing的困难。

只需将所有实际处理代码写入单独的类库程序集,然后由Windows服务可执行文件引用,以及控制台应用程序和testing工具来解决此问题。

除了基本的定时器逻辑之外,所有更复杂的处理都发生在通用组件中,并且可以很容易地按需testing/运行。

我已经修改了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); } } 

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

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

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引用,来加强你的逻辑。 然后,在您的应用程序中,使用ServiceHost.Instance.Start()ServiceHost.Instance.Stop()方法,无论您是以控制台模式运行还是作为服务运行。

也许检查进程父母是否是C:\ Windows \ system32 \ services.exe。

我发现实现这一目的的唯一方法是通过访问try / catch块内的任何控制台对象属性(例如Title)来检查控制台是否被附加到进程中。

如果服务是由SCM启动的,那么没有控制台,访问该属性将会抛出一个System.IO.IOError。

但是,由于这种感觉有点太像依靠特定于实现的细节(如果某些平台上的SCM或某一天决定为启动的进程提供一个控制台,那该怎么办?),我总是使用一个命令行开关(-console )在生产应用程序…

这是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; 

这是一个自我插件,但我有一个小应用程序,将通过reflection在您的应用程序中加载您的服务types,并执行它们。 我包括源​​代码,所以你可以稍微改变它来显示标准输出。

使用此解决scheme无需更改代码。 我有一个Debugger.IsAttachedtypes的解决scheme,以及足够通用,可用于任何服务。 链接在这篇文章: .NET Windows服务亚军

那么有一些非常古老的代码(大约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); }