49

現在、コンソールで実行できるサービスのブートストラップ コードを少し書いています。基本的には、ServiceBase を使用してサービスを開始および停止する代わりに、OnStart() メソッドを呼び出すことになります (アプリケーションがサービスとしてインストールされていない場合はアプリケーションを実行せず、デバッグが悪夢になるため)。

現在、Debugger.IsAttached を使用して、ServiceBase.Run または [service].OnStart を使用する必要があるかどうかを判断していますが、エンド ユーザーがコンソールでサービスを実行したい場合があるため (出力などリアルタイム)。

Windows サービス コントローラーが「私」を開始したかどうか、またはユーザーがコンソールで「私」を開始したかどうかを判断する方法についてのアイデアはありますか? どうやらEnvironment.IsUserInteractiveは答えではありません。コマンドライン引数を使用することを考えましたが、それは「汚い」ようです。

ServiceBase.Run の周りの try-catch ステートメントについては常に確認できましたが、それは汚いようです。編集: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" であることに問題があり、それが答えのようです。今日テストします。

4

13 に答える 13

26

別の回避策..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);
}
于 2010-07-02T10:48:25.347 に答える
20

私は通常、コンソールを使用して実行する「-console」のコマンド ライン パラメーターを取るコンソール アプリケーションとして Windows サービスにフラグを立てます。それ以外の場合は、サービスとして実行されます。デバッグするには、プロジェクト オプションのコマンド ライン パラメータを「-console」に設定するだけで完了です。

これにより、デバッグが簡単になり、アプリがデフォルトでサービスとして機能するようになります。

于 2008-10-14T06:28:55.437 に答える
16

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
于 2008-10-20T16:01:19.943 に答える
15

私のために働くもの:

  • 実際のサービス作業を行うクラスは、別のスレッドで実行されます。
  • このスレッドは、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() ;
    }
}
于 2008-10-16T10:47:17.283 に答える
10

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);
    }
}
于 2010-01-21T17:44:12.377 に答える
10

ジョナサン、あなたの質問に対する正確な答えではありませんが、Windows サービスの作成を終えたばかりで、デバッグとテストの難しさも指摘しました。

実際のすべての処理コードを別のクラス ライブラリ アセンブリに記述し、それを Windows サービスの実行可能ファイル、コンソール アプリ、およびテスト ハーネスによって参照するだけで解決しました。

基本的なタイマー ロジックとは別に、より複雑な処理はすべて共通のアセンブリで行われ、必要に応じて非常に簡単にテスト/実行できました。

于 2008-10-14T06:34:44.580 に答える
3

プロセスの親がC:\ Windows \ system32\services.exeであるかどうかを確認している可能性があります。

于 2008-11-04T07:31:17.520 に答える
2

これを達成するために私が見つけた唯一の方法は、try/catch ブロック内のコンソール オブジェクト プロパティ (タイトルなど) にアクセスして、最初にコンソールがプロセスに接続されているかどうかを確認することです。

サービスが SCM によって開始された場合、コンソールはなく、プロパティにアクセスすると System.IO.IOError がスローされます。

ただし、これは実装固有の詳細に頼りすぎているように感じられるため (一部のプラットフォームの SCM またはいつか開始するプロセスにコンソールを提供することを決定した場合はどうなるでしょうか?)、私は常にコマンド ライン スイッチ (-console ) 本番アプリで...

于 2008-10-14T06:37:58.850 に答える
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;
于 2015-01-14T02:54:50.163 に答える
0

これは少しセルフプラグですが、リフレクションを介してアプリにサービス タイプをロードし、その方法で実行する小さなアプリがあります。ソース コードを含めますので、標準出力を表示するように少し変更できます。

このソリューションを使用するためにコードを変更する必要はありません。私は Debugger.IsAttached タイプのソリューションも持っています。これは、どのサービスでも使用できるほど汎用的です。リンクはこの記事にあります: .NET Windows Service Runner

于 2008-10-14T06:39:11.017 に答える
0

非常に古いコードがいくつかあります (約 20 年ほど、私からではなく、野生の野生の Web で発見され、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);
}
于 2014-09-12T11:43:37.000 に答える