How to add a timer to C# Service

Copper Contributor

Hi everyone
I am new to coding in c # and trying to write a windows service in c #, my service seems to be working fine except for one thing:
I try to make it run in a loop, say every 30 seconds, another program.
But for the moment when I launch my service, it executes the program only once.

My code: 

 

 

 

 

using System;
using System.Runtime.InteropServices;
using System.Diagnostics; // SET STATUS
using System.ComponentModel;// SET STATUS
using System.ServiceProcess;
using System.Timers;
//using System.ServiceProccess;// SET STATUS

public enum ServiceType : int
{                                       // SET STATUS [
    SERVICE_WIN32_OWN_PROCESS = 0x00000010,
    SERVICE_WIN32_SHARE_PROCESS = 0x00000020,
};                                                                    // SET STATUS ]

public enum ServiceState : int
{                                      // SET STATUS [
    SERVICE_STOPPED = 0x00000001,
    SERVICE_START_PENDING = 0x00000002,
    SERVICE_STOP_PENDING = 0x00000003,
    SERVICE_RUNNING = 0x00000004,
    SERVICE_CONTINUE_PENDING = 0x00000005,
    SERVICE_PAUSE_PENDING = 0x00000006,
    SERVICE_PAUSED = 0x00000007,
};                                                                    // SET STATUS ]

[StructLayout(LayoutKind.Sequential)]                                 // SET STATUS [
public struct ServiceStatus
{
    public ServiceType dwServiceType;
    public ServiceState dwCurrentState;
    public int dwControlsAccepted;
    public int dwWin32ExitCode;
    public int dwServiceSpecificExitCode;
    public int dwCheckPoint;
    public int dwWaitHint;
};                                                                    // SET STATUS ]

public enum Win32Error : int
{ // WIN32 errors that we may need to use
    NO_ERROR = 0,
    ERROR_APP_INIT_FAILURE = 575,
    ERROR_FATAL_APP_EXIT = 713,
    ERROR_SERVICE_NOT_ACTIVE = 1062,
    ERROR_EXCEPTION_IN_SERVICE = 1064,
    ERROR_SERVICE_SPECIFIC_ERROR = 1066,
    ERROR_PROCESS_ABORTED = 1067,
};

public class Service_PSTest : ServiceBase
{ // PSTest may begin with a digit; The class name must begin with a letter
    private System.Diagnostics.EventLog eventLog;                       // EVENT LOG
    private ServiceStatus serviceStatus;                                // SET STATUS

    public Service_PSTest()
    {
        ServiceName = "Test2";
        CanStop = true;
        CanPauseAndContinue = false;
        AutoLog = true;

        eventLog = new System.Diagnostics.EventLog();                     // EVENT LOG [
        if (!System.Diagnostics.EventLog.SourceExists(ServiceName))
        {
            System.Diagnostics.EventLog.CreateEventSource(ServiceName, "Application");
        }
        eventLog.Source = ServiceName;
        eventLog.Log = "Application";                                        // EVENT LOG ]
        EventLog.WriteEntry(ServiceName, "Test2.exe Test2()");      // EVENT LOG
    }

    [DllImport("advapi32.dll", SetLastError = true)]                      // SET STATUS
    private static extern bool SetServiceStatus(IntPtr handle, ref ServiceStatus serviceStatus);

    protected override void OnStart(string[] args)
    {
        EventLog.WriteEntry(ServiceName, "Test2.exe OnStart() // Entry. Starting script 'c:\\temp\\Test.exe\\Test1.ps1' -SCMStart"); // EVENT LOG
                                                                                                                                     // Set the service state to Start Pending.                        // SET STATUS [
                                                                                                                                     // Only useful if the startup time is long. Not really necessary here for a 2s startup time.
        serviceStatus.dwServiceType = ServiceType.SERVICE_WIN32_OWN_PROCESS;
        serviceStatus.dwCurrentState = ServiceState.SERVICE_START_PENDING;
        serviceStatus.dwWin32ExitCode = 0;
        serviceStatus.dwWaitHint = 2000; // It takes about 2 seconds to start PowerShell
        SetServiceStatus(ServiceHandle, ref serviceStatus);               // SET STATUS ]
                                                                          // Start a child process with another copy of this script
        try
        {
            Process p = new Process();
            // Redirect the output stream of the child process.
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.FileName = "PowerShell.exe";
            p.StartInfo.Arguments = "-ExecutionPolicy Bypass -c & 'c:\\temp\\Test.exe\\Test1.ps1' -SCMStart"; // Works if path has spaces, but not if it contains ' quotes.
            p.Start();
            // Read the output stream first and then wait. (To avoid deadlocks says Microsoft!)
            string output = p.StandardOutput.ReadToEnd();
            // Wait for the completion of the script startup code, that launches the -Service instance
            p.WaitForExit();
            if (p.ExitCode != 0) throw new Win32Exception((int)(Win32Error.ERROR_APP_INIT_FAILURE));
            // Success. Set the service state to Running.                   // SET STATUS
            serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING;    // SET STATUS
        }
        catch (Exception e)
        {
            EventLog.WriteEntry(ServiceName, "Test2.exe OnStart() // Failed to start 'c:\\temp\\Test.exe\\Test1.ps1' " + e.Message, EventLogEntryType.Error); // EVENT LOG
                                                                                                                                                              // Change the service state back to Stopped.                    // SET STATUS [
            serviceStatus.dwCurrentState = ServiceState.SERVICE_STOPPED;
            Win32Exception w32ex = e as Win32Exception; // Try getting the WIN32 error code
            if (w32ex == null)
            { // Not a Win32 exception, but maybe the inner one is...
                w32ex = e.InnerException as Win32Exception;
            }
            if (w32ex != null)
            {    // Report the actual WIN32 error
                serviceStatus.dwWin32ExitCode = w32ex.NativeErrorCode;
            }
            else
            {                // Make up a reasonable reason
                serviceStatus.dwWin32ExitCode = (int)(Win32Error.ERROR_APP_INIT_FAILURE);
            }                                                               // SET STATUS ]
        }
        finally
        {
            serviceStatus.dwWaitHint = 0;                                   // SET STATUS
            SetServiceStatus(ServiceHandle, ref serviceStatus);             // SET STATUS
            EventLog.WriteEntry(ServiceName, "Test2.exe OnStart() // Exit"); // EVENT LOG
        }
    }

