C# Scheduler for Service (Testing in Winforms first)

I wrote a service in a winforms.  Now I'm creating a service and copying the code form the winforms to the service.  I am adding a scheduler to the service (testing in winforms first).  I found a sample below and have the code that I'm using included below also.  How does the scheduler work?  Trying to figure out where to put my export process and that it will run per schedule.

https://www.aspsnippets.com/Articles/Simple-Windows-Service-that-runs-periodically-and-once-a-day-at-specific-time-using-C-and-VBNet.aspx
    public class ServiceScheduler
    {
        private Timer Schedular;
        private string logfile = AppSettings.GetExportLocationArchive() + AppSettings.GetServiceLog();

        public void ScheduleService()
        {
            try
            {
                Schedular = new Timer(new TimerCallback(SchedularCallback));
                string mode = AppSettings.GetMode();
                //this.WriteToFile("Simple Service Mode: " + mode + " {0}");

                //Set the Default Time.
                DateTime scheduledTime = DateTime.MinValue;

                if (mode == "DAILY")
                {
                    //Get the Scheduled Time from AppSettings.
                    scheduledTime = DateTime.Parse(System.Configuration.ConfigurationManager.AppSettings["ScheduledTime"]);
                    if (DateTime.Now > scheduledTime)
                    {
                        //If Scheduled Time is passed set Schedule for the next day.
                        scheduledTime = scheduledTime.AddDays(1);
                    }
                }

                if (mode.ToUpper() == "INTERVAL")
                {
                    //Get the Interval in Minutes from AppSettings.
                    int intervalMinutes = Convert.ToInt32(ConfigurationManager.AppSettings["IntervalMinutes"]);

                    //Set the Scheduled Time by adding the Interval to Current Time.
                    scheduledTime = DateTime.Now.AddMinutes(intervalMinutes);
                    if (DateTime.Now > scheduledTime)
                    {
                        //If Scheduled Time is passed set Schedule for the next Interval.
                        scheduledTime = scheduledTime.AddMinutes(intervalMinutes);
                    }
                }

                TimeSpan timeSpan = scheduledTime.Subtract(DateTime.Now);

                string schedule = string.Format("{0} day(s) {1} hour(s) {2} minute(s) {3} seconds(s)", timeSpan.Days, timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);
                ExportResult.WriteLog(schedule, logfile);

                //this.WriteToFile("Items Region Export Service scheduled to run after: " + schedule + " {0}");

                //Get the difference in Minutes between the Scheduled and Current Time.
                int dueTime = Convert.ToInt32(timeSpan.TotalMilliseconds);

                //Change the Timer's Due Time.
                Schedular.Change(dueTime, Timeout.Infinite);
            }
            catch (Exception ex)
            {
                string filename = AppSettings.GetExportLocationArchive() + AppSettings.GetExportErrorFileName();
                ExportResult.WriteError(ex.Message + ex.StackTrace, filename);

                //WriteToFile("Simple Service Error on: {0} " + ex.Message + ex.StackTrace);

                //Stop the Windows Service.
                //using (System.ServiceProcess.ServiceController serviceController = new System.ServiceProcess.ServiceController("SimpleService"))
                //{
                //    serviceController.Stop();
                //}
                Environment.Exit(1);
            }
        }

        private void SchedularCallback(object e)
        {
            ExportResult.WriteLog("Simple Service Log: {0}", logfile);
            this.ScheduleService();
        }

        private void WriteToFile(string text)
        {
            string filename = AppSettings.GetExportLocationArchive() + AppSettings.GetServiceLog();
            ExportResult.WriteLog(string.Format(text, DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss tt")), filename);
        }
    }

Open in new window

LVL 2
CipherISAsked:
Who is Participating?
 
Chinmay PatelConnect With a Mentor Enterprise ArchitectCommented:
Sorry it was pretty late yesterday - I think close to 2 AM and I could not hold out any more (surprisingly I was sleepy xD)

Anyways I modified your code a bit, please check the listing below.

using System;
using System.ServiceProcess;
using System.Threading;

namespace CrmXpress.WinService
{
    public partial class MainService : ServiceBase
    {
        public MainService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            this.ScheduleService();
        }

        protected override void OnStop()
        {
            this.Log("Simple Service stopped {0}");
            this.Schedular.Dispose();
        }

        private Timer Schedular;

        public void ScheduleService()
        {
            try
            {
                Schedular = new Timer(new TimerCallback(SchedularCallback));
                this.Log(string.Concat("Scheduler Service Mode: ", CrmXpress.WinService.Properties.Settings.Default.Mode));

                //Set the Default Time.
                DateTime scheduledTime = DateTime.MinValue;

                if (CrmXpress.WinService.Properties.Settings.Default.Mode.Equals("DAILY", StringComparison.InvariantCultureIgnoreCase))
                {
                    //Get the Scheduled Time from AppSettings.
                    scheduledTime = DateTime.Parse(CrmXpress.WinService.Properties.Settings.Default.ScheduledTime);
                    if (DateTime.Now > scheduledTime)
                    {
                        //If Scheduled Time is passed set Schedule for the next day.
                        scheduledTime = scheduledTime.AddDays(1);
                    }
                }
                else if (CrmXpress.WinService.Properties.Settings.Default.Mode.Equals("INTERVAL", StringComparison.InvariantCultureIgnoreCase))
                {
                    //Get the Interval in Minutes from AppSettings.
                    int intervalMinutes = CrmXpress.WinService.Properties.Settings.Default.IntervalMinutes;

                    //Set the Scheduled Time by adding the Interval to Current Time.
                    scheduledTime = DateTime.Now.AddMinutes(intervalMinutes);
                    if (DateTime.Now > scheduledTime)
                    {
                        //If Scheduled Time is passed set Schedule for the next Interval.
                        scheduledTime = scheduledTime.AddMinutes(intervalMinutes);
                    }
                }

                TimeSpan timeSpan = scheduledTime.Subtract(DateTime.Now);
                string schedule = string.Format("{0} day(s) {1} hour(s) {2} minute(s) {3} seconds(s)", timeSpan.Days, timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);

                this.Log("CrmXpress Scheduler Service scheduled to run after: " + schedule);

                //Get the difference in Minutes between the Scheduled and Current Time.
                int dueTime = Convert.ToInt32(timeSpan.TotalMilliseconds);

                //Change the Timer's Due Time.
                Schedular.Change(dueTime, Timeout.Infinite);
            }
            catch (Exception ex)
            {
                Log("CrmXpress Scheduler Service Error on: {0} " + ex.Message + ex.StackTrace);

                //Stop the Windows Service.
                using (System.ServiceProcess.ServiceController serviceController = new System.ServiceProcess.ServiceController(this.ServiceName))
                {
                    serviceController.Stop();
                }
            }
        }

        private void SchedularCallback(object e)
        {
            this.Log(string.Concat("Scheduler Callback Executtion Begin : ", DateTime.Now));
            
            // Do whatever you need to do.
                   
            this.ScheduleService();
        }

        private void Log(string text)
        {
            this.EventLog.WriteEntry(text);
        }
    }
}

Open in new window


Some other tips
1. Run the Visual Studio Command prompt as an administrator(Right click)
2. Run Visual Studio itself as an administrator(Please note if you log in as administrator does not mean all the apps will be executed as an administrator. You have to explicitly instruct the O/S to launch a process as an administrator.
3. It has been a 4-5 years since I wrote a Windows Service - hence I made a wrong comment earlier, you don't need to provide credential for the service. You can execute it as a Network Service (Internally that is also a heavily restricted OS-owned account). To explore this option check ProcessInstaller.
4. There will be multiple instances of the service in memory when you are developing it - the grayed out one is a Visual Studio host process, that is not the target. Your service - if it is running will be enabled and you will be easily able to detach the debugger to it.
5. I replaced AppSettings with Properties.Settings

Let me know how it goes :)
0
 
Chinmay PatelEnterprise ArchitectCommented:
Hi CipherIS,

Winform and Windows Service both are very different. I suggest you go ahead with Windows Service only to derive your solution.

To answer your query,

Copy your code to your WinForm app's main form. Add appropriate references and then in your Form_Load event just call ScheduleService method.

Regards,
Chinmay.
0
 
CipherISAuthor Commented:
Yes, I know that they are different.  In my winforms on the onload event I have

ExportData.PerformExport();

I am trying to figure out where to put that code.

I wanted to test in winforms so I can see what happens as I'm a visual person.

Suggestions?
0
Cloud Class® Course: Amazon Web Services - Basic

Are you thinking about creating an Amazon Web Services account for your business? Not sure where to start? In this course you’ll get an overview of the history of AWS and take a tour of their user interface.

 
Chinmay PatelEnterprise ArchitectCommented:
You won't see anything at all. I think you need to create a WinForm app from scratch and copy paste the code you have posted in your question. Also The code mentioned in the question does not have anything that you will be able to see on the form.
0
 
CipherISAuthor Commented:
I know.  I have a winforms app and I want to create a service.  I've copied the code from the winforms to the service app.  I want to schedule this service.

I've tested on winforms and I am getting the expected results.
0
 
Chinmay PatelEnterprise ArchitectCommented:
Got it. In that case, put your exportdata method in Scheduler Callback method. The code you posted confused me as it does not have signature of a windows service.
0
 
CipherISAuthor Commented:
Thanks.  Sorry about that.  I created a class for it.  I moved it to the service.cs.
0
 
CipherISAuthor Commented:
Code should be as below?  I put the code in private void SchedularCallback(object e)
    public partial class Service1 : ServiceBase
    {
        private Timer Schedular;
        private string logfile = AppSettings.GetExportLocationArchive() + AppSettings.GetServiceLog();

        public Service1()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            this.ScheduleService();
        }

        protected override void OnStop()
        {
            this.Schedular.Dispose();
        }

        public void ScheduleService()
        {
            try
            {
                Schedular = new Timer(new TimerCallback(SchedularCallback));
                string mode = AppSettings.GetMode();

                //Set the Default Time.
                DateTime scheduledTime = DateTime.MinValue;

                if (mode == "DAILY")
                {
                    //Get the Scheduled Time from AppSettings.
                    scheduledTime = DateTime.Parse(System.Configuration.ConfigurationManager.AppSettings["ScheduledTime"]);
                    if (DateTime.Now > scheduledTime)
                    {
                        //If Scheduled Time is passed set Schedule for the next day.
                        scheduledTime = scheduledTime.AddDays(1);
                    }
                }

                if (mode.ToUpper() == "INTERVAL")
                {
                    //Get the Interval in Minutes from AppSettings.
                    int intervalMinutes = Convert.ToInt32(ConfigurationManager.AppSettings["IntervalMinutes"]);

                    //Set the Scheduled Time by adding the Interval to Current Time.
                    scheduledTime = DateTime.Now.AddMinutes(intervalMinutes);
                    if (DateTime.Now > scheduledTime)
                    {
                        //If Scheduled Time is passed set Schedule for the next Interval.
                        scheduledTime = scheduledTime.AddMinutes(intervalMinutes);
                    }
                }

                TimeSpan timeSpan = scheduledTime.Subtract(DateTime.Now);

                string schedule = string.Format("{0} day(s) {1} hour(s) {2} minute(s) {3} seconds(s)", timeSpan.Days, timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);
                ExportResult.WriteLog(schedule, logfile);

                //this.WriteToFile("Items Region Export Service scheduled to run after: " + schedule + " {0}");

                //Get the difference in Minutes between the Scheduled and Current Time.
                int dueTime = Convert.ToInt32(timeSpan.TotalMilliseconds);

                //Change the Timer's Due Time.
                Schedular.Change(dueTime, Timeout.Infinite);
            }
            catch (Exception ex)
            {
                string filename = AppSettings.GetExportLocationArchive() + AppSettings.GetExportErrorFileName();
                ExportResult.WriteError(ex.Message + ex.StackTrace, filename);

                //WriteToFile("Simple Service Error on: {0} " + ex.Message + ex.StackTrace);

                //Stop the Windows Service.
                using (System.ServiceProcess.ServiceController serviceController = new System.ServiceProcess.ServiceController("ItemsRegionExport"))
                {
                    serviceController.Stop();
                }
            }
        }

        private void SchedularCallback(object e)
        {
            ExportResult.WriteLog("Simple Service Log: {0}", logfile);
            this.ScheduleService();
            ExportData.PerformExport();  //EXPORT PROCESS
        }

        private void WriteToFile(string text)
        {
            string filename = AppSettings.GetExportLocationArchive() + AppSettings.GetServiceLog();
            ExportResult.WriteLog(string.Format(text, DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss tt")), filename);
        }
    }

Open in new window

0
 
Chinmay PatelEnterprise ArchitectCommented:
Yupp.. I think this should do it.
0
 
CipherISAuthor Commented:
How do I run the service on my local machine?  I am running

installutil.exe C:\..\..\..\app.exe

It provides a login in form.  I enter the credentials on the local machine or leave it blank and i get "System.ComponentModel.Win32Exception: Access is denied".
0
 
Chinmay PatelEnterprise ArchitectCommented:
Service should be installed with valid credentials. Are you providing proper credentials so that a service can be run? Basically it is asking as which user this service should run?
0
 
CipherISAuthor Commented:
I'm using the login for my local computer.
0
 
Chinmay PatelEnterprise ArchitectCommented:
Are you a local administrator on your computer?
0
 
Shaun VermaakConnect With a Mentor Technical Specialist/DeveloperCommented:
TL/DR

This is how I do it and I always use TopShelf, that way I do not first need to create a WinForm app to test
http://topshelf-project.com/

        public void Start()
        {
            //Interval in seconds
            long interval = 60;

            aTimer = new System.Timers.Timer(1000);

            // Hook up the Elapsed event for the timer.
            aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);

            aTimer.Interval = interval * 1000;
            aTimer.Enabled = true;
        }
        public void Stop()
        {
            aTimer.Stop();
        }
        private static void OnTimedEvent(object source, ElapsedEventArgs e)
        {

        }

Open in new window

0
 
Snarf0001Commented:
Couple things to check:

1) You did add the installer class to your service project correct?
2) As Chinmay asked, do you have admin rights on your system?
3) Open an elevated command prompt (Run as Administrator) and call installutil from there
0
 
