Multiple use exe: determine if running as service

  • Thread starter Thread starter Ross Presser
  • Start date Start date
R

Ross Presser

OK, I've been researching this problem and can't find a definitive answer
yet.

The situation is one that seems to have come up a few times to different
folks. I am writing an application that will function as a windows service
and will also present a GUI to the user. I don't want "Interact with
desktop"; I want the same exe to run as a normal non-interactive service
when started properly by the service control manager, or instead to run as
a quiet winforms app (it will load a tray icon) if started by the user.

The idea is not new; I know of many programs outside the dotnet world that
behave this way. For example, VNC. winvnc.exe, if started with no
arguments by a user, it will attempt to start the service; if started with
-install, it will install the service; if started with -remove, it will
remove it; if started with -servicehelper, it will provide a tray icon for
setting configuration; and if started by the service controller, obviously,
it runs as a service. Prime95 is another example.

I have code already that can either run as a service or provide the tray
icon; it just can't make the decision yet. Modified from msdn:
'-------------------------------------------------------------
Public Class UserService1
Inherits System.ServiceProcess.ServiceBase

Public Sub New()
Me.ServiceName = "UserService1"
Me.CanStop = True
Me.AutoLog = True
End Sub

Shared Sub Main()
If Running_As_Service()
System.ServiceProcess.ServiceBase.Run(New UserService1)
Else
TrayIconApp.Run() ' this is in another module in my project
' trust me, it works right, no problem with that part
End If
End Sub

Protected Overrides Sub OnStart(ByVal args() As String) ...
Protected Overrides Sub OnStop(ByVal args() As String) ...

Private Function Running_As_Service() As Boolean
' This is what I have trouble with.
Return True
End Class
'------------------------------------------------------------------------

What I'm having trouble with is what to put in Running_As_Service().

There seem to be several approaches:

- use a commandline argument to control operation, espoused by
http://tinyurl.com/ddckz

Also used in the autogenerated code for a C++ windows service (i.e., To
install the service, type: "foo.exe -Install"). I'm willing to fall back
on this if I have to.

- Get the process id of the current process and use System.Management to
query the service database using the ProccId as search criteria. This idea
from http://tinyurl.com/94vp7 . This seems promising but I haven't tried it
yet.

- Some people suggest relying on something happening in On_Start(), however
that seems off the mark to me, as we have to figure out what to do in main,
BEFORE On_Start gets raised.

- Someone suggested using a servicecontroller to query the status of this
service. If it's "pending" then it's a fair bet that we're the service; if
it's "stopped" or "running" or "paused" or anything else then it's certain
we're not. I'm leery of this idea as it seems time-sensitive; if the user
clicked the app while the service was still starting up, it would get
confused.

- It seems like it should be possible to examine the parent process that
launched me, and if it is the service control manager then I'm a service,
else not. However I can't find any code to accomplish this.

I'd be grateful for any help or ideas you could offer.
 
1. System.Environment.UserInteractive gets you part way down the road - from
the online .net help:

"This will be false only when running as a Service Process or from inside a
Web application. When this property is false, you should not display any
modal dialogs or message boxes, because there is no graphical user interface
for the user to interact with."

2. Another thought is as follows. Assume there is something unique about
your service such as its name. Is there a way for your process to ask if the
service with that name is running? If so, then the process that made the
inquiry is running as that service. If not, then the process that made the
inquiry is not running as that service. (Assuming you prevent two instances
of the service running). I haven't researched this thought or tried any
experiments.
 
1. System.Environment.UserInteractive gets you part way down the road - from
the online .net help:

"This will be false only when running as a Service Process or from inside a
Web application. When this property is false, you should not display any
modal dialogs or message boxes, because there is no graphical user interface
for the user to interact with."

I'll check this out. But one of the newsgroup articles I read

http://tinyurl.com/7je6u

says that System.Environment.UserInteractive was always true when he tested
it. Possibly it would return false if run from the meat of the service,
instead of in Main(); that won't help me.
2. Another thought is as follows. Assume there is something unique about
your service such as its name. Is there a way for your process to ask if the
service with that name is running? If so, then the process that made the
inquiry is running as that service. If not, then the process that made the
inquiry is not running as that service. (Assuming you prevent two instances
of the service running). I haven't researched this thought or tried any
experiments.

