Solved

Convert C# console app into a Windows Service

Posted on 2004-08-19
23
924 Views
Last Modified: 2011-05-09
Hi all,
I have a multithreaded C# Console app that needs to run every minute or every 30 seconds 24/7/365. There is nothing in the code related to a timer or Windows service (although that can be modified as I have the source) I can think of some ways of accomplishing that:
1. Leave console app code as it is and separately write a Windows Service that "calls" the app after every interval
2. Somehow modify the source code of the console app so it becomes a service with all the funcionality of the original app

Unfortunately, I have tried and don't know enough about Windows Service coding to make this work. Right now I have a  Windows Service separately that just writes text to a file on startup and and on shutdown. I don't know how I can merge the Console App into this service and also add a timer into the final product.
HELP!!!

0
Comment
Question by:kaptaindotnet
  • 8
  • 7
  • 6
  • +1
23 Comments
 
LVL 3

Expert Comment

by:bigjim2000
ID: 11844195
Keep in mind that services run even when there is no user logged into the computer.  Services cannot do things like display consoles or forms (directly).  You need to create a form, and have the form communicate with the service.
For what you want to do, perhaps look at Scheduled Tasks in the control panels.  You can schedule something to run at certain intervals, times of days, etc.

-Eric
0
 

Author Comment

by:kaptaindotnet
ID: 11844369
Thanks for prompt response,

1. Thats what I want, the service should keep running even when no one is logged on, although there might be a problem here, because the console app accesses a shared drive on the network which is mapped automatically when the user is logged on. I don't know what will happen when the user is not logged on, can the service access access the shared drive directly i.e. \\OtherComputer\Share ?

2. I do not need any user interaction with the service, so display/forms are not needed

3. If I want to run it every 30 seconds, don't you think making it  a service will make more sense than using scheduled tasks?

0
 
LVL 22

Expert Comment

by:cookre
ID: 11844409
Actually, if you wait until someone logs in, you can call the APIs for creating windows and catching WM_ messages.  What you won't get from in a service, even if a user has logged on, is shell messages, e.g., system tray event notifications.

That aside,  I'd just modify the code and make the meaty part of it the target of a timer:

* set timer interval
enable timer
...


TimerTriggered:
disable timer
do what you have to
re-enable timer
0
 
LVL 3

Expert Comment

by:bigjim2000
ID: 11844413
as for #1, yes.

#2:  Ok, then just make a new service project in Visual studio, and you can just copy most of your code... just comment out the stuff for display.

#3:  That just depends.  If you use your program as-is, it might get kinda annoying to see the console pop up every 30 seconds, but technically it will work fine.  If you make a service, then you can just hide it in the background.

-Eric
0
 
LVL 22

Expert Comment

by:cookre
ID: 11844501
Getting to a network drive from a service still requires credentials for the remote server.  A common way of doing that is haed-coding encrypted credentials in the source and decrypting them at run-time.  Even with a user logged on, you wouldn't be able to get their password unless you write a GINA replacement.  I doubt your security folks would be happy with that.
0
 

Author Comment

by:kaptaindotnet
ID: 11844596
cookre:
So you're saying, I don't even make it a service? Just add a timer to the console app such that the "meaty" part is excuted every 30 seconds but the app itself is started only once?

Eric:
The app will run on a dedicated server, so the annoying pop-up every 30 seconds is not an issue, although it would be nice if it can be avoided.

Ok, the question again, then how do I make it a service so it does what I listed in the original post. The issue is that it has to run 24/7/365 ..and excute the "meaty" part every 30 seconds.
Thanks for the continued help
0
 
LVL 3

Expert Comment

by:bigjim2000
ID: 11844705
cookre, if he doesen't use a service, then it won't run all the time.  Services run so long as it is started, and the computer is on.

As for the network security, just have the service run in the contect of a user that has permissions to access the network resource.

-Eric
0
 
LVL 3

Expert Comment

by:bigjim2000
ID: 11844745
Here is a short turorial that is pretty decent that should give you what you need to know to make a simple service:
http://www.fawcette.com/archives/premier/mgznarch/vbpj/2001/08aug01/ce0108/ce0108-1.asp

Basically, there is a start method, a stop method, and a pause method (which correspond to starting, stopping, and pausing the service...)  You make a thread in the "global" scope of your service class, then start it.  This thread will loop, and execute the function you want every 30 seconds, then sleep the rest of the time.

Hope this helps.

