ARTICLE
A Scheduled Application Launcher Service in VB.NET
Tags: .NET, Application Launcher, Application Launcher Service in VB.NET., Callback, Convert.ToDateTime, Event Handler, GetProcesses, GetType, Parsing XML, ProcessInfo, ProcessReader, VB.NET, Windows Services, WithClass, Xml File, XmlDocument, XmlNodeList, XPath
This an article is on launching scheduled tasks. Not quite as exciting as launching a spaceship into outer space, but…hey, even astronauts have to automate some of their day to day activities.
Download
Files:
Well we've entered a new era folks! The government isn't the only one who can make a spacecraft and launch it into space. In honor of the historical space mission by Mike Melvill, first civilian astronaut, I've decided to write an article on launching, well... scheduled tasks. Not quite as exciting as launching a spaceship into outer space, but... hey, even astronauts have to automate some of their day to day activities.

Although Microsoft already includes a task scheduler in the operating system, I thought it would be an interesting exercise to create one that runs processes read out of an xml file. This task scheduler did not have to be all things to all people. I simply needed it to start a process at a certain time of day everyday. Specifically, I need it to start and stop windows services using the two batch files listed below:
| Start.bat | Stop.bat |
| net start MyService | net stop MyService |
Table 1 - Using Batch files to stop and start Windows Services The UML design for the Schedule Launcher (reverse engineered with WithClass) is shown below in figure 2. The design consists of the Service1 class that is automatically generated by the framework. The Service class contains a Threading.Timer which is used to poll the system clock for the current time in order to check for a possible launch. Also in the design is a singleton ProcessReader class that reads the processes and times out of an xml file.

Figure 2 - UML Diagram of the Windows Service for Launching Scheduled Tasks
The timer class allows us to intercept an event every 30 seconds that the service is active. The timer is constructed with the callback delegate for the event handler along with the time we want the timer to trigger an event. Initially we set the time to infinite in order to keep the timer stopped.
Listing 1 - Constructor of Service containing timer construction
Public Sub Service1()
' This call is required by the Windows.Forms Component Designer.
InitializeComponent()
' Read process and launch times from the xml file
ReadProcesses()
' set up the timer in the stopped state
_timer = New Timer(New TimerCallback(AddressOf OnNextMinute), Nothing, Timeout.Infinite, Timeout.Infinite)
End Sub
When we are ready to start the timer, we simply change the time period from infinite to a finite period in milliseconds: Listing 2 - Starting the Timer
Private
Const TIMER_INTERVAL As Long = 30000L
Private Sub StartTimer()
' set the timer to trigger an event every 30 seconds
_timer.Change(0, TIMER_INTERVAL)
End Sub Once we've started the timer we need to check it against the system clock to see if we are ready to launch our process. The OnNextMinute event handler is triggered every 30 seconds by the timer and compares the system clock against the file time in the xml file corresponding to the process. If the time is within 1 minute, it launches the process. Listing 3 - Event Handler triggered by the timer every 30 seconds
Public
Sub OnNextMinute(ByVal state As Object)
' get the current system clock time
Dim currentTime As DateTime = DateTime.Now
' loop through each process data read from the XML file
For Each p As ProcessInfo In _processes
If (currentTime.Hour = p.StartTime.Hour) AndAlso (currentTime.Minute = p.StartTime.Minute) AndAlso p.Started = False Then
' minute reached, start the process
Dim path As String = p.Path & "\" & p.File
System.Diagnostics.Process.Start(path)
p.Started = True
End If
' reset process flag two minutes later, to be safe
If (currentTime.Hour = p.StartTime.Hour) AndAlso (currentTime.Minute > p.StartTime.Minute + 2) AndAlso p.Started = True Then
p.Started = False
End If
Next p ' end for each process info
End Sub Parsing Xml with XPath
Using an XmlDocument with XPath is a very convenient way for us to get the process information out of our file. Our Xml file consists of a set of processes containing the file to execute, the path, and the time to execute in each process node.
Listing 4 - Xml File containing Processes to Launch <?
xml version="1.0" encoding="utf-8" ?>
<Processes>
<Process>
<Path>C:\workspace\QuotingService\bin\bin</Path>
<File>start.bat</File>
<Time>9:20 AM</Time>
</Process>
<Process>
<Path>C:\workspace\QuotingService\bin\bin</Path>
<File>stop.bat</File>
<Time> 4:15 PM </Time>
</Process>
<Process>
<Path>C:\CommodityService\bin</Path>
<File>start.bat</File>
<Time>9:20 AM</Time>
</Process>
<Process>
<Path>C:\CommodityService\bin</Path>
<File>stop.bat</File>
<Time> 4:15 PM </Time>
</Process>
</Processes> Below is the routine in the ProcessReader that reads the process nodes into an array of ProcessInfo classes. The constructor loads in the Xml file by calling Load on the XmlDocument. The GetProcesses method selects all the process nodes via XPath. The XPath query //Processes/* asks for all of the nodes underneath the Processes node. The double slash tells SelectNodes to skip past all the ancestor nodes and go right to the Processes node. The star (*) tells xpath to choose all of the nodes underneath Processes.
Listing 5 - Reading the Process Nodes using XPath and XmlDocument Private
_xDoc As XmlDocument = Nothing
Public Sub ProcessReader()
Dim path As String = GetConfigPath()
_xDoc = New XmlDocument()
_xDoc.Load(path) ' Load the xml file
End Sub
Public Function GetProcesses() As ProcessInfo()
' XPath statement for selecting nodes
Dim xpath As String = "//Processes/*"
' Select the nodes with the XPath query
Dim nodes As XmlNodeList = _xDoc.SelectNodes(xpath)
' Create an array to hold process info
Dim processes As ProcessInfo() = CType(Array.CreateInstance(Type.GetType("ApplicationLauncherService.ProcessInfo"), nodes.Count), ProcessInfo())
' Go through each process node and populate process
' info object
Dim i As Integer = 0
For Each node As XmlNode In nodes
Dim p As ProcessInfo = New ProcessInfo(node("Path").InnerText, node("File").InnerText, node("Time").InnerText, "")
processes(i) = p
i += 1
Next node
Return processes
End Function Below is the ProcessInfo class used to contain the process launch information that we read with our GetProcesses method. It is a simple class containing only fields to hold the process info and times to launch along with a constructor. This class also converts our time string to a DateTime. We are only interested in the time here, so we concatenate a date onto the string in order to produce a legitimate DateTime with the Convert class.
Listing 6 - Reading Process info class Public
Class ProcessInfo
Public Path As String ' path of the process file
Public File As String ' name of the application
Public Arguements As String ' arguments of the app
Public Started As Boolean ' whether or not it was already started
Public StartTime As DateTime
Public Sub New(ByVal path_Renamed As String, ByVal file_Renamed As String, ByVal starttime_Renamed As String, ByVal arguments As String)
Path = path_Renamed
File = file_Renamed
StartTime = Convert.ToDateTime(" 11/17/1965 " & starttime_Renamed)
Arguements = arguments
Started = False
End Sub
End Class Conclusion The time for civilian space travel may be here sooner than we know it. In the meantime, while I'm waiting for my lunar flight, I'll continue to hang out in my namespace and experiment with .NET. Happy Launching!
NOTE: THIS ARTICLE IS CONVERTED FROM C# TO VB.NET USING A CONVERSION TOOL. ORIGINAL ARTICLE CAN BE FOUND ON C# CORNER (WWW.C-SHARPCORNER.COM).