C#/.NET: 检测程序是作为服务运行还是控制台应用程序

28
我有一个C#/.NET程序,既可以作为控制台应用程序运行,也可以作为服务运行。 目前,我使用命令行选项将其启动为控制台应用程序,但我想避免这样做。
是否可能通过编程方式检测我的程序是否正在作为服务启动?
如果它是纯Win32,我可以尝试使用StartServiceCtrlDispatcher以服务的身份启动,并在返回ERROR_FAILED_SERVICE_CONTROLLER_CONNECT时回退到控制台,但是System.ServiceProcess.ServiceBase.Run()如果失败,则会弹出错误对话框,然后只需返回而不向程序发出错误信号。
有任何想法?

1
也许原帖作者不知道这是一个重复问题,因为已经有答案的那个问题的标题很糟糕。 - rory.ap
8个回答

38

5
如果服务在运行Windows XP,并设置为“允许此服务与桌面交互”,那会怎么样? - ta.speot.is
31
它会停止施展魔法,就是这样 :) - Ishmaeel
这也会在各种Cygwin shell和类似的环境下出现问题。你需要使用WMI强制执行才能正确地完成此操作。 - Alastair Maw
在Docker进程中运行时,如果没有使用“交互式终端”开关,它并不会真正执行任何魔法。Console.ReadKey将失败,但Environment.UserInteractive返回true。 - Jørn Jensen
它不会!在LocalSystem上下文中运行您的应用程序 psexec -s YourApp.exe 并且 Environment.UserInteractive == false - marsh-wiggle

8

您可以尝试使用进程对象的SessionId属性。以我的经验来看,如果进程正在运行服务,则SessionId设置为0。


1
这种方法简单、快速、可靠。它不能告诉你你的进程是否是服务,还是仅作为服务的子进程运行,但这确实有效(请注意,它仅适用于Vista及以上版本 - 在此之前,第一个用户登录会话也将是会话ID零,并与服务共享)。 - Alastair Maw

7

Rasmus,这是早期的问题

从答案中看来,最流行的方法是使用简单的命令行选项,或在try catch块中尝试访问Console对象(在服务中,控制台未附加到进程中,尝试访问它会引发异常)。

或者,如果您在测试/调试服务时遇到问题,请将代码移动到单独的dll程序集中,并创建一个单独的测试工具(winforms/console等)。

(刚注意到Jonathan已将他的解决方案添加到问题的末尾。)


3
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle(int nStdHandle);
const int STD_OUTPUT_HANDLE = -11;

IntPtr iStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

if (iStdOut == IntPtr.Zero)

{    
    app.RunAsWindowsService = true;

}

// Run as Service
if (runAsWindowsService)                                
{
     // .....
     ServiceBase.Run(myService);
}
else 
{
    // Run as Console
    // Register Ctrl+C Handler...
}

2
如果服务在交互模式下运行,这是否有效? - Stephen Drew

3

我没有尝试过,但可能Process.GetCurrentProcess有所帮助——在控制台模式下,进程名称将与可执行文件相同,而当作为服务运行时,我预计(请再次确认!)将会有所不同。


我认为该进程通常仍然是同一个exe本身,除非您通过svchost.exe或类似方式重新托管。 - Marc Gravell
可能很难从我的当前桌子上进行测试 :( - Jon Skeet
至少在mono下,进程名称确实会更改为执行实体的名称(即如果使用mono-service运行,则进程现在称为mono-service)。尚未尝试作为Windows服务。 - Arne Claassen

3
使用这个答案中提到的ParentProcessUtilities结构体来查找父进程,你可以这样做:
static bool RunningAsService() {
    var p = ParentProcessUtilities.GetParentProcess();
    return ( p != null && p.ProcessName == "services" );
}

请注意,父进程的进程名称不包括扩展名".exe"。

1
我不确定这是否有效,但您可以尝试使用PInvoke与this代码,并检查父进程是否为"services.exe"。

0

我最终通过检查Console.IsErrorRedirected来检测是否在控制台应用程序中。对于控制台应用程序,它返回“false”,而对于我测试的非控制台应用程序,则返回“true”。我也可以使用IsOutputRedirected

我想象中有些情况下这些可能不准确,但对我来说这个方法很有效。


如果控制台应用程序是从控制台启动并且输出被重定向或管道化(> 或 |),则IsOutputRedirected将为true。 - codeape

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接