-Eric
0
 

Author Comment

by:kaptaindotnet
ID: 11844889
So, the questions is HOW will that be done. I am not asking anyone to write the code for me, but I need some pointers with regards to how this can be done. Maybe the structures of what I have will help:

1. The Console app  (basic structure)

main()
{
// Some declarations
// initial setup
ConnectToDatabase();
//the following is what should be done every 30 seconds, aka the meaty part [I like this term Cookre used:-)]
QueryDatabase()
if( resultsReturned > 0 )
// Create new thread for every result returned.
}

When this is run as console app, it runs as long as it has records to process and then exits. And also queries the DB only once. Of course if there are no records to process, it will exit rightaway. We don't want that, it should query DB again after 30 secs for records...and so on forever!

2. Service  (completely separate from the above)

Service1()
Main()
InitializeComponent()
Dispose()
OnStart()
{ //Write to the log file "Service1 started"}
OnStop()
{ //Write to the log file "Service Stopped"}

And then I have the installer class, which does what it's supposed to do. Now How would make the above service call the console app as an exe (case 1 in Original post) , where would I add the code to do that every 30 secs?
OR
How/where would I copy/paste code form the console app into this service code , so the app becomes a service (case 2 from original post)
Of course I will also need a timer somewhere...
0
 
LVL 22

Expert Comment

by:cookre
ID: 11845162
>>>As for the network security, just have the service run in the contect of a user that has permissions to access the network resource.
That's what I meant by embedding credentials in the code.

>>>So you're saying, I don't even make it a service?
There's no real need.  Run the program from AllUsers StartUp and just never exit the program.

If you really want a service, you'll be able to use just about all of your code - as long as it doesn't do any console writes or pop-ups:
1) create a new service project.
2) in the On-Start(), start up the timer that triggers  the code that does all the work.  In the routine that does the bulk of the work, make sure to disable the timer at the beginning and re-enable it at the end.  Not doing so could create some interesting fireworks if what your doing in one of the pass takes longer than the timer is set for, to wit, your processing routine could have another instance triggered while it's still running.

If you have to get to a network drive, you'll have to use somebody's credentials to access.  Running under system context only gets you access to the local file system and the rights to grant yourself other rights as may be needed for some local processing such as reboot,  Registry enumeration, and process enumeration.

Speaking of the Registry, be aware that if no user is logged on, Registry pseudo-key HKCU won't point to anything.  If you need something from what you would normally get from HKCU, you'd have to decide which user's settings to use, then figure out which HKU subtree corresponds to that user.

---

When writing services, I usually prefer to make them self-installing - that makes it nicely self-contained.


0
 
LVL 3

Assisted Solution

by:bigjim2000
bigjim2000 earned 65 total points
ID: 11845199
just have something like this:

private System.DateTime lastTime, curTime = System.DateTime.Now;
private bool bActive = false;
private System.Threading.Thread mMainThread;

public void OnStart()
{
  this.mMainThread = new System.Threading.Thread( new System.Threading.ThreadStart(MainThreadLoop));
  this.bActive = true;
  this.mMainThread.Start();
}

public void OnStop()
{
  this.bActive = false;
}

private void MainThreadLoop()
{
while( this.bActive )
{
  this.lastTime = this.curTime;
  while( ( this.curTime - this.lastTime ).seconds <= 30 )  //Not sure on the exact syntax, I'm not at my dev computer at the moment.... but you get the idea
  {
    this.curTime = System.DateTime.Now;
    System.Threading.Thread.Sleep(100);  //Sleep a bit to let the computer do it's thang
  }

  ConnectToDatabase();
  //the following is what should be done every 30 seconds, aka the meaty part [I like this term Cookre used:-)]
  QueryDatabase()
  if( resultsReturned > 0 )
  // Create new thread for every result returned.
}
}
0
Highfive Gives IT Their Time Back

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 22

Accepted Solution

by:
cookre earned 60 total points
ID: 11845270
Here's some self-install code (run exe with -i option):