This is almost the same thing as the next-to-last method I mentioned.
You're telling me to interrogate the status of the service, and if it's
running then I must be the service. In fact that's not true; the service
could already be running before I start the exe interactively.

The right way to make that work is to test if the status is "StartPending",
which should only happen when the service is actually starting up and I am
the service. But once again, it could be time sensitive; if for some reason
the service is just getting started at the same time that I launch the exe
interactively, both processes will be told they are the service.
 
How about creating a Mutex when the app is running as a Service? When your
exe is run from the command line, it tries to create the same Mutex. If it
can't, the service is installed already and running. This tells you that
this instance is not the service instance.
 
How about creating a Mutex when the app is running as a Service? When
your exe is run from the command line, it tries to create the same
Mutex. If it can't, the service is installed already and running.
This tells you that this instance is not the service instance.

Catch-22: when the service starts, how does it know it IS the service
instance? and not just the app being started when the service state is
currently stopped?

There are only a few things to distinguish between the service starting up,
and the program starting when the service is stopped. One is the parent
process: if started by the service control manger, I'm the service,
otherwise not. Related to that is that we might be able to match the
process ID with WMI; I'm still checking on that one. Another is the
username, but I'd rather not depend on the username because the enduser
might have changed it.
 
Well, the results are in. Only tested on one Windows XP Pro machine so
far, but here's what I've got:

System.Environment.UserInteractive:
when started from commandline or shell: True
when starting service: False (yay!)

Service status:
When started from commandline or shell: depends on whether service is
running already
when starting service: 2 (StartPending) (yay!)

Matching my PID with the pid reported by WMI for the service:
When started from commandline or shell: empty collection
When starting service: empty collection (boo!)

Getting the parent process's name:
When started from commandline: cmd.exe
When started from shell: explorer.exe
When started from development environment: devenv.exe
When starting service: services.exe (yaay!!)

So it looks like I have a few winning methods. I think I will combine
them and only continue as a service if ALL agree.

Look for a writeup on my blog in the near future: rpresser.blogspot.com
 
Ross Presser said:
Catch-22: when the service starts, how does it know it IS the service
instance? and not just the app being started when the service state is
currently stopped?

Like I said, the Mutex method will only tell you that the instance you are
trying to start is not the Service instance if the service instance is
installed and running.
There are only a few things to distinguish between the service starting
up,
and the program starting when the service is stopped. One is the parent
process: if started by the service control manger, I'm the service,
otherwise not. Related to that is that we might be able to match the
process ID with WMI; I'm still checking on that one. Another is the
username, but I'd rather not depend on the username because the enduser
might have changed it.

All valid points, but then again why not just simplify your life and use the
command-line parameters, as you originally said? If it's started with no
command line parameters, then it's the service instance. To start the
interface portion make the user add /i (or some such) to the command line
for interface. Usually I just keep the interface in a completely separate
exe from the service, but whatever solution you come up with I'd definitely
be interested in seeing your results - I might start doing the single exe
thing as well.
 
Ross Presser said:
Catch-22: when the service starts, how does it know it IS the service
instance? and not just the app being started when the service state is
currently stopped?

Like I said, the Mutex method will only tell you that the instance you are
trying to start is not the Service instance if the service instance is
installed and running.
There are only a few things to distinguish between the service starting
up,
and the program starting when the service is stopped. One is the parent
process: if started by the service control manger, I'm the service,
otherwise not. Related to that is that we might be able to match the
process ID with WMI; I'm still checking on that one. Another is the
username, but I'd rather not depend on the username because the enduser
might have changed it.

All valid points, but then again why not just simplify your life and use the
command-line parameters, as you originally said? If it's started with no
command line parameters, then it's the service instance. To start the
interface portion make the user add /i (or some such) to the command line
for interface. Usually I just keep the interface in a completely separate
exe from the service, but whatever solution you come up with I'd definitely
be interested in seeing your results - I might start doing the single exe
thing as well.
 
Back
Top