【问题标题】:Am I Running as a Service我是否作为服务运行
【发布时间】:2008-10-14 06:19:09
【问题描述】:

我目前正在为可以在控制台中运行的服务编写一些引导代码。它本质上归结为调用 OnStart() 方法,而不是使用 ServiceBase 来启动和停止服务(因为如果应用程序没有作为服务安装,它就不会运行应用程序,并且会使调试成为一场噩梦)。

现在我正在使用 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”时遇到问题,看起来这可能是答案。我今天会测试。

【问题讨论】:

  • 感谢您添加您的解决方案,应该是一个有用的参考。
  • 并不是 IsUserInteractive 将 not 为控制台应用程序返回 false,正如您在上面提供的链接中所指出的那样 - 至少一般情况下不会。我将它用于此目的,并且从未遇到任何问题。
  • 这能回答你的问题吗? Detect if code is running as a service
  • @MarkSchultheiss 如果我理解正确的话,我确实添加了一个答案,它被删除了:)

标签: c# .net windows-services


【解决方案1】:

另一种解决方法.. 所以可以作为 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 的设计目的。
  • 我想知道如果您为该服务选中了“允许服务与桌面交互”会发生什么。据我所知,这将允许该服务具有 GUI。那么 UserInteractive 属性不应该返回 true 吗? [MSDN:对于 Windows 进程或在没有用户界面的情况下运行的 IIS 等服务,UserInteractive 属性报告为 false。]
  • 我测试过:当你勾选“允许服务与桌面交互”时,UserInteractive 为真。
  • 我尝试从 Windows Docker 容器运行我的进程,并且在那里 UserInteractive 也是错误的......但我绝对没有作为服务运行。
  • 危险:有几种情况Environment.UserInteractive == false 即使没有服务。其中之一是应用程序作为 LocalSystem 运行时。
【解决方案2】:

我通常将我的 Windows 服务标记为控制台应用程序,它采用“-console”的命令行参数来使用控制台运行,否则它作为服务运行。要进行调试,只需将项目选项中的命令行参数设置为“-console”即可!

这使调试变得简单而轻松,这意味着应用程序默认作为服务运行,这正是您想要的。

【讨论】:

  • 我也是这样做的。效果很好;调试的唯一问题是安全性(哪个帐户)和工作文件夹 - 更容易编写代码。
【解决方案3】:

与 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

【讨论】:

  • 如果同一个程序集可以作为控制台应用程序或 Windows 服务运行,我看不出这将如何工作...Assembly.GetEntryAssembly() 和 Assembly.EntryPoint 在两种情况。我猜它只有在这两种情况下运行不同的程序集时才有效。
  • @DanPorts:我从未尝试将相同的程序集同时作为控制台应用程序和 Windows 服务运行。但是,有时将同一组类编译到每种应用程序中是很有用的,在这种情况下,上面的类可用于确定它在哪个上下文中使用。
  • 我得到 "System.Object" 而不是 "System.ServiceProcess.ServiceBase" 值作为 ..ReflectedType.BaseType 的返回值。全名(是的,我从“服务”窗口将代码作为服务运行)?
【解决方案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() ;
    }
}

【讨论】:

  • 感谢 gyrolf 的提示,但不幸的是 Environment.UserInteractive 仅适用于 Windows 窗体应用程序:(。
  • 据我了解文档和其中的示例代码,对 Windows 窗体应用程序没有任何限制。我在普通控制台应用程序中成功使用它。
  • 我可以确认这是正确的。 Environment.UserInteractive 在运行控制台时为 True,如果作为服务运行则为 False。
  • 此代码在某些情况下会失败 - 如果您从 Windows 调度程序将此应用程序作为任务运行,则 Environment.UserInteractive 设置为 false,即使它不是服务。如果您打算从调度程序运行应用程序,请考虑更强大的解决方案。
  • @pg0xC 上面的类派生自 ServiceBase。我认为没有人会尝试将此作为任务运行。最初的问题是关于可以在控制台中运行或作为服务运行的应用程序。我同意 voithos 的观点,即 Environment.UserInteractive 在所需的情况下工作。如果您将应用程序作为任务运行,它应该返回 false。
【解决方案5】:

Jonathan,不完全是您问题的答案,但我刚刚完成了一个 Windows 服务的编写,并且还注意到了调试和测试的困难。

只需在单独的类库程序集中编写所有实际处理代码即可解决此问题,然后由 Windows 服务可执行文件以及控制台应用程序和测试工具引用。

除了基本的计时器逻辑之外,所有更复杂的处理都发生在通用程序集中,并且可以非常容易地按需测试/运行。

【讨论】:

  • 这是非常有用的信息,我想这是“正确”的做法。我希望你能接受两个答案:)。
  • 没问题乔纳森,很高兴它有用。这些天来,我尝试对所有应用程序遵循这种方法(单独的应用程序逻辑组件)。这样一来,Windows 服务就可以被视为应用程序的另一种视图。我猜这就是模型视图控制器模式。