    protected override void OnStop()
    {
        EventLog.WriteEntry(ServiceName, "Test2.exe OnStop() // Entry");   // EVENT LOG
                                                                           // Start a child process with another copy of ourselves
        try
        {
            Process p = new Process();
            // Redirect the output stream of the child process.
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.FileName = "PowerShell.exe";
            p.StartInfo.Arguments = "-ExecutionPolicy Bypass -c & 'c:\\temp\\Test.exe\\Test1.ps1' -SCMStop"; // Works if path has spaces, but not if it contains ' quotes.
            p.Start();
            // Read the output stream first and then wait. (To avoid deadlocks says Microsoft!)
            string output = p.StandardOutput.ReadToEnd();
            // Wait for the PowerShell script to be fully stopped.
            p.WaitForExit();
            if (p.ExitCode != 0) throw new Win32Exception((int)(Win32Error.ERROR_APP_INIT_FAILURE));
            // Success. Set the service state to Stopped.                   // SET STATUS
            serviceStatus.dwCurrentState = ServiceState.SERVICE_STOPPED;      // SET STATUS
        }
        catch (Exception e)
        {
            EventLog.WriteEntry(ServiceName, "Test2.exe OnStop() // Failed to stop 'c:\\temp\\Test.exe\\Test1.ps1'. " + e.Message, EventLogEntryType.Error); // EVENT LOG
                                                                                                                                                             // Change the service state back to Started.                    // SET STATUS [
            serviceStatus.dwCurrentState = ServiceState.SERVICE_RUNNING;
            Win32Exception w32ex = e as Win32Exception; // Try getting the WIN32 error code
            if (w32ex == null)
            { // Not a Win32 exception, but maybe the inner one is...
                w32ex = e.InnerException as Win32Exception;
            }
            if (w32ex != null)
            {    // Report the actual WIN32 error
                serviceStatus.dwWin32ExitCode = w32ex.NativeErrorCode;
            }
            else
            {                // Make up a reasonable reason
                serviceStatus.dwWin32ExitCode = (int)(Win32Error.ERROR_APP_INIT_FAILURE);
            }                                                               // SET STATUS ]
        }
        finally
        {
            serviceStatus.dwWaitHint = 0;                                   // SET STATUS
            SetServiceStatus(ServiceHandle, ref serviceStatus);             // SET STATUS
            EventLog.WriteEntry(ServiceName, "Test2.exe OnStop() // Exit"); // EVENT LOG
        }
    }

    public static void Main()
    {
        Timer timer = new Timer(); // name space(using System.Timers;) 
        void OnElapsedTime(object source, ElapsedEventArgs e)
        {
            System.ServiceProcess.ServiceBase.Run(new Service_PSTest());
        }

        timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);
        timer.Interval = 5000; //number in milisecinds  
        timer.Enabled = true;
    }
}

 

Help will be welcome

 

2 Replies

Please post only relevant code next time. The only relevant part is lines 177-188, the rest is irrelevant to your question.

Your timer gets disposed when main is exited; you need to pull it (it being the timer variable) out of main and make it a class member (so make it a private field on Service_PSTest for example) or find another way to keep the instance 'alive'.

I can't build a test right now, but my first thought is that the issue is that your Main is completing (there's nothing holding the main thread). *

Instead of an event-based timer, why not use Task-based asynchronous code.

 

Not tested, but something like:

 

async static Task Main()
{
    while (isRunning) // A token or flag to hold the application running until the service is stopped
    {
        // do work

        await Task.Delay(sleepTimeInMs);
    }
}

 

 

Also, your OnStart and OnStop methods are extremely similar; I would look at taking the shared code to a function (e.g., runPowershellScript).

 

* RobIII is definitely more correct on this point. The issue is that your Timer is no longer referenced at the end of Main, so is being collected.

I assume the service continues to say it's running, as it's life-cycle managed by SCM.

Regardless, my suggestion is one way to solve it.