Snarf0001Connect With a Mentor Commented:
And in future, an easy way to get around the winform debugging issue is to simply put a bit of conditional logic in the main startup code (not the service start, the static void main entry point in Program.cs).

The windows service will never (should never) be interactive, so you can make use of the Environment property to conditionally either run it from ServiceBase or start it manually.

I have dozens of services in production, I always develop them like this for simple debugging.

        static void Main(string[] args)
        {
            if (Environment.UserInteractive)
            {
                // Run as a typical console application
                var service = new MainService();
                service.Start(args);

                Console.WriteLine("Press [Enter] to exit.");
                Console.ReadLine();

                service.Stop();
            }
            else
            {
                // Run as windows service
                ServiceBase.Run(new MainService());
            }

Open in new window

0
 
it_saigeConnect With a Mentor DeveloperCommented:
This section:
//Stop the Windows Service.
using (System.ServiceProcess.ServiceController serviceController = new System.ServiceProcess.ServiceController("ItemsRegionExport"))
{
    serviceController.Stop();
}

Open in new window

Is a potential sore spot and unneeded.  Instead, you should simply call your services stop method internally; e.g. -
catch (Exception ex)
{
    Log("CrmXpress Scheduler Service Error on: {0} " + ex.Message + ex.StackTrace);

    //Stop the Windows Service.
    Stop();
}

Open in new window


-saige-
0
 
CipherISAuthor Commented:
Oh Monday.  Ok.

1) You did add the installer class to your service project correct?
No.  How do I do this?