static void Main()
{
RegistryKey reg;

int STANDARD_RIGHTS_REQUIRED      = 0x000F0000;
int SERVICE_QUERY_CONFIG          = 0x00000001;
int SERVICE_CHANGE_CONFIG         = 0x00000002;
int SERVICE_QUERY_STATUS          = 0x00000004;
int SERVICE_ENUMERATE_DEPENDENTS  = 0x00000008;
int SERVICE_START                 = 0x00000010;
int SERVICE_STOP                  = 0x00000020;
int SERVICE_PAUSE_CONTINUE        = 0x00000040;
int SERVICE_INTERROGATE           = 0x00000080;
int SERVICE_USER_DEFINED_CONTROL  = 0x00000100;
int SERVICE_WIN32_OWN_PROCESS     = 0x00000010;
int SERVICE_INTERACTIVE_PROCESS   = 0x00000100;
int SERVICE_AUTO_START            = 0x00000002;
int SERVICE_ERROR_NORMAL          = 0x00000001;
int GENERIC_WRITE                 = 0x40000000;
int DELETE                        = 0x00010000;
int SC_MANAGER_CONNECT            = 0x00000001;
int SC_MANAGER_CREATE_SERVICE     = 0x00000002;
int SC_MANAGER_ENUMERATE_SERVICE  = 0x00000004;
int SC_MANAGER_LOCK               = 0x00000008;
int SC_MANAGER_QUERY_LOCK_STATUS  = 0x00000010;
int SC_MANAGER_MODIFY_BOOT_CONFIG = 0x00000020;

int SERVICE_ALL_ACCESS =    STANDARD_RIGHTS_REQUIRED
                          | SERVICE_QUERY_CONFIG
                          | SERVICE_CHANGE_CONFIG
                          | SERVICE_QUERY_STATUS
                          | SERVICE_ENUMERATE_DEPENDENTS
                          | SERVICE_START
                          | SERVICE_STOP
                          | SERVICE_PAUSE_CONTINUE
                          | SERVICE_INTERROGATE
                          | SERVICE_USER_DEFINED_CONTROL;

int SC_MANAGER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED
                          | SC_MANAGER_CONNECT
                          | SC_MANAGER_CREATE_SERVICE
                          | SC_MANAGER_ENUMERATE_SERVICE
                          | SC_MANAGER_LOCK
                          | SC_MANAGER_QUERY_LOCK_STATUS
                          | SC_MANAGER_MODIFY_BOOT_CONFIG;

IntPtr SCM;
IntPtr CS;

// See if we're installing
string[] CmdArgs=Environment.GetCommandLineArgs();
if (CmdArgs.Length>1)
   {
   if (CmdArgs[1].ToLower()=="-i")
      {
      SCM=OpenSCManager(null,null,SC_MANAGER_CREATE_SERVICE);
      if (SCM.ToInt32()==0)
         {
         // Unable to open SCM
         ........
         return;
         }
      CS=CreateService(SCM,"some string","some other string"

 attributes as needed

                      ,SERVICE_ALL_ACCESS
                      ,SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS
                      ,SERVICE_AUTO_START
                      ,SERVICE_ERROR_NORMAL
                      ,System.Windows.Forms.Application.ExecutablePath
                      ,null,0,null,null,null);
      if (CS.ToInt32()==0)
         {
         // Unable to create service
         .....
         CloseServiceHandle(SCM);
         return;
         }

      // Service has been created, now let's start it
      if (0==StartService(CS,0,null))
         {
         // Unable to start service
         LogFile.WriteLine("Unable to start service ("+GetLastError().ToString()+")");
         LogFile.Close();
         CloseServiceHandle(CS);
         CloseServiceHandle(SCM);
         return;
         }
      CloseServiceHandle(CS);
      CloseServiceHandle(SCM);

      // Looks like it installed OK,
      return;                                                 If it was a -i install, it would exit here
      }

   }

                                                                If it was loaded by SCM, execution would continue here

System.ServiceProcess.ServiceBase[] ServicesToRun;
ServicesToRun = new System.ServiceProcess.ServiceBase[] { new Service1() /* ,new MySecondUserService()*/ };
System.ServiceProcess.ServiceBase.Run(ServicesToRun);
}
0
 

Author Comment

by:kaptaindotnet
ID: 11845444
woah! you guys are on the roll... I need some time to try these solutions out to see which one works for me.... will post back ASAP
Thanks gentlemen!
0
 

Author Comment

by:kaptaindotnet
ID: 11845997
Eric,
What would the main() have, now that I have changed it to mainthreadpool(). VS is complaining about not finding an entry point.
thanks
0
 
LVL 3

Expert Comment

by:bigjim2000
ID: 11855336
There would be no "main()" now... only the start method.  That is the main entrypoint of a service.  

