Nov 09 2021 02:09 AM - edited Nov 09 2021 02:11 AM
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
Nov 09 2021 02:30 AM - edited Nov 09 2021 02:50 AM
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'.
Nov 09 2021 02:37 AM - edited Nov 09 2021 02:50 AM
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.