Sono in esecuzione come servizio

Attualmente sto scrivendo un piccolo codice di bootstrap per un servizio che può essere eseguito nella console. Si riduce essenzialmente a chiamare il metodo OnStart () invece di utilizzare ServiceBase per avviare e interrompere il servizio (perché non esegue l’applicazione se non è installato come servizio e rende il debug un incubo).

In questo momento sto usando Debugger.IsAttached per determinare se dovrei usare ServiceBase.Run o [service] .OnStart, ma so che non è la migliore idea perché alcune volte gli utenti finali vogliono eseguire il servizio in una console (per vedere l’output ecc. in tempo reale).

Qualche idea su come potrei determinare se il controller di servizio di Windows ha avviato “me” o se l’utente ha avviato “me” nella console? Apparentemente Environment.IsUserInteractive non è la risposta. Ho pensato di usare la linea di comando, ma sembra “sporco”.

Potrei sempre vedere una dichiarazione try-catch su ServiceBase.Run, ma sembra sporca. Modifica: prova a prendere non funziona.

Ho una soluzione: mettila qui per tutti gli altri impilatori interessati:

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: C’era un’altra domanda su StackOverflow in cui il ragazzo aveva problemi con Environment.CurrentDirectory “C: \ Windows \ System32” sembra che potrebbe essere la risposta. Metterò alla prova oggi.

Come Ash, scrivo tutto il codice di elaborazione effettivo in un assembly della libreria di classi separato, a cui poi fa riferimento l’eseguibile del servizio Windows, nonché un’app console.

Tuttavia, ci sono occasioni in cui è utile sapere se la libreria di classi è in esecuzione nel contesto dell’eseguibile del servizio o dell’app della console. Il modo in cui lo faccio è quello di riflettere sulla class base dell’app di hosting. (Ci scusiamo per il VB, ma immagino che quanto segue possa essere c # -ificato abbastanza facilmente):

 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 

Un’altra soluzione .. quindi può essere eseguito come WinForm o come servizio di 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); } 

Di solito contrassegno il servizio Windows come un’applicazione console che accetta un parametro da riga di comando di “-console” per essere eseguito come console, altrimenti viene eseguito come un servizio. Per eseguire il debug basta impostare i parametri della riga di comando nelle opzioni del progetto su “-console” e sei fuori!

Questo rende il debug facile e intuitivo e significa che l’app funziona come un servizio di default, che è ciò che vorrai.

Cosa funziona per me:

  • La class che esegue il lavoro di servizio effettivo è in esecuzione in un thread separato.
  • Questo thread viene avviato dal metodo OnStart () e interrotto da OnStop ().
  • La decisione tra il servizio e la modalità console dipende da Environment.UserInteractive

Codice d’esempio:

 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, non è esattamente una risposta alla tua domanda, ma ho appena finito di scrivere un servizio di Windows e ho anche notato la difficoltà con il debug e il test.

Risolto il problema semplicemente scrivendo tutto il codice di elaborazione effettivo in un assembly della libreria di classi separato, a cui poi faceva riferimento l’eseguibile del servizio Windows, oltre a un’app console e un’imbracatura di test.

Oltre alla logica del timer di base, tutta l’elaborazione più complessa è avvenuta nell’assemblaggio comune e potrebbe essere testata / eseguita su richiesta incredibilmente facilmente.

Ho modificato il ProjectInstaller per aggiungere il parametro / servizio dell’argomento della riga di comando, quando viene installato come servizio:

 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 viene quindi modificato per sovrascrivere un 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); } } 

Questo thread è davvero vecchio, ma ho pensato di buttare la mia soluzione là fuori. Molto semplicemente, per gestire questo tipo di situazione, ho creato un “cablaggio di servizio” che viene utilizzato sia nella console che nei casi di assistenza di Windows. Come sopra, la maggior parte della logica è contenuta in una libreria separata, ma questo è più per test e “linkability”.

Il codice allegato non rappresenta in alcun modo il “miglior modo ansible” per risolvere questo, solo il mio approccio. In questo caso, il cablaggio del servizio viene chiamato dall’app console quando è in “modalità console” e dalla stessa logica “start service” dell’applicazione quando è in esecuzione come servizio. Facendolo in questo modo, ora puoi chiamare

ServiceHost.Instance.RunningAsAService (Boolean)

da qualsiasi parte del codice per verificare se l’applicazione è in esecuzione come servizio o semplicemente come console.

Ecco il codice:

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

Tutto quello che devi fare qui è sostituire quel riferimento di ReconcilationEngine dall’aspetto inquietante con qualunque metodo stia stravolgendo la tua logica. Quindi, nell’applicazione, utilizzare i ServiceHost.Instance.Start() e ServiceHost.Instance.Stop() indipendentemente dal fatto che si esegua in modalità console o come servizio.

Magari controllando se il processo genitore è C: \ Windows \ system32 \ services.exe.

L’unico modo per ottenerlo è verificare se una console è collegata al processo in primo luogo, accedendo a qualsiasi proprietà dell’object Console (ad esempio Title) all’interno di un blocco try / catch.

Se il servizio viene avviato da SCM, non c’è alcuna console e l’accesso alla proprietà genererà un errore System.IO.IO.Error.

Tuttavia, poiché questo sembra un po ‘troppo come fare affidamento su un dettaglio specifico dell’implementazione (cosa succederebbe se lo SCM su alcune piattaforms o un giorno decidesse di fornire una console ai processi che avvia?), Utilizzo sempre un commando da riga di comando (-console ) nelle app di produzione …

Ecco una traduzione della risposta di chksr a .NET, ed evitando il bug che non riesce a riconoscere i servizi interattivi:

 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; 

Questo è un po ‘un auto-plug, ma ho una piccola app che caricherà i tuoi tipi di servizio nella tua app attraverso la riflessione e li eseguirà in questo modo. Includo il codice sorgente, quindi potresti cambiarlo leggermente per visualizzare l’output standard.

Non sono necessarie modifiche al codice per utilizzare questa soluzione. Ho anche un tipo di soluzione Debugger.IsAttached che è abbastanza generico da poter essere utilizzato con qualsiasi servizio. Il collegamento è in questo articolo: .NET Windows Service Runner

Beh, c’è un codice molto vecchio (circa 20 anni o giù di lì, non da me ma trovato nella wild, wild web, e in C non C #) che dovrebbe darti un’idea di come fare il lavoro:

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