You need to create a service installer project in order for your service to run.  You can't just "play" like you can with windows or console applications.  You can also build your project, and install it via the command line.  One you build it, there will be an exe file.  Just load up the command prompt and type:

installutil app.exe  [ where app is your specific application name ]

to uninstall the service after you are done debugging, type:
installutil /u app.exe

Note, that when you deploy your project to other people, you will need to create the installer package.

Also note, what cookre has been saying WILL work... I just think that it is a bit much for what you need to do.  For a simple service such as this, using the tools built into Visual Studio I think will give you a much easier time.

-Eric
0
 
LVL 22

Expert Comment

by:cookre
ID: 11856519
>>>There would be no "main()" now... only the start method.
???
Every service I've ever written has a main.  Shoot, even the VS.NET c# IDE generates a static void Main(), since a service starts out as a regulat exe.  If one wants to self install, that can be  done in Main().  In any case, the service process is started from Main() by virtue of running the ServiceBase.  In c# that triggers a rountine that usually just calls Initialize() which then triggers the OnStart.

Note that from the time ServiceBase is started, you've got 30 seconds to post a SERVICE_RUNNING message.  An interesting 'feature' of a c# service is that once control reaches OnStart, .NET sends PENDING messages with hints for you, then sends the RUNNING message as it exits OnStart.  The upshot of this is that if you have more than 30 seconds worth of initializing to do, put it in OnStart and not Initialize().

<SoapBoxMode>
I'm still of two minds about .NET's implementation of services.  On the one hand, it's very easy to create them.  On the other hand, this very simplicity has led to many programs being created as services with little understanding of the intent of services or of the environment under which they run, often engendering no small amount of confusion and hair pulling when trying to get them to run.  
I don't know if this is good or bad.
</SoapBoxMode>
0
 
LVL 3

Expert Comment

by:bigjim2000
ID: 11856740
I'm sorry I was unclear.  The Main() method still exists, but you would do all your initialization in the OnStart() method now... at least for the purposes of this type of application.

Normally initialization is done in the Main() method, but you can safely leave it alone for this application.

-Eric
0
 
LVL 3

Expert Comment

by:bigjim2000
ID: 11856771
I think at this point I will just conceed to cookre's knowledge of services.  I was trying tor the simplest approach, not the most self-contained or versatile/robust.

-Eric
0
 
LVL 22

Expert Comment

by:cookre
ID: 11856995
Simplest is usually best - and most robust, not to mention easier to maintain.

>>>but you can safely leave it alone for this application.
You just triggered something in my little pea-brai - that's probably why .NET sends PENDING messages and hints in OnStart(), in particular since Main() is static.
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 11858216
Why don't you use the windows sheduler service to run your console app every 30 seconds? No coding required, and it will work just af if you wrote your own service (since the sheduler is in fact a service).
0
 

Author Comment

by:kaptaindotnet
ID: 11870980
Thanks guys for all the comments and suggestions.
AvonWyss: we've discussed that above... and now it's too late to abandon the services idea as most of the work is done....thanks though

All said and done, I have  a service now with a timer that calls the Console app every "interval" minutes. However,  there is one problem. The console app works fine when run separately. But when the service calls it, it just stays there doing nothing. I can see it in Task Manager processes, but it does not do what it's supposed to do. I believe it's a permissions issue, but don't exaclty know how to tackle this. The domain account I am using has administrative privileges on the local machine and has access to the shared drive. Any ideas?
Once we're through this one, this thread will be closed and points allocated.
Thanks again
0
 
LVL 14

Expert Comment

by:AvonWyss
ID: 11871020
Maybe you should try to use direct UNC notation instead of a shared drive; shared drives are user-dependent.
0
 

Author Comment

by:kaptaindotnet
ID: 11915962
Cookre and Eric, with your help, I am able to run the  service now.... Thanks for all the help!
0

Featured Post

Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

Join & Write a Comment

In order to hide the "ugly" records selectors (triangles) in the rowheaders, here are some suggestions. Microsoft doesn't have a direct method/property to do it. You can only hide the rowheader column. First solution, the easy way The first sol…
Summary: Persistence is the capability of an application to store the state of objects and recover it when necessary. This article compares the two common types of serialization in aspects of data access, readability, and runtime cost. A ready-to…
This video gives you a great overview about bandwidth monitoring with SNMP and WMI with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're looking for how to monitor bandwidth using netflow or packet s…
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

21 Experts available now in Live!

Get 1:1 Help Now