dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.36k stars 4.74k forks source link

API Proposal: ServiceBase.IsRunningInWindowsService #29252

Open davidfowl opened 5 years ago

davidfowl commented 5 years ago

We recently added an API to detect if a .NET process was being lauched as a windows service (https://github.com/aspnet/Extensions/pull/1369). I believe this belongs in SeviceBase as it would be useful to have this feature outside of our hosting wrapper.

public class ServiceBase
{
    public static bool IsRunningInWindowsService { get; }
}

I'm open to other names.

cc @danmosemsft

analogrelay commented 5 years ago

Example implementation: https://github.com/aspnet/Extensions/pull/1369/files

danmoseley commented 5 years ago

@anipik owns this area. Why is the API useful?

davidfowl commented 5 years ago

We use it in our hosting layer to make the windows service specific logic noop. The benefit being that we can light up specific behavior when running in that context (like hooking into the lifetime events). This means my application runs in both modes (service or self hosted) seamlessly.

We’ve also had a customer ask us for this logic because they wanted a change to write some service specific logic in their startup.

danmoseley commented 5 years ago

@anurse is that method documented, eg there is some reason to believe the service host process name won't change in future Windows versions?

I don't feel strongly but Im inclined to think we would want more evidence of need. If it was wrapping something like ::IsAService() it would be a lower bar in my mind.

davidfowl commented 5 years ago

I just described how we use it. What evidence are you looking for? Are you not convinced that there are scenarios where knowing that you are running in a windows service is useful?

If windows changes it in the future we’ll update the code. If you know of a better way to detect that scenario there’ll be no pushback

Wraith2 commented 5 years ago

I believe this would be useful. In the past I've hooked up a log reader if running outside a service so I can direct the output to a user interface.

danmoseley commented 5 years ago

if this was on ServiceBase the app would require a reference to the compat pack which it might not otherwise need. On the flip side, it is very Windows specific...

davidfowl commented 5 years ago

@danmosemsft do yo have a proposal? Where would it live?

analogrelay commented 5 years ago

There is Environment.UserInteractive which checks if the application is running in an "Interactive Session".

From the docs:

The UserInteractive property reports false for a Windows process or a service like IIS that runs without a user interface. If this property is false, do not display modal dialogs or message boxes because there is no graphical user interface for the user to interact with.

In my initial cursory testing, a console app reports Environment.UserInteractive as true.

We might need further testing here, but it may serve our purpose. It's not guaranteed to indicate that you're in a Windows Service though since there may be other non-Windows Service non-Interactive scenarios.

Tratcher commented 5 years ago

That would also include scheduled tasks.

analogrelay commented 5 years ago

That's what I was looking for :). I figured there might be a counter-example here.

davidfowl commented 5 years ago

Doesn't that also report true when running in IIS?

danmoseley commented 5 years ago

As an aside, I had a quick look at the service related Win32 API and I didn't see an alternative way to check the current process is a service. The API's all deal with handles or service names (from which you get the handle). I don't see a way eg to enumerate services from a process handle.

Ideally we could get hold of the services parent process by a more robust method than using its name, but I don't know how.

MarcoRossignoli commented 5 years ago

Name+path(%SystemRoot%\System32\services.exe)+Environment.UserInteractive ?

MarcoRossignoli commented 5 years ago

Another idea could be check associated windows station name, from Windows Internals 6:

Unless other directed, the Windows subsystem associates services running in the local system account with a nonvisible windows station named Service-0x0-3e7$ that all noninteractive services share.
...
However only processes owned by the system and Windows services run in session 0; all other logon sessions, including those of console users, run in different session. Any windows displayed by process in session 0 is therefore not visible to the user.

Check for Service-0x + login session identified + $ I did some check and it works also under IIS

image

The only issue is in case of SERVICE_INTERACTIVE_PROCESS flag for LocalSystem account...process will be associated with WinSta0, this is however not recommended for security reason and supported for backward compatibility.

danmoseley commented 5 years ago

@Anipik @maryamariyan please limit 3.0 milestone to issues that must be fixed to ship 3.0.

terrajobst commented 5 years ago

Conclusion

There are more questions. It seems someone needs to sit down and go through the Windows SCM APIs to see how this can be made reliable. There is also the question whether we need an API to return this for other processes, and not just the current one. Windows also allows hosting multiple services in a single process.

We also need to decide what, if any, the cross-platform angle is. For example, Windows might support multiple services by a single process. Daemons might not. It seems safer to scope this API to be Windows-only. However, it means we shouldn't burn a name like Process.IsRunningAsService, for instance.

george-chakhidze commented 5 years ago

Environment.UserInteractive always returns true in .NET Core. Maybe, while at it, you could properly implement this API as well?

danmoseley commented 4 years ago

@george-chakhidze yep, this was done for 5.0. And as was pointed out above, it is not the same as this proposed API, although for some use cases it may be enough.

KalleOlaviNiemitalo commented 3 years ago

In C++, I just call StartServiceCtrlDispatcher, and if that fails with ERROR_FAILED_SERVICE_CONTROLLER_CONNECT, then I know I'm not running as a service.

The equivalent thing was not feasible to implement on .NET Framework because ServiceBase.Run would handle the error by popping up a Windows Forms message box. That was removed in the .NET Core port but the method still logs the error to the console and to event logs.