現在、コンソールで実行できるサービスのコードbootstrapコードを書いています。基本的に、ServiceBaseを使用してサービスを開始および停止する代わりに、OnStart()メソッドを呼び出すことになります。 (サービスとしてインストールされていない場合はアプリケーションを実行せず、デバッグを悪夢にするためです)。
現在、Debugger.IsAttachedを使用してServiceBase.Runまたは[service] .OnStartを使用する必要があるかどうかを判断していますが、エンドユーザーがコンソールでサービスを実行したい場合があるため(これを表示するには)出力などリアルタイム)。
Windowsサービスコントローラーが「私」を開始したかどうか、またはユーザーがコンソールで「私」を開始したかどうかを判断する方法に関するアイデアはありますか?明らかに Environment.IsUserInteractive は答えではありません。コマンドライン引数の使用を考えましたが、それは「汚い」ようです。
ServiceBase.Runに関するtry-catchステートメントについては常に確認できましたが、それは汚いようです。編集:try 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();
}
}
編集:StackOverflowで別の質問がありました。男がEnvironment.CurrentDirectoryが "C:\ Windows\System32"であるという問題があったので、それが答えのようです。今日テストします。
Ashと同様に、実際の処理コードはすべて別個のクラスライブラリアセンブリに記述します。このアセンブリは、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
別の回避策.. 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);
}
私は通常、Windowsサービスに「-console」というコマンドラインパラメーターを使用してコンソールを使用して実行するコンソールアプリケーションとしてフラグを立てます。デバッグするには、プロジェクトオプションのコマンドラインパラメータを "-console"に設定するだけです。
これにより、デバッグが簡単になり、アプリはデフォルトでサービスとして機能します。
私のために働くもの:
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() ;
}
}
ProjectInstallerを変更して、サービスとしてインストールされるときにコマンドライン引数パラメーター/ serviceを追加しました。
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);
}
}
ジョナサン、あなたの質問への正確な回答ではありませんが、Windowsサービスの作成を終えたばかりで、デバッグとテストの難しさも指摘しました。
実際のすべての処理コードを別のクラスライブラリアセンブリに書き込むだけで解決しました。これは、Windowsサービスの実行可能ファイル、およびコンソールアプリとテストハーネスによって参照されました。
基本的なタイマーロジックとは別に、より複雑な処理はすべて共通のアセンブリで行われ、非常に簡単にオンデマンドでテスト/実行できます。
このスレッドは本当に古いですが、私は自分の解決策をそこに捨てると思いました。簡単に言うと、この種の状況に対処するために、コンソールと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()
メソッドを使用して、コンソールモードで実行しているか、サービスとして実行しているかを確認します。
プロセスの親がC:\ Windows\system32\services.exeかどうかを確認します。
これを実現するために私が見つけた唯一の方法は、try/catchブロック内のConsoleオブジェクトプロパティ(例:Title)にアクセスして、最初にコンソールがプロセスに接続されているかどうかを確認することです。
SCMによってサービスが開始された場合、コンソールは存在せず、プロパティにアクセスするとSystem.IO.IOErrorがスローされます。
ただし、これは実装固有の詳細に依存しているように感じる(プラットフォームによっては、SCMが起動したプロセスにコンソールを提供することを決定した場合はどうなるのか)ので、常にコマンドラインスイッチ(-console )本番アプリでは...
これは.NETに対するchksrの回答の翻訳であり、インタラクティブサービスを認識できないバグを回避しています。
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を起動していることを確認することもできます。
これは少し自己プラグインですが、リフレクションを介してアプリにサービスタイプをロードし、そのように実行する小さなアプリがあります。ソースコードを含めたので、標準出力を表示するように少し変更できます。
このソリューションを使用するためにコードを変更する必要はありません。 Debugger.IsAttachedタイプのソリューションもあり、どのサービスでも使用できるほど汎用的です。リンクはこの記事にあります: 。NET Windows Service Runner