Pregunta

Actualmente estoy escribiendo un pequeño código de arranque para un servicio que se puede ejecutar en la consola. Básicamente, se reduce a llamar al método OnStart () en lugar de usar ServiceBase para iniciar y detener el servicio (porque no ejecuta la aplicación si no está instalada como un servicio y hace que la depuración sea una pesadilla).

Ahora mismo estoy usando Debugger.IsAttached para determinar si debo usar ServiceBase.Run o [service] .OnStart, pero sé que no es la mejor idea porque algunas veces los usuarios finales desean ejecutar el servicio en una consola (para ver la salida, etc. en tiempo real).

¿Alguna idea sobre cómo podría determinar si el controlador de servicio de Windows inició 'yo' o ??si el usuario comenzó 'yo' en la consola? Aparentemente Environment.IsUserInteractive no es la respuesta. Pensé en usar argumentos de la línea de comandos, pero eso parece "sucio".

Siempre pude ver una declaración try-catch alrededor de ServiceBase.Run, pero eso parece sucio. Edición: probar captura no funciona.

Tengo una solución: ponerlo aquí para todos los demás apiladores interesados:

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

EDITAR: había otra pregunta en StackOverflow en la que el tipo tenía problemas con el Environment.CurrentDirectory siendo " C: \ Windows \ System32 " Parece que esa puede ser la respuesta. Voy a probar hoy.

¿Fue útil?

Solución

Al igual que Ash, escribo todo el código de procesamiento real en un conjunto de biblioteca de clases separado, al que luego hizo referencia el ejecutable del servicio de Windows, así como una aplicación de consola.

Sin embargo, hay ocasiones en que es útil saber si la biblioteca de clases se está ejecutando en el contexto del servicio ejecutable o la aplicación de la consola. La forma en que hago esto es reflexionar sobre la clase base de la aplicación de alojamiento. (Lo siento por el VB, pero me imagino que lo siguiente podría ser c--ified con bastante facilidad):

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

Otros consejos

Otra solución alternativa ... puede ejecutarse como WinForm o como servicio de 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);
}

Por lo general, el servicio de Windows lo marca como una aplicación de consola que toma un parámetro de línea de comando de " -console " para ejecutar como una consola, de lo contrario se ejecuta como un servicio. Para depurar, simplemente configure los parámetros de la línea de comandos en las opciones del proyecto en " -console " y ya estas fuera!

Esto hace que la depuración sea agradable y fácil, y significa que la aplicación funciona como un servicio por defecto, que es lo que querrás.

Lo que funciona para mí:

  • La clase que realiza el trabajo de servicio real se ejecuta en un subproceso separado.
  • Este hilo se inicia desde el método OnStart () y se detiene desde OnStop ().
  • La decisión entre el servicio y el modo de consola depende de Environment.UserInteractive

Código de muestra:

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, no es exactamente una respuesta a tu pregunta, pero acabo de terminar de escribir un servicio de Windows y también noté la dificultad con la depuración y las pruebas.

Se resolvió simplemente escribiendo todo el código de procesamiento real en un conjunto de biblioteca de clases separado, al que luego hizo referencia el ejecutable del servicio de Windows, así como una aplicación de consola y un arnés de prueba.

Además de la lógica básica del temporizador, todo el procesamiento más complejo sucedió en el ensamblaje común y podría probarse / ejecutarse a pedido de manera increíblemente fácil.

He modificado ProjectInstaller para agregar el parámetro / servicio de la línea de comandos, cuando se está instalando como servicio:

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

El ProjectInstaller.cs se modifica para anular un OnBeforeInstall () y 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);
    }
}

Este hilo es muy antiguo, pero pensé que lanzaría mi solución allí. Sencillamente, para manejar este tipo de situación, construí un " arnés de servicio " que se utiliza tanto en la consola como en los casos de servicio de Windows. Como se mencionó anteriormente, la mayor parte de la lógica está contenida en una biblioteca separada, pero esto es más para pruebas y "vinculabilidad".

El código adjunto de ninguna manera representa el " mejor " posible " Manera de resolver esto, solo mi propio enfoque. Aquí, la aplicación de la consola llama al arnés del servicio cuando está en " modo de consola " y por el mismo " servicio de inicio " Lógica cuando se está ejecutando como un servicio. Al hacerlo de esta manera, ahora puedes llamar

ServiceHost.Instance.RunningAsAService (Boolean)

desde cualquier parte de su código para verificar si la aplicación se está ejecutando como un servicio o simplemente como una consola.

Aquí está el 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);
    }
}

Todo lo que necesitas hacer aquí es reemplazar esa siniestra referencia de ReconcilationEngine con cualquier método que sea para mejorar tu lógica. Luego, en su aplicación, use los métodos ServiceHost.Instance.Start () y ServiceHost.Instance.Stop () , ya sea que esté ejecutando en modo consola o como servicio.

Tal vez verifique si el proceso primario es C: \ Windows \ system32 \ services.exe.

La única forma que he encontrado para lograr esto, es verificar si una consola está conectada al proceso en primer lugar, accediendo a cualquier propiedad del objeto de la Consola (por ejemplo, Título) dentro de un bloque try / catch.

Si el servicio es iniciado por el SCM, no hay consola, y acceder a la propiedad lanzará un System.IO.IOError.

Sin embargo, dado que esto se parece demasiado a depender de un detalle específico de la implementación (¿qué sucede si el SCM en algunas plataformas o algún día decide proporcionar una consola a los procesos en los que comienza?), siempre uso un interruptor de línea de comandos (-console) en aplicaciones de producción ...

Aquí hay una traducción de la respuesta de chksr a .NET, y evitar el error que no reconoce los servicios interactivos:

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;

Esto es un poco de auto-conexión, pero tengo una pequeña aplicación que cargará tus tipos de servicio en tu aplicación a través de la reflexión y los ejecutará de esa manera. Incluyo el código fuente, por lo que podría cambiarlo ligeramente para mostrar la salida estándar.

No se necesitan cambios de código para usar esta solución. También tengo un tipo de solución Debugger.IsAttached que es lo suficientemente genérico para ser utilizado con cualquier servicio. El enlace está en este artículo: .NET Service Runner de Windows

Bueno, hay un código muy antiguo (alrededor de 20 años aproximadamente, no de mi parte pero que se encuentra en la red salvaje, en la red salvaje, y en C no en C #) que debería darle una idea de cómo hacer el trabajo:

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);
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top