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?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Chinmay PatelChief Technical NinjaCommented:
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
Chinmay PatelChief Technical NinjaCommented:
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
OWASP: Forgery and Phishing

Learn the techniques to avoid forgery and phishing attacks and the types of attacks an application or network may face.

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 PatelChief Technical NinjaCommented:
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 PatelChief Technical NinjaCommented:
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 PatelChief Technical NinjaCommented:
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 PatelChief Technical NinjaCommented:
Are you a local administrator on your computer?
0
Shaun VermaakTechnical Specialist IVCommented:
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
Snarf0001Commented:
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
Chinmay PatelChief Technical NinjaCommented:
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

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
it_saigeDeveloperCommented:
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 PatelChief Technical NinjaCommented:
0
CipherISAuthor Commented:
@Chinmay - Yes I did do that.  Thx
0
Chinmay PatelChief Technical NinjaCommented:
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 PatelChief Technical NinjaCommented:
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 PatelChief Technical NinjaCommented:
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 PatelChief Technical NinjaCommented:
:( 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 PatelChief Technical NinjaCommented:
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 PatelChief Technical NinjaCommented:
:) 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
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
.NET Programming

From novice to tech pro — start learning today.