2) As Chinmay asked, do you have admin rights on your system?
Yes.

3) Open an elevated command prompt (Run as Administrator) and call installutil from there
Did that.
0
 
Snarf0001Commented:
You can right click and add installers, but I find that they put in a lot of extra code.
This is the minimum you need, add a class called Installer.cs and paste this into it:

using System.ComponentModel;
using System.Configuration.Install;
using System.ServiceProcess;

[RunInstaller(true)]
public class WindowsServiceInstaller : Installer
{
    public WindowsServiceInstaller()
    {
        var serviceProcessInstaller = new ServiceProcessInstaller();
        var serviceInstaller = new ServiceInstaller();

        // Service Account Information
        serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
        serviceProcessInstaller.Username = null;
        serviceProcessInstaller.Password = null;

        // Service Information
        serviceInstaller.DisplayName = "-- name for the program --;
        serviceInstaller.ServiceName = "-- name for the program --;
        serviceInstaller.StartType = ServiceStartMode.Automatic;
        serviceInstaller.Description = "-- description for the program --";

        this.Installers.Add(serviceProcessInstaller);
        this.Installers.Add(serviceInstaller);
    }
}

Open in new window

0
 
Chinmay PatelEnterprise ArchitectCommented:
0
 
CipherISAuthor Commented:
@Chinmay - Yes I did do that.  Thx
0
 
Chinmay PatelEnterprise ArchitectCommented:
Alright then we should be all green in that case. Are you facing any errors?
0
 
CipherISAuthor Commented:
I have the service installed.  I am trying to run it.  It keeps encountering an error.  I've indicated where the error is occurring in all caps in the code.

The error is:

Service cannot be started. System.ArgumentException: Empty path name is not legal. at System.IO.StreamWriter..ctor(String path, Boolean append, Encoding encoding, Int32 bufferSize, Boolean checkHost) at System.IO.StreamWriter..ctor(String path, Boolean append) at ItemsRegionExport.Classes.ExportResult.WriteError(String value, String filename) in c:\xxxx\Development\ItemsRegionExport\ItemsRegionExport\Classes\ExportResult.cs:line 28 at ItemsRegionExport.Service1.ScheduleService() in c:\Carlos\Development\ItemsRegionExport\ItemsRegionExport\Service1.cs:line 103 at ItemsRegionExport.Service1.OnStart(String[] args) in c:\Carlos\Development\ItemsRegionExport\ItemsRegionExport\Service1.cs:line 35 at System.ServiceProcess.ServiceBase.ServiceQueuedMainCallback(Object state)
public void ScheduleService()
        {
            try
            {
                Schedular = new Timer(new TimerCallback(SchedularCallback));
                string mode = AppSettings.GetMode();
                this.WriteToFile("Simple Service Mode: " + mode + " {0}");
                ExportResult.WriteLog("Simple Service Mode: " + mode + " {0}", logfile);

                //Set the Default Time.
                DateTime scheduledTime = DateTime.MinValue;

                if (mode == "DAILY")
                {
                    //Get the Scheduled Time from AppSettings.
                    scheduledTime = DateTime.Parse(System.Configuration.ConfigurationManager.AppSettings["ScheduledTime"]);
                    if (DateTime.Now > scheduledTime)
                    {
                        //If Scheduled Time is passed set Schedule for the next day.
                        scheduledTime = scheduledTime.AddDays(1);
                    }
                }

                if (mode.ToUpper() == "INTERVAL")
                {
                    //Get the Interval in Minutes from AppSettings.
                    int intervalMinutes = AppSettings.GetIntervalMinutes();

                    //Set the Scheduled Time by adding the Interval to Current Time.
                    scheduledTime = DateTime.Now.AddMinutes(intervalMinutes);
                    if (DateTime.Now > scheduledTime)
                    {
                        //If Scheduled Time is passed set Schedule for the next Interval.
                        scheduledTime = scheduledTime.AddMinutes(intervalMinutes);
                    }
                }

                TimeSpan timeSpan = scheduledTime.Subtract(DateTime.Now);

                string schedule = string.Format("{0} day(s) {1} hour(s) {2} minute(s) {3} seconds(s)", timeSpan.Days, timeSpan.Hours, timeSpan.Minutes, timeSpan.Seconds);
                ExportResult.WriteLog(schedule, logfile);

                ExportResult.WriteLog("Items Region Export Service scheduled to run after: " + schedule + " {0}", logfile);
                this.WriteToFile("Items Region Export Service scheduled to run after: " + schedule + " {0}");

                //Get the difference in Minutes between the Scheduled and Current Time.
                int dueTime = Convert.ToInt32(timeSpan.TotalMilliseconds);

                //Change the Timer's Due Time.
                Schedular.Change(dueTime, Timeout.Infinite);
            }
            catch (Exception ex)
            {
                //ERROR OCCURS HERE
                string filename = AppSettings.GetExportLocationArchive() + AppSettings.GetExportErrorFileName();
                ExportResult.WriteError(ex.ToString(), filename);

                Stop();
            }
        }

Open in new window


Any idea why

                string filename = AppSettings.GetExportLocationArchive() + AppSettings.GetExportErrorFileName();
                ExportResult.WriteError(ex.ToString(), filename);

is causing an error?  In windows form this is a non-issue.  I'm able to get the values from the app.config file.
0
 
Snarf0001Commented:
Can you post the code for AppSettings.GetExportLocationArchive and AppSettings.GetExportError?
Those are custom functions written in your code somewhere, need to see what they contain.
0
 
Chinmay PatelEnterprise ArchitectCommented:
I am not sure about using App Settings code. That's why I was asking to debug the service. I have one suggestion, can you check your project settings? I think your app.config is not valid or is not being read. Please check.
0
 
CipherISAuthor Commented:
Can't get debug to work.  There is an app.config file in the project.  How to check project settins?  What needs to be checked?
0
 
Chinmay PatelEnterprise ArchitectCommented:
In your Solution Explorer - Check app.config file. Copy its content and post it here.

What I was suggesting to do is:
1. Go to Solution Explorer (CTRL + L)
2. Right Click on your project, not the solution
3. A window will open up. Click on Settings.
4. Define whatever paths you need there
5. Access these settings using
YourProjectNameSpace.Properties.Settings.SettingName.Default;
0
 
CipherISAuthor Commented:
Can't seem to get setting name.
0
 
Chinmay PatelEnterprise ArchitectCommented:
:( That is an example. You will add a setting using settings area of your project. That name cane be

GetExportLocationArchive

Or

GetExportErrorFileName

Once you have added these settings, in your code you can refer to these settings via

YourProjectNameSpace.Properties.Settings.SettingName.GetExportLocationArchive;

Open in new window


Or

YourProjectNameSpace.Properties.Settings.SettingName.GetExportErrorFileName;

Open in new window

0
 
CipherISAuthor Commented:
Settings does not contain a definition for SettingsName
0
 
Chinmay PatelEnterprise ArchitectCommented:
Yes. My mistake.

YourProjectNameSpace.Properties.Settings.GetExportLocationArchive;

Or

YourProjectNameSpace.Properties.Settings.GetExportErrorFileName;
0
 
CipherISAuthor Commented:
Actually it seems that it is Properties.Settings.Default."SettingName".

Trying it now.

Thx
0
 
Chinmay PatelEnterprise ArchitectCommented:
:) Thanks.
0
 
CipherISAuthor Commented:
Chinmay Patel - Thanks for helping me get through this.  Testing now.  It is running.

I don't like the changes to experts-exchange.  There were others who helped but I can't add assisted.

Thank You.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.