2008-10-14 3 views
41

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

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

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

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

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

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: Был еще один вопрос, на StackOverflow, где парень имел проблемы с Environment.CurrentDirectory быть «C: \ Windows \ System32 "похоже, что это может быть ответ. Я буду проверять сегодня.

+0

Благодаря ДОРАМ добавив ваше решение, должно быть полезной справочной. – Ash 2008-10-16 09:57:22

+2

Не то, что IsUserInteractive будет * not * возвращать false для консольных приложений, как указано в ссылке, которую вы указали выше, по крайней мере, не в общем. Я использую его для этой цели, и у меня никогда не было никаких проблем. – 2010-03-06 09:22:55

+0

[Вот тот же вопрос для C++] (https://stackoverflow.com/questions/1974828/) – 2018-02-20 04:06:30

ответ

15

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

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

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 
19

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

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

+3

Точно так же я тоже это делаю. Работает очень хорошо; единственной уловкой с отладкой тогда является безопасность (какая учетная запись) и рабочая папка, которые легче кодировать. – 2008-10-14 06:43:30

9

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

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

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

+0

Это очень полезная информация, я думаю, что это «правильный» способ сделать это. Хотелось бы, чтобы вы могли принять два ответа :). – 2008-10-14 06:52:28

+0

Нет проблем Джонатан, рад, что это было полезно. В эти дни я стараюсь следовать этому подходу (отдельная сборка логики приложений) для всех приложений. Таким образом, служба Windows может рассматриваться как просто еще один вид представления в приложении. Я думаю, что это шаблон модели View View. – Ash 2008-10-15 03:07:45

2

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

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

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

-1

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

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

+0

Я на самом деле написал для них базовый класс, который имеет метод Start(), поэтому мне не нужно прибегать к отражению. Спасибо за подсказку. – 2008-10-14 07:02:16

+0

Это предназначено для автономного способа запуска любой службы вне среды служб Windows без изменения кода. Просто дважды нажмите на бегун, выберите свой сервис .exe или .dll и нажмите «ОК». Если вы запустите бегун для командной строки, вы увидите стандартный IO. – 2008-10-14 07:32:42

12

Что работает для меня:

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

Спасибо за подсказку gyrolf, но, к сожалению, Environment.UserInteractive применим только для приложений Windows Forms :( – 2008-10-17 05:29:29

+6

Поскольку я понимаю документацию и пример кода в ней, нет никаких ограничений на приложения Windows Forms. Я использую ее успешно в обычном режиме консольные приложения – gyrolf 2008-10-17 08:38:16

3

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

8

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

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

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

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

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

ServiceHost.Instance.RunningAsAService (Boolean)

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

Вот код:

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 ссылки с любым методом boostrapping вашей логики.Затем в вашем приложении используйте методы ServiceHost.Instance.Start() и ServiceHost.Instance.Stop(), независимо от того, работаете ли вы в режиме консоли или в качестве службы.

-1

Ну есть некоторые очень старый код (около 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); 
} 
1

Вот перевод ответа 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; 
Смежные вопросы