Question

J'écris actuellement un petit code d'amorçage pour un service pouvant être exécuté dans la console. Cela revient essentiellement à appeler la méthode OnStart () au lieu d'utiliser ServiceBase pour démarrer et arrêter le service (car l'application ne s'exécute pas si elle n'est pas installée en tant que service et fait du débogage un véritable cauchemar).

À l’heure actuelle, 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 car certains utilisateurs finaux souhaitent exécuter le service dans une console. (pour voir la sortie, etc. en temps réel).

Avez-vous des idées sur la manière de déterminer si le contrôleur de service Windows a démarré "moi" ou si l'utilisateur a lancé "moi" dans la console? Apparemment Environment.IsUserInteractive n’est pas la solution. J'ai pensé à utiliser des arguments en ligne de commande, mais cela semble "sale".

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

J'ai une solution: la mettre ici pour tous les autres empileurs 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". on dirait que c'est peut-être la solution. Je vais tester aujourd'hui.

Était-ce utile?

La solution

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

Cependant, il est parfois utile de savoir si la bibliothèque de classes est en cours d'exécution dans le contexte de l'exécutable du service ou de l'application de la console. Pour ce faire, je réfléchis à 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-assez facilement modifié):

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

Autres conseils

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

Je signale généralement par le service Windows une application console prenant un paramètre de ligne de commande de " -console " pour s'exécuter en tant que 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 de projet sur " -console " et c'est parti!

Cela facilite le débogage et signifie que l'application fonctionne comme un service par défaut, c'est ce que vous voudrez.

Ce qui fonctionne pour moi:

  • La classe effectuant le travail de service réel est exécutée dans un thread séparé.
  • Ce thread est démarré à partir de la méthode OnStart () et arrêté à partir de OnStop ().
  • La décision entre le mode 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() ;
    }
}

Jonathan, ce n’est pas vraiment une réponse à votre question, mais je viens de terminer la rédaction d’un service Windows et j’ai également noté la difficulté avec le débogage et les tests.

Pour résoudre ce problème, il suffit d'écrire tout le code de traitement dans un assemblage de bibliothèque de classes séparé, qui est ensuite référencé par l'exécutable du service Windows, ainsi que dans une application de console et un faisceau de test.

Hormis la logique de base du minuteur, tous les traitements plus complexes ont eu lieu dans l’assemblage commun et ont pu être testés / exécutés à la demande très facilement.

J'ai modifié ProjectInstaller pour ajouter le paramètre / service d'argument de ligne de commande lors de son installation 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 fichier 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);
    }
}

Ce fil est vraiment vieux, mais je pensais jeter ma solution là-bas. Tout simplement, pour gérer ce type de situation, j’ai construit un "harnais de service". utilisé dans les cas de service de la console et de Windows. Comme ci-dessus, la majeure partie de la logique est contenue dans une bibliothèque distincte, mais elle est davantage destinée aux tests et à la "capacité de liaison".

Le code joint ne représente en aucun cas le "meilleur possible". façon de résoudre cela, juste ma propre approche. Ici, le harnais de service est appelé par l’application de la console en mode "console". et par le même "service de démarrage" de la même application logique quand il fonctionne en tant que service. En procédant ainsi, vous pouvez maintenant appeler

ServiceHost.Instance.RunningAsAService (Boolean)

n'importe où dans votre code pour vérifier si l'application est exécutée 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 avez à faire ici est de remplacer la référence ReconcilationEngine qui ressemble à un sinistre par une méthode qui optimise 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.

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

Le seul moyen que j’ai trouvé pour y parvenir est de vérifier si une console est liée au processus, en accédant à une propriété d’objet Console (par exemple, Titre) dans un bloc try / catch.

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

Cependant, comme cela ressemble un peu trop à compter sur un détail spécifique à l'implémentation (que se passe-t-il si le GDS 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 ...

Voici une traduction de la réponse de chksr en .NET, en é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;

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

Aucune modification de code nécessaire pour utiliser cette solution. J'ai un type de solution Debugger.IsAttached également assez générique pour être utilisé avec n'importe quel service. Le lien est dans cet article: Gestionnaire de services Windows .NET

Eh bien, il existe un très vieux code (environ 20 ans environ, pas de moi mais trouvé dans le Web sauvage et sauvage, et en C non C #) qui devrait vous donner une idée de la façon de faire le travail:

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);
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top