Friday, 20 November 2015

Creating Windows Task Scheduler with Task Scheduler Managed Wrapper in C# & VB

While discussion with my mentor Serena Yeoh about creating Task Scheduler in code and we stumble upon Task Scheduler Managed Wrapper. This is a wrapper to the Windows Task Scheduler. For more detail, you can check it out from the following link. https://taskscheduler.codeplex.com/. There are other ways to create and manage the task scheduler, including PowerShell. But in this post, I'm going to just focus on Task Scheduler Managed Wrapper.

Creating task scheduler in code is pretty much straight forward. There are a few mandatory information you need to provide when creating a task scheduler, which is the name of the task scheduler, trigger (one time, daily, weekly, monthly) and the action (EG: execute an application). The rest of it are optional.

To use the Task Scheduler Managed Wrapper, you need to get the package from NuGet called "Task Scheduler Managed Wrapper". Once done, instantiate a new instance of TaskService.

[C#]
TaskService taskService = new TaskService();

[VB]
Dim taskService As TaskService = New TaskService()


TaskDefinition is required to be used for defining your actions and triggers, which is the mandatory information for creating task scheduler. TaskDefinition can also be used to define other optional information, such as task scheduler's description, when the task scheduler being created, who created the task scheduler and many more.

[C#]
TaskDefinition taskDefinition = taskService.NewTask();

[VB]
Dim taskDefinition As TaskDefinition = taskService.NewTask()


Once the TaskDefinition has been instantiated, we can start defining the trigger. In task scheduler, there are 4 different types of trigger (One Time, Daily, Weekly, Monthly). Each of them is using different trigger class (instantiate TimeTrigger() for [One Time], instantiate DailyTrigger() for [Daily], instantiate WeeklyTrigger() for [Weekly], instantiate MonthlyTrigger() for [Monthly]).

While all the triggers needs the start time by supplying the value to StartBoundary property, some of the trigger requires additional information. Such as the recurring days for daily schedule, recurring weeks for weekly schedule, the scheduled days of the week, and so on.

The following are the trigger example for daily. The task scheduler will be triggered every 2 days and the task scheduler will be triggered after 3 minutes from the current date time.

[C#]
DailyTrigger dailyTrigger = new DailyTrigger();
dailyTrigger.DaysInterval = 2;
dailyTrigger.StartBoundary = DateTime.Now.AddMinutes(3);

[VB]
Dim dailyTrigger As DailyTrigger = New DailyTrigger()
dailyTrigger.DaysInterval = 2
dailyTrigger.StartBoundary = DateTime.Now.AddMinutes(3)


Once you have define your trigger, add them to your TaskDefinition.

[C#]
taskDefinition.Triggers.Add(trigger);

[VB]
taskDefinition.Triggers.Add(trigger)


Next we will define the action for the task scheduler. ExecAction class will be used to define the application's executable path and its argument if any. The following code define the action to launch command prompt.

[C#]
ExecAction execAction = new ExecAction(@"C:\Windows\System32\cmd.exe");

[VB]
Dim execAction As ExecAction = New ExecAction("C:\Windows\System32\cmd.exe")


After define your action, add them to your TaskDefinition.

[C#]
taskDefinition.Actions.Add(execAction);

[VB]
taskDefinition.Actions.Add(execAction)


With both trigger and action defined, you can now create the task scheduler. The following code will create a task scheduler with a name "SampleTask" and it is located in the root path of your Task Scheduler Window.

[C#]
taskService.RootFolder.RegisterTaskDefinition("SampleTask", taskDefinition);

[VB]
taskService.RootFolder.RegisterTaskDefinition("SampleTask", taskDefinition)


You can check out the sample code here https://onedrive.live.com/redir?resid=E6612168B803803D!348&authkey=!AG20kICe6IVyxiU&ithint=file%2czip . For more examples, you can check it out from the github https://github.com/dahall/taskscheduler.




Friday, 13 November 2015

Thread Synchronization with Semaphore and how to Implement it in C# & VB

In the previous couple of posts, I have posted about aquiring exclusive lock with SpinLock http://jaryl-lan.blogspot.com/2015/08/exclusive-lock-with-spinlock-c-vb.html or with Mutex http://jaryl-lan.blogspot.com/2015/08/thread-synchronization-with-mutex.html. So for today, I'm going to post about something different yet it can still provide exclusive right to a thread in a multi-threaded environment, which is Semaphore.

Semaphore is used to control the number of concurrent threads to access a particular section of the code / resources. With the ability to control the number of concurrent threads, It can do more than just acquiring exclusive lock by limiting to 1 thread. I will demonstrate on how to use Semaphore to control the concurrent threads with an example. The example below is by no means the best practice, it is just to show how Semaphore works.

Imagine that you need to process more than 1 file at the same time with FileSystemWatcher. By default, the event only process 1 file at a time. So to process multiple file at the same time and control the threads, we will use Task.Run to simulate fire-and-forget and Semaphore to control the threads.

As usual, you need an instance of Semaphore. The following constructor accept 2 parameters. The first parameter define the number of free / available requests can be accepted. The second parameter define the maximum number of concurrent threads. In this case, we have 3 available requests and can accept only up to 3 concurrent threads.

[C#]
Semaphore semaphore = new Semaphore(3, 3);

[VB]
Dim semaphore As Semaphore = New Semaphore(3, 3)


In your FileSystemWatcher's created event, call WaitOne() to occupy 1 available request. If the request is not available, it will wait until a new request is available.

[C#]
semaphore.WaitOne();

[VB]
semaphore.WaitOne()


We will use Task.Run to execute the method in another thread. In that method, call Release() to release the request. Allowing another thread to acquire the available request.

[C#]
semaphore.Release();

[VB]
semaphore.Release()



If you play around with the sample code, you will notice that the thread id is different from when it call WaitOne() and call Release(). This is due to the thread that execute Task.Run spawn a new thread and since it did not call await or .Wait() for the Task.Run, it end up leaving the Task.Run without waiting for its completion and exit the method. To re-explain this in steps, 
  1. Thread A call WaitOne() and execute Task.Run.
  2. Task.Run spawn a new thread B to further execute the method specified in Task.Run. 
  3. Thread A leaves without waiting thread B completes its execution and exit the method. (fire-and-forget)
  4. Thread B executing the method and call Release().

List of Additional Funtionality to Include During Self Install Windows Service in C# & VB

To continue on the previous post about self install a windows service. http://jaryl-lan.blogspot.com/2014/09/create-simple-windows-service-and-self.html. Have you ever been wondering what else can you do during installation of the windows service? Well, you can include methods or functionalities that only requires to execute once that are not suitable to be called during the windows service is running. So, let's look into the list of methods or functionalities to be included.


Create Event Log & Source

If you need to write logs into the event log with custom log and source, it is advisable to create them during windows service installation. This is because writing log into event log requires the log and source to be there. The good thing is that if it does not exist, the code will attempt to create the log and source. But if the Windows Service is launched with a user with low-privilege, then the service will throw an exception due to the user did not have the permission to creation of log and source. For more detail on dealing with Event Log, you can refer to the following link. http://jaryl-lan.blogspot.com/2015/09/create-configure-and-write-to-event-log.html


Automatically Start / Stop the Windows Service During Installation & Uninstall

To simplify the installation, you may want to consider starting the service for the user after installation. Otherwise the user need to manually start the windows service by launching the Services window to find the installed windows service. Also, there's a high chance that the user is not aware of the windows service name.

[C#]
using (var serviceController = new ServiceController(_serviceName))
{
    if (serviceController.Status != ServiceControllerStatus.Stopped) return;

    serviceController.Start();
    serviceController.WaitForStatus(ServiceControllerStatus.Running);
}

[VB]
Using ServiceController As ServiceController = New ServiceController(_serviceName)
    If Not ServiceController.Status = ServiceControllerStatus.Stopped Then
        Exit Sub
    End If

    ServiceController.Start()
    ServiceController.WaitForStatus(ServiceControllerStatus.Running)
End Using

Other than that, you may want to stop the service before uninstall the windows service. When the windows service is stopping, the OnStop method will be executed, so you can write the necessary code to do some cleanup in the method.

[C#]
using (var serviceController = new ServiceController(_serviceName))
{
    if (serviceController.Status != ServiceControllerStatus.Running) return;

    serviceController.Stop();
    serviceController.WaitForStatus(ServiceControllerStatus.Stopped);
}

[VB]
Using ServiceController As ServiceController = New ServiceController(_serviceName)
    If Not ServiceController.Status = ServiceControllerStatus.Running Then
        Exit Sub
    End If

    ServiceController.Stop()
    ServiceController.WaitForStatus(ServiceControllerStatus.Stopped)
End Using


Windows Service Recovery

For those who are unaware about Windows Service Recovery, you can actually configure them in the Recovery Tab by navigating to the Windows Service's properties and look for the Recovery Tab. But it can be tedious to configure each and every Windows Service that you have installed. To simplify this, recovery settings should be set during Windows Service installation. There are couple of ways to do it, but I will only demonstrate how to set it using command line with Process class.

The arguments specified in the code below will do the following:
  • The error count will be reset after 3600 seconds.
  • The Windows Service will restart itself when it gets terminated unexpectedly for the first and second failure (After 5 minutes).
  • The subsequent failure will attempt to execute the windows service with the argument "-email" (After 30 seconds).

[C#]
using (var process = new Process())
{
    var startInfo = process.StartInfo;
    startInfo.FileName = SC_COMMAND;
    startInfo.Arguments = string.Format("failure \"{0}\" reset= 3600 actions= restart/300000/restart/300000/run/30000 command= \"\\\"{1}\\\" -email\"", _serviceName, executableLocation);

    process.Start();
    process.WaitForExit();
}

[VB]
Using process As Process = New Process()
    Dim startInfo = process.StartInfo
    startInfo.FileName = SC_COMMAND
    startInfo.Arguments = String.Format("failure ""{0}"" reset= 3600 actions= restart/300000/restart/300000/run/30000 command= ""\""{1}\"" -email""", _serviceName, executableLocation)

    process.Start()
    process.WaitForExit()
End Using


Display Error Message for Failed Installation / Uninstall

It is best to show an error message whenever an installation or uninstall for the windows service is being performed, otherwise it will be hard and tedious to trace the problem (Check installation log and Event Log) & the end user that does the installation or uninstall may not aware on how to trace the problem. With a proper and meaningful error message being displayed, the user will be aware of the error and might be able to perform the necessary action or amendment to fix the problem and then retry the installation or uninstall of the windows service.

The sample code can be obtained here. https://onedrive.live.com/redir?resid=E6612168B803803D!345&authkey=!AOIm5LtxJbwXoQo&ithint=file%2czip