Я работаю как служба
В настоящее время я пишу небольшой код начальной загрузки для службы, которую можно запустить в консоли. По сути, это сводится к вызову метода OnStart() вместо того, чтобы использовать ServiceBase для запуска и остановки службы (потому что она не запускает приложение, если оно не установлено как служба и делает отладку кошмаром).
В настоящее время я использую Debugger.IsAttached, чтобы определить, следует ли мне использовать ServiceBase.Run или [service].OnStart, но я знаю, что это не лучшая идея, потому что некоторые конечные пользователи хотят запустить службу в консоли (чтобы увидеть выход и т.д. в реальном времени).
Любые идеи о том, как я могу определить, запущен ли сервисный контроллер Windows "я", или пользователь начал "меня" в консоли? Очевидно, Environment.IsUserInteractive - не ответ. Я думал об использовании командной строки args, но это кажется "грязным".
Я всегда мог видеть выражение о try-catch в 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", выглядит так, что это может быть ответ. Я буду тестировать сегодня.
Ответы
Ответ 1
Как и Эш, я пишу весь фактический код обработки в отдельной сборке библиотеки классов, на которую затем ссылается исполняемый файл службы 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
Ответ 2
Еще одно обходное решение.. поэтому может работать как 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);
}
Ответ 3
Я обычно отмечаю свою службу Windows как консольное приложение, которое принимает параметр командной строки "-console" для запуска с использованием консоли, в противном случае она запускается как служба. Для отладки вы просто устанавливаете параметры командной строки в опциях проекта на "-console", и все готово!
Это делает отладку приятной и простой и означает, что приложение функционирует как служба по умолчанию, что вам и нужно.
Ответ 4
Что работает для меня:
- Класс, выполняющий фактическую работу службы, запускается в отдельном потоке.
- Этот поток запускается из метода 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() ;
}
}
Ответ 5
Джонатан, не совсем ответ на ваш вопрос, но я только что закончил писать службу Windows, а также отметил трудности с отладкой и тестированием.
Решил его простым написанием всего фактического кода обработки в отдельной сборке библиотеки классов, на который тогда ссылался исполняемый файл службы Windows, а также консольное приложение и тестовый жгут.
Помимо основной таймерной логики, все более сложная обработка происходила в общей сборке и могла быть протестирована/запущена по требованию невероятно легко.
Ответ 6
Я изменил 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);
}
}
Ответ 7
Эта тема действительно старая, но я думал, что брошу свое решение там. Достаточно просто, чтобы справиться с такой ситуацией, я построил "служебную упряжь", которая используется как в консольных, так и в 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
с помощью любого метода, который расширяет вашу логику. Затем в вашем приложении используйте методы ServiceHost.Instance.Start()
и ServiceHost.Instance.Stop()
, если вы работаете в режиме консоли или в качестве службы.
Ответ 8
Возможно, проверьте, является ли родитель процесса C:\Windows\system32\services.exe.
Ответ 9
Единственный способ, который я нашел для этого, - проверить, подключена ли консоль к процессу в первую очередь, путем доступа к любому объекту Консоли (например, Title) внутри блока try/catch.
Если служба запускается SCM, консоли нет, и доступ к свойству будет вызывать System.IO.IOError.
Однако, поскольку это немного похоже на то, что я полагаюсь на детали, специфичные для реализации (что, если SCM на некоторых платформах или когда-нибудь решит предоставить консоль для запуска процессов?), я всегда использую переключатель командной строки (-console) в производственных приложениях...
Ответ 10
Вот перевод ответа 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;
Ответ 11
Это немного самозапуск, но у меня есть небольшое приложение, которое будет загружать ваши типы услуг в вашем приложении через отражение и выполнять их таким образом. Я включаю исходный код, поэтому вы можете немного изменить его, чтобы отобразить стандартный вывод.
Никаких изменений кода, необходимых для использования этого решения. У меня также есть решение Debugger.IsAttached, которое достаточно общего для использования с любой услугой. Ссылка есть в этой статье:
.NET Windows Service Runner
Ответ 12
Хорошо, там очень старый код (около 20 лет или около того, но не от меня, а в дикой природе, в дикой сети и в 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 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);
}