web-dev-qa-db-fra.com

Suis-je en cours d'exécution en tant que service

J'écris actuellement un peu de code bootstrap pour un service qui peut être exécuté dans la console. Cela revient essentiellement à appeler la méthode OnStart () au lieu d'utiliser la ServiceBase pour démarrer et arrêter le service (car il n'exécute pas l'application s'il n'est pas installé en tant que service et fait du débogage un cauchemar).

En ce moment j'utilise Debugger.IsAttached pour déterminer si je dois utiliser ServiceBase.Run ou [service] .OnStart, mais je sais que ce n'est pas la meilleure idée parce que parfois les utilisateurs finaux veulent exécuter le service dans une console (pour voir la sortie, etc. en temps réel).

Des idées sur la façon dont je pourrais déterminer si le contrôleur de service Windows a démarré "moi", ou si l'utilisateur a démarré "moi" dans la console? Apparemment Environment.IsUserInteractive n'est pas la réponse. J'ai pensé à utiliser des arguments de ligne de commande, mais cela semble "sale".

Je pouvais toujours voir une déclaration try-catch autour de ServiceBase.Run, mais cela semble sale. Edit: Essayez catch ne fonctionne pas.

J'ai une solution: la mettre ici pour tous les autres gerbeurs intéressés:

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

EDIT: Il y avait une autre question sur StackOverflow où le gars avait des problèmes avec Environment.CurrentDirectory étant "C:\Windows\System32", cela pourrait être la réponse. Je vais tester aujourd'hui.

44

Comme Ash, j'écris tout le code de traitement réel dans une bibliothèque de classes distincte Assembly, qui a ensuite été référencée par l'exécutable du service Windows, ainsi qu'une application console.

Cependant, il est parfois utile de savoir si la bibliothèque de classes s'exécute dans le contexte de l'exécutable de service ou de l'application console. La façon dont je le fais est de réfléchir sur la classe de base de l'application d'hébergement. (Désolé pour le VB, mais j'imagine que ce qui suit pourrait être c # -ified assez facilement):

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
15
Kramii

Une autre solution de contournement .. peut donc s'exécuter en tant que WinForm ou en tant que service 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);
}
27
rnr_never_dies

Je marque généralement mon service Windows comme une application console qui prend un paramètre de ligne de commande "-console" pour s'exécuter à l'aide d'une console, sinon il s'exécute en tant que service. Pour déboguer, il vous suffit de définir les paramètres de ligne de commande dans les options du projet sur "-console" et c'est parti!

Cela rend le débogage agréable et facile et signifie que l'application fonctionne comme un service par défaut, ce que vous voudrez.

20
Sean

Ce qui fonctionne pour moi:

  • La classe effectuant le travail de service réel s'exécute dans un thread distinct.
  • Ce thread est démarré à partir de la méthode OnStart () et arrêté à partir de OnStop ().
  • La décision entre le service et le mode console dépend de Environment.UserInteractive

Exemple de code:

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

J'ai modifié ProjectInstaller pour ajouter le paramètre/service de l'argument de ligne de commande, lorsqu'il est installé en tant que service:

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

Le ProjectInstaller.cs est ensuite modifié pour remplacer un OnBeforeInstall () et 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);
    }
}
9
Rolf Kristensen

Jonathan, pas exactement une réponse à votre question, mais je viens de terminer l'écriture d'un service Windows et j'ai également noté la difficulté de débogage et de test.

Résolu en écrivant simplement tout le code de traitement réel dans un assembly de bibliothèque de classes distinct, qui a ensuite été référencé par l'exécutable du service Windows, ainsi qu'une application console et un faisceau de test.

En dehors de la logique de minuterie de base, tous les traitements plus complexes se sont produits dans l'assemblage commun et peuvent être testés/exécutés à la demande incroyablement facilement.

9
Ash

Ce fil est vraiment ancien, mais je pensais que je jetterais ma solution là-bas. Tout simplement, pour gérer ce type de situation, j'ai construit un "faisceau de services" qui est utilisé à la fois dans les cas de console et de service Windows. Comme ci-dessus, la majeure partie de la logique est contenue dans une bibliothèque distincte, mais c'est plus pour le test et la "liaison".

Le code joint ne représente en aucun cas la "meilleure façon" de résoudre ce problème, juste ma propre approche. Ici, le faisceau de services est appelé par l'application console lorsqu'elle est en "mode console" et par la logique "start service" de la même application lorsqu'elle s'exécute en tant que service. En procédant ainsi, vous pouvez maintenant appeler

ServiceHost.Instance.RunningAsAService (Booléen)

de n'importe où dans votre code pour vérifier si l'application s'exécute en tant que service ou simplement en tant que console.

Voici le code:

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

Tout ce que vous devez faire ici est de remplacer cette référence inquiétante ReconcilationEngine par la méthode qui booste votre logique. Ensuite, dans votre application, utilisez les méthodes ServiceHost.Instance.Start() et ServiceHost.Instance.Stop() que vous exécutiez en mode console ou en tant que service.

4
shockwave121

Peut-être en vérifiant si le parent du processus est C:\Windows\system32\services.exe.

3
Matias

Le seul moyen que j'ai trouvé pour y parvenir est de vérifier si une console est attachée au processus en premier lieu, en accédant à n'importe quelle propriété d'objet Console (par exemple, Title) à l'intérieur d'un bloc try/catch.

Si le service est démarré par le SCM, il n'y a pas de console et l'accès à la propriété générera une erreur System.IO.IOError.

Cependant, comme cela ressemble un peu trop au fait de s'appuyer sur un détail spécifique à l'implémentation (et si le SCM sur certaines plates-formes ou décide un jour de fournir une console aux processus qu'il démarre?), J'utilise toujours un commutateur de ligne de commande (-console ) dans les applications de production ...

2
mdb

Voici une traduction de la réponse de chksr à .NET et évitant le bogue qui ne reconnaît pas les services interactifs:

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;
1
Ben Voigt

On dirait que je suis un peu en retard à la fête, mais une différence intéressante lors de l'exécution en tant que service est qu'au début, le dossier actuel pointe vers le répertoire système (C:\windows\system32 Par défaut). Son application utilisateur peu probable démarrera à partir du dossier système dans n'importe quelle situation de la vie réelle.

Donc, j'utilise l'astuce suivante (c #):

protected static bool IsRunAsService()
{
    string CurDir = Directory.GetCurrentDirectory();
    if (CurDir.Equals(Environment.SystemDirectory, StringComparison.CurrentCultureIgnoreCase))
    { 
         return true; 
    }

    return (false);
}

Pour une extension future, une vérification supplémentaire doit être effectuée pour System.Environment.UserInteractive == false (Mais je ne sais pas comment cela est en corrélation avec les paramètres du service "Autoriser le service à interagir avec le bureau").

Vous pouvez également vérifier la session de fenêtre par System.Diagnostics.Process.GetCurrentProcess().SessionId == 0 (je ne sais pas comment elle est également en corrélation avec les paramètres du service "Autoriser le service à interagir avec le bureau").

Si vous écrivez du code portable (par exemple, avec .NetCore), vous pouvez également vérifier Environment.OSVersion.Platform Pour vous assurer que vous êtes d'abord sur Windows.

0
Serge Ageyev

C'est un peu auto-plug, mais j'ai une petite application qui chargera vos types de services dans votre application via la réflexion et les exécutera de cette façon. J'inclus le code source, vous pouvez donc le modifier légèrement pour afficher la sortie standard.

Aucun changement de code nécessaire pour utiliser cette solution. J'ai également un type de solution Debugger.IsAttached qui est suffisamment générique pour être utilisé avec n'importe quel service. Le lien se trouve dans cet article: . NET Windows Service Runner

0
Anderson Imes