Estou correndo como um serviço

Atualmente, estou escrevendo um pequeno código de boot para um serviço que pode ser executado no console. Basicamente, resume-se a chamar o método OnStart () em vez de usar o ServiceBase para iniciar e parar o serviço (porque ele não executa o aplicativo se não estiver instalado como um serviço e torna a debugging um pesadelo).

Agora estou usando o Debugger.IsAttached para determinar se devo usar ServiceBase.Run ou [service] .OnStart, mas sei que essa não é a melhor idéia, porque algumas vezes os usuários finais querem executar o serviço em um console (ver a saída etc. em tempo real).

Alguma idéia de como eu poderia determinar se o controlador de serviço do Windows iniciou ‘me’ ou se o usuário iniciou ‘me’ no console? Aparentemente Environment.IsUserInteractive não é a resposta. Pensei em usar argumentos de linha de comando, mas isso parece “sujo”.

Eu sempre pude ver uma declaração try-catch em torno de ServiceBase.Run, mas isso parece sujo. Edit: Tente pegar não funciona.

Eu tenho uma solução: colocá-lo aqui para todos os outros stackers interessados:

public void Run() { if (Debugger.IsAttached || Environment.GetCommandLineArgs().Contains("-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(); } } 

EDIT: Houve outra pergunta no StackOverflow onde o cara teve problemas com o Environment.CurrentDirectory sendo “C: \ Windows \ System32” parece que pode ser a resposta. Vou testar hoje.

Como Ash, eu escrevo todo o código de processamento real em um assembly de biblioteca de classs separado, que foi referenciado pelo executável do serviço do windows, bem como um aplicativo de console.

No entanto, há ocasiões em que é útil saber se a biblioteca de classs está sendo executada no contexto do executável do serviço ou do aplicativo do console. A maneira como faço isso é refletir sobre a class base do aplicativo de hospedagem. (Desculpem o VB, mas imagino que o seguinte poderia ser c # -ified com bastante facilidade):

 Public Class ExecutionContext '''  ''' Gets a value indicating whether the application is a windows service. '''  '''  ''' true if this instance is service; otherwise, false. '''  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 

Outra solução alternativa .. assim pode ser executado como WinForm ou como serviço do 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); } 

Eu normalmente sinalizo pelo serviço do Windows como um aplicativo de console que usa um parâmetro de linha de comando “-console” para ser executado como um console, caso contrário, ele é executado como um serviço. Para depurar, basta definir os parâmetros da linha de comando nas opções do projeto para “-console” e você está desligado!

Isso torna a debugging simples e fácil e significa que o aplicativo funciona como um serviço por padrão, que é o que você deseja.

O que funciona para mim:

  • A class que faz o trabalho de serviço real está sendo executada em um encadeamento separado.
  • Esse encadeamento é iniciado a partir do método OnStart () e interrompido no OnStop ().
  • A decisão entre o serviço e o modo do console depende do Environment.UserInteractive

Código de amostra:

 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() ; } } 

Jonathan, não é exatamente uma resposta à sua pergunta, mas acabei de terminar de escrever um serviço do Windows e também notei a dificuldade com a debugging e teste.

Resolveu-se simplesmente escrevendo todo o código de processamento real em uma assembly de biblioteca de classs separada, que foi então referenciada pelo executável do serviço do Windows, bem como um aplicativo de console e um chicote de teste.

Além da lógica básica do timer, todo o processamento mais complexo aconteceu na assembly comum e poderia ser testado / executado sob demanda incrivelmente fácil.

Eu modifiquei o ProjectInstaller para append o argumento / serviço de linha de comando, quando ele está sendo instalado como serviço:

 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(); } } } 

O ProjectInstaller.cs é modificado para replace um OnBeforeInstall () e 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); } } 

Esse segmento é muito antigo, mas achei que lançaria minha solução por aí. Muito simplesmente, para lidar com esse tipo de situação, criei um “conjunto de serviços” que é usado nos casos de serviço do console e do Windows. Como acima, a maior parte da lógica está contida em uma biblioteca separada, mas isso é mais para testes e “linkability”.

O código em anexo de forma alguma representa a melhor maneira possível de resolver isso, apenas a minha própria abordagem. Aqui, o chicote de serviços é chamado pelo aplicativo do console quando está em “modo de console” e pela mesma lógica de “início de serviço” do aplicativo quando está sendo executado como um serviço. Ao fazer isso dessa maneira, agora você pode ligar

ServiceHost.Instance.RunningAsAService (Boolean)

de qualquer lugar no seu código para verificar se o aplicativo está sendo executado como um serviço ou simplesmente como um console.

Aqui está o código:

 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); } } 

Tudo o que você precisa fazer aqui é replace a referência ReconcilationEngine aparência sinistra por qualquer método que esteja ampliando sua lógica. Em seguida, em seu aplicativo, use os methods ServiceHost.Instance.Start() e ServiceHost.Instance.Stop() , se você estiver executando no modo de console ou como um serviço.

Talvez verifique se o pai do processo é C: \ Windows \ system32 \ services.exe.

A única maneira que encontrei para conseguir isso é verificar se um console está conectado ao processo, acessando qualquer propriedade do object Console (por exemplo, Title) dentro de um bloco try / catch.

Se o serviço for iniciado pelo SCM, não haverá console e o access à propriedade lançará um erro System.IO.IOEIO.

No entanto, uma vez que isso parece um pouco demais como confiar em um detalhe específico da implementação (e se o SCM em algumas plataformas ou algum dia decidir fornecer um console para os processos iniciados?), Sempre uso um switch de linha de comando (-console ) em aplicativos de produção …

Aqui está uma tradução da resposta da chksr ao .NET, e evitando o bug que falha em reconhecer os serviços interativos:

 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; 

Este é um pouco auto-plug, mas eu tenho um pequeno aplicativo que irá carregar seus tipos de serviço em seu aplicativo através de reflection e executá-los dessa maneira. Eu incluo o código-fonte, então você pode alterá-lo ligeiramente para exibir a saída padrão.

Nenhuma alteração de código necessária para usar esta solução. Eu tenho um tipo de solução Debugger.IsAttached, bem que é genérico o suficiente para ser usado com qualquer serviço. Link está neste artigo: .NET Windows Service Runner

Bem, há algum código muito antigo (cerca de 20 anos ou mais, não de mim, mas encontrado na web selvagem, selvagem e em C não C #) que deve lhe dar uma idéia de como fazer o trabalho:

 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); }