Я работаю как услуга - PullRequest
44 голосов
/ 14 октября 2008

В настоящее время я пишу небольшой код начальной загрузки для службы, которая может быть запущена в консоли. По сути, он сводится к вызову метода OnStart () вместо использования ServiceBase для запуска и остановки службы (поскольку он не запускает приложение, если оно не установлено в качестве службы, и делает отладку кошмаром).

Сейчас я использую Debugger.IsAttached, чтобы определить, использовать ли мне ServiceBase.Run или [service] .OnStart, но я знаю, что это не лучшая идея, потому что иногда конечные пользователи хотят запускать службу в консоли (чтобы увидеть результат и т. д. в реальном времени).

Есть какие-нибудь идеи о том, как я могу определить, запускает ли диспетчер служб Windows «я» или пользователь запускает «я» в консоли? Очевидно, Environment.IsUserInteractive не является ответом. Я думал об использовании аргументов командной строки, но это кажется «грязным».

Я всегда мог видеть оператор try-catch вокруг ServiceBase.Run, но это выглядит грязно. Изменить: Попытка поймать не работает.

У меня есть решение: выложить его здесь для всех других заинтересованных укладчиков:

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

РЕДАКТИРОВАТЬ: Был еще один вопрос о StackOverflow, где у парня были проблемы с Environment.CurrentDirectory, "C: \ Windows \ System32" выглядит так, что это может быть ответ. Я проверю сегодня.

Ответы [ 12 ]

25 голосов
/ 02 июля 2010

Еще один обходной путь .. поэтому можно запустить как WinForm или как службу 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);
}
20 голосов
/ 14 октября 2008

Я обычно отмечаю службой Windows как консольное приложение, которое принимает параметр командной строки «-console» для запуска в качестве консоли, в противном случае оно запускается как служба. Для отладки вы просто устанавливаете параметры командной строки в опциях проекта на "-console" и все выключено!

Это делает отладку приятной и простой и означает, что приложение по умолчанию функционирует как служба, что вам и нужно.

14 голосов
/ 20 октября 2008

Как и Эш, я записываю весь фактический код обработки в отдельную сборку библиотеки классов, на которую затем ссылается исполняемый файл службы Windows, а также консольное приложение.

Однако бывают случаи, когда полезно знать, работает ли библиотека классов в контексте исполняемого файла службы или приложения консоли. Я делаю это, чтобы отразить базовый класс хостинг-приложения. (Извините за VB, но я представляю, что следующее можно довольно легко c-указать):

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
14 голосов
/ 16 октября 2008

Что у меня работает:

  • Класс, выполняющий фактическую работу службы, выполняется в отдельном потоке.
  • Этот поток запускается из метода OnStart () и останавливается из OnStop ().
  • Решение между сервисом и консольным режимом зависит от Environment.UserInteractive

Пример кода:

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() ;
    }
}
9 голосов
/ 21 января 2010

Я изменил ProjectInstaller для добавления параметра / службы аргумента командной строки, когда он устанавливается как служба:

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 затем изменяется, чтобы переопределить OnBeforeInstall () и 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 голосов
/ 14 октября 2008

Джонатан, не совсем ответ на ваш вопрос, но я только что закончил писать службу Windows и также отметил трудности с отладкой и тестированием.

Решил это, просто записав весь фактический код обработки в отдельную сборку библиотеки классов, на которую затем ссылался исполняемый файл службы Windows, а также консольное приложение и тестовая программа.

Помимо базовой логики таймера, все более сложные процессы выполняются в общей сборке и могут быть невероятно легко протестированы / запущены по требованию.

4 голосов
/ 04 апреля 2012

Эта ветка действительно старая, но я думал, что выложу свое решение там. Проще говоря, чтобы справиться с подобной ситуацией, я создал «жгут проводов», который используется как в консоли, так и в случаях обслуживания Windows. Как и выше, большая часть логики содержится в отдельной библиотеке, но это больше для тестирования и «связности».

Прикрепленный код никоим образом не представляет собой «наилучший из возможных» способов решения этой проблемы, только мой собственный подход. Здесь служебный жгут вызывается консольным приложением в «режиме консоли» и логикой «запуска службы» того же приложения, когда оно работает как служба. Делая это таким образом, теперь вы можете позвонить

ServiceHost.Instance.RunningAsAService (логическое значение)

из любого места в вашем коде, чтобы проверить, работает ли приложение как служба или просто как консоль.

Вот код:

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

Все, что вам нужно здесь сделать, - это заменить зловещую ссылку ReconcilationEngine на любой метод, усиливающий вашу логику. Затем в своем приложении используйте методы ServiceHost.Instance.Start() и ServiceHost.Instance.Stop() независимо от того, работаете ли вы в режиме консоли или в качестве службы.

3 голосов
/ 04 ноября 2008

Возможно, проверяется, является ли родительский процесс C: \ Windows \ system32 \ services.exe.

2 голосов
/ 14 октября 2008

Единственный способ, который я нашел для достижения этой цели, - это проверить, подключена ли в первую очередь консоль к процессу, путем доступа к любому свойству объекта консоли (например, Заголовку) внутри блока try / catch.

Если служба запущена SCM, консоли нет, а доступ к свойству вызовет ошибку System.IO.IOError.

Однако, так как это слишком похоже на использование деталей, специфичных для реализации (что, если SCM на некоторых платформах или когда-нибудь решит предоставить консоль процессам, которые он запускает?), Я всегда использую ключ командной строки (-консоль) в производственных приложениях ...

1 голос
/ 14 января 2015

Вот перевод ответа chksr в .NET, а также ошибка, которая не может распознать интерактивные сервисы:

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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...