【解决方案6】:

我已经修改了 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);
    }
}

【讨论】:

【解决方案7】:

这个帖子真的很老了,但我想我会把我的解决方案放在那里。很简单,为了处理这种情况,我构建了一个用于控制台和 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() 方法,无论您是在控制台模式下运行还是作为服务运行。

【讨论】:

    【解决方案8】:

    可能检查进程父进程是否为 C:\Windows\system32\services.exe。

    【讨论】:

      【解决方案9】:

      我发现实现这一点的唯一方法是首先通过访问 try/catch 块中的任何控制台对象属性(例如标题)来检查控制台是否附加到进程。

      如果服务是单片机启动的,没有控制台,访问属性会抛出System.IO.IOError。

      但是,由于这感觉有点过于依赖特定于实现的细节(如果某些平台上的 SCM 或某天决定为其启动的进程提供控制台怎么办?),我总是使用命令行开关(-console) 在生产应用中...

      【讨论】:

        【解决方案10】:

        这里是chksr对.NET的回答的翻译,避免了无法识别交互服务的bug:

        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;
        

        【讨论】:

        • 即使没有服务,进程也可以作为 LocalSystem 运行:psexec -s MyApp.exe。在这种情况下,默认情况下它也不是交互式的。
        • @marsh-wiggle:IMO 如果服务在进程外完成某些工作,则生成的子进程仍然是“服务的一部分”。但如果他们不是服务的根进程,他们确实不想调用StartServiceCtrlDispatcher
        【解决方案11】:

        这有点像自插式,但我有一个小应用程序,它可以通过反射将您的服务类型加载到您的应用程序中并以这种方式执行它们。我包含了源代码,因此您可以稍微更改它以显示标准输出。

        使用此解决方案无需更改代码。我也有一个 Debugger.IsAttached 类型的解决方案,它足够通用,可以与任何服务一起使用。链接在这篇文章中: .NET Windows Service Runner

        【讨论】:

        • 我实际上为它们编写了一个具有 Start() 方法的基类,这样我就不必求助于反射了。不过感谢您的提示。
        • 这是一种独立的方式,可以在 Windows 服务环境之外运行任何服务,而无需更改任何代码。只需双击运行程序,选择您的服务 .exe 或 .dll,然后单击确定。如果您为命令行运行运行程序,您将看到标准 IO。
        【解决方案12】:

        嗯,有一些非常古老的代码(大约 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#。
        • 但是可以翻译成C#,不是吗?
        • 不容易c#化
        • 在 C 语言中有一种更简单的方法。在某些时候,您必须在 C 代码中调用 StartServiceCtrlDispatcher。如果该调用失败并且GetLastError 返回ERROR_FAILED_SERVICE_CONTROLLER_CONNECT,那么您就知道您没有作为服务运行。我想知道 C# 中是否有类似的东西?
        • ServiceBase.Run 从控制台应用程序调用时不一定会引发异常。在我使用 .NET Core 2.2 的测试代码下,如果只是将以下错误打印到控制台窗口并继续无异常运行:无法从命令行或调试器启动服务。 A Windows Service must first be installed and then started with the ServerExplorer, Windows Services Administrative tool or the NET START command.
        【解决方案13】:

        似乎我参加聚会有点晚了,但作为服务运行时有趣的区别是,在开始时,当前文件夹指向系统目录(默认为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 在无数现实生活案例中,例如以管理员身份运行应用程序、以提升的权限运行、从脚本运行或手动 CD 在那里。这不是一种告诉您正在作为服务运行的方法。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-13
        相关资源
        最近更新 更多