Я работаю как услуга

В настоящее время я пишу небольшой код начальной загрузки для службы, которую можно запустить в консоли. По сути, это сводится к вызову метода 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», похоже, это может быть ответ. Сегодня протестирую.

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

Ash 16.10.2008 13:57

Не то чтобы IsUserInteractive вернет нет false для консольных приложений, как было указано в приведенной вами ссылке - по крайней мере, не в целом. Я использую его для этой цели, и у меня никогда не было с этим проблем.

Christian.K 06.03.2010 12:22

Отвечает ли это на ваш вопрос? Определить, работает ли код как служба

MarkovskI 10.03.2020 15:54

@MarkovskI Я обычно голосую за новый вопрос как за дубликат этого старого и, если могу, добавляю ответ на старый (этот вопрос в данном случае), если это полезно.

Mark Schultheiss 20.06.2020 18:08

@MarkSchultheiss, если я вас правильно понял, я добавил ответ на этот, он был удален :)

MarkovskI 22.06.2020 13:05

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

Mark Schultheiss 22.06.2020 16:21
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
48
7
35 531
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

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

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

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

Marc Gravell 14.10.2008 10:43

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

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

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

Это очень полезная информация, я думаю, это «правильный» способ сделать это. Я бы хотел, чтобы вы приняли два ответа :).

Jonathan C Dickinson 14.10.2008 10:52

Нет проблем, Джонатан, рад, что это было полезно. Сейчас я стараюсь следовать этому подходу (отдельная сборка логики приложения) для всех приложений. Таким образом, службу Windows можно рассматривать как еще один вид приложения. Я предполагаю, что это шаблон контроллера представления модели.

Ash 15.10.2008 07:07

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

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

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

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

Для использования этого решения не требуется никаких изменений кода. У меня также есть решение типа Debugger.IsAttached, которое достаточно универсально для использования с любой службой. Ссылка в этой статье: .NET Windows Service Runner

На самом деле я написал для них базовый класс, который имеет метод Start (), так что мне не нужно прибегать к отражению. Тем не менее, спасибо за совет.

Jonathan C Dickinson 14.10.2008 11:02

Это разработано как автономный способ запуска любой службы вне среды служб Windows без изменения кода. Просто дважды щелкните бегунок, выберите свой сервис .exe или .dll и нажмите «ОК». Если вы запустите бегун для командной строки, вы увидите стандартный ввод-вывод.

Anderson Imes 14.10.2008 11:32

Что мне подходит:

  • Класс, выполняющий фактическую работу по обслуживанию, работает в отдельном потоке.
  • Этот поток запускается из метода 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() ;
    }
}

Спасибо за подсказку gyrolf, но, к сожалению, Environment.UserInteractive верно только для приложений Windows Forms :(.

Jonathan C Dickinson 17.10.2008 09:29

Насколько я понимаю документацию и пример кода в ней, нет никаких ограничений для приложений Windows Forms. Успешно использую в обычных консольных приложениях.

gyrolf 17.10.2008 12:38

Могу подтвердить, что это правильно. Environment.UserInteractive имеет значение True при работе с консолью и False при работе в качестве службы.

voithos 21.12.2011 23:18

В некоторых случаях этот код не работает - если вы запускаете это приложение из планировщика Windows в качестве задачи, тогда Environment.UserInteractive устанавливается в значение false, даже если это не служба. Если вы планируете запускать приложение из планировщика, подумайте о более надежном решении.

pg0xC 10.04.2015 14:04

@ pg0xC Приведенный выше класс является производным от ServiceBase. Я не думаю, что кто-то попытается запустить это как задачу. Первоначальный вопрос касался приложения, которое можно было запускать на консоли или в качестве службы. Я согласен с voithos, что Environment.UserInteractive работает в желаемых случаях. Если вы запускаете приложение как задачу, оно ДОЛЖНО возвращать false.

Derek Johnson 21.04.2017 22:04
Ответ принят как подходящий

Как и Эш, я пишу весь фактический код обработки в отдельной сборке библиотеки классов, на которую затем ссылается исполняемый файл службы 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

Я не понимаю, как это могло бы работать, если бы одна и та же сборка могла быть запущена как консольное приложение или как служба Windows ... Assembly.GetEntryAssembly () и Assembly.EntryPoint возвращают одинаковые значения в обоих случаях. Я предполагаю, что это работает только в том случае, если вы запускаете разные сборки в двух случаях.

Dan Ports 19.12.2011 21:08

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

Kramii 20.12.2011 00:33

Я получаю "System.Object" вместо значения "System.ServiceProcess.ServiceBase" в качестве возвращаемого значения ..ReflectedType.BaseType.FullName (и да, я запускаю код как службу из окна Services)?

Emre Guldogan 09.08.2019 14:34

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

Я изменил 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);
    }
}

Пример выше не обрабатывает кавычки должным образом, проверьте это для лучшего решения stackoverflow.com/questions/4862580/…

Palani 22.01.2012 18:54

Улучшена обработка кавычек вокруг пути.

Rolf Kristensen 28.11.2013 17:36

Другой обходной путь ... так что можно запускать как 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);
}

Мне нравится это решение, похоже, именно для этого был разработан Environment.UserInteractive.

Josh M. 30.11.2011 21:25

Интересно, что бы произошло, если бы вы отметили «Разрешить службе взаимодействовать с рабочим столом» для этой службы. Насколько я знаю, это позволит сервису иметь графический интерфейс. Разве тогда свойство UserInteractive не должно возвращать true? [MSDN: свойство UserInteractive сообщает false для процесса Windows или такой службы, как IIS, которая работает без пользовательского интерфейса.]

Mircea Ion 29.08.2013 17:42

Я тестировал: когда вы устанавливаете флажок «Разрешить службе взаимодействовать с рабочим столом», то UserInteractive верен.

csname1910 13.08.2014 04:10

Я попытался запустить свой процесс из контейнера Windows Docker, и там UserInteractive также неверен ... Тем не менее, я определенно не работаю как служба.

9Rune5 04.05.2017 16:03

Эта ветка действительно старая, но я подумал, что выкину свое решение там. Проще говоря, чтобы справиться с подобными ситуациями, я создал «служебную привязку», которая используется как в консоли, так и в служебных случаях 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() независимо от того, работаете ли вы в консольном режиме или в качестве службы.

Ну, есть очень старый код (лет 20 или около того, не от меня, но найденный в дикой, дикой сети и на C, а не на C#), который должен дать вам представление о том, как выполнять эту работу:

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

Это не похоже на C#.

LarsTech 12.09.2014 15:47

Но ведь его можно было бы перевести на C#, не так ли?

chksr 13.11.2014 18:10

Не так-то просто C# -ified

as9876 30.11.2015 23:03

В Си есть гораздо более простой способ сделать это. В какой-то момент вам нужно вызвать StartServiceCtrlDispatcher в вашем коде C. Если этот вызов завершился неудачно, И GetLastError вернет ERROR_FAILED_SERVICE_CONTROLLER_CONNECT, значит, вы знаете, что работаете не как служба. Интересно, есть ли этому аналог на C#?

Ferruccio 08.04.2016 19:08

@Ferruccio да, вы можете вызвать ServiceBase.Run и поймать исключение

Pavel Mayorov 21.06.2017 11:56

ServiceBase.Run не обязательно вызывает исключение при вызове из консольного приложения. В моем тестовом коде с использованием .NET Core 2.2 if просто выводит следующую ошибку в окно консоли и продолжает работу без исключения: Невозможно запустить службу из командной строки или отладчика. A Windows Service must first be installed and then started with the ServerExplorer, Windows Services Administrative tool or the NET START command.

James 16.08.2019 02:01

Вот перевод ответа 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;

Кажется, я немного опаздываю на вечеринку, но интересная разница при запуске в качестве службы заключается в том, что при запуске текущая папка указывает на системный каталог (по умолчанию C:\windows\system32). Едва ли маловероятно, что пользовательское приложение запустится из системной папки в любой реальной жизненной ситуации.

Итак, я использую следующий трюк (C#):

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

    return (false);
}

Для будущего расширения необходимо выполнить дополнительную проверку для System.Environment.UserInteractive == false (но я не знаю, как это соотносится с настройками службы «Разрешить службе взаимодействовать с рабочим столом»).

Вы также можете проверить сеанс окна с помощью System.Diagnostics.Process.GetCurrentProcess().SessionId == 0 (я не знаю, как он соотносится с настройками службы «Разрешить службе взаимодействовать с рабочим столом»).

Если вы пишете переносимый код (скажем, с помощью .NetCore), вы также можете проверить Environment.OSVersion.Platform, чтобы убедиться, что вы сначала находитесь в Windows.

Текущий каталог может быть system32 во множестве реальных случаев, таких как запуск приложения от имени администратора, запуск с повышенными привилегиями, запуск его из сценария или ручное копирование туда. Это НЕ способ сказать, что вы работаете как служба.

himself 07.06.2020 23:03

Другие вопросы по теме