It's actually pretty easy, but you're going to have to do a little bit of footwork since you're using the Express edition of Visual Studio. The commercial versions have project templates and build actions that make this process a little easier, but there's nothing stopping you from doing the work yourself.
In fact, the answer to the question "Can the Express edition do..." is always yes if it's something built into the .NET framework; in fact, the answer is "Notepad can do it". I'm not going to be that hardcore though. I just manually created a service without using the project templates, and I'll walk you through the process.
Initial Project Setup
First, create a new project. If it's available, choose the "Empty project" template. The first thing you need to do is add a reference to
System.ServiceProcess.dll and
System.Configuration.Install.dll.
Create ServiceBase subclass
The
ServiceBase class is a class that all Windows services must derive from. It provides methods such as
OnStart and
OnStop that you can override in order to handle service events. This class is where your service does things. For our example, we'll make a service that monitors the My Documents folder and writes to a log file whenever a file is created. The implementation is pretty simple:
Code:
Imports System.ServiceProcess
Imports System.IO
Public Class DemoService
Inherits ServiceBase
Friend Const DemoServiceName As String = "DemoService"
Private _watcher As FileSystemWatcher
Private _logFilePath As String
Public Sub New()
Me.ServiceName = DemoServiceName
Me.CanHandlePowerEvent = False
Me.CanHandleSessionChangeEvent = False
Me.CanPauseAndContinue = False
Me.CanShutdown = True
Me.CanStop = True
_watcher = New FileSystemWatcher()
_watcher.Path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
AddHandler _watcher.Created, AddressOf WatcherOnCreated
' Log file goes in application data directory
_logFilePath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
_logFilePath = Path.Combine(_logFilePath, "DemoService")
_logFilePath = Path.Combine(_logFilePath, "log.txt")
End Sub
Protected Overrides Sub OnShutdown()
MyBase.OnShutdown()
RemoveHandler _watcher.Created, AddressOf WatcherOnCreated
_watcher.Dispose()
End Sub
' When the service is started, we tell the watcher to listen for events
Protected Overrides Sub OnStart(ByVal args() As String)
MyBase.OnStart(args)
_watcher.EnableRaisingEvents = True
End Sub
' When the service is stopped, we tell the watcher to stop listening for events
Protected Overrides Sub OnStop()
MyBase.OnStop()
_watcher.EnableRaisingEvents = False
End Sub
Private Sub WatcherOnCreated(ByVal sender As Object, ByVal e As System.IO.FileSystemEventArgs)
If Not Directory.Exists(Path.GetDirectoryName(_logFilePath)) Then
Directory.CreateDirectory(Path.GetDirectoryName(_logFilePath))
End If
Using writer = New StreamWriter(_logFilePath, True)
Dim messageTemplate As String = "{0}: File {1} created."
writer.WriteLine(messageTemplate, DateTime.Now, e.Name)
End Using
End Sub
End Class
Notice I indicated that all the user can do with this service is start it, stop it, or shut it down. It's pretty simple: it creates the watcher in the constructor, starts the watcher's events when the service is started, and stops the watcher's events when the service is stopped. Now, we have to make something that can run it.
Runner Stub Setup
In Windows Forms applications, you use the
Application class to get things started. In a Windows Service, you need to use
ServiceBase. Add a new class to the project named
Program, and paste this code in:
Code:
Imports System.ServiceProcess
Public Class Program
Public Shared Sub Main()
Dim servicesToRun As ServiceBase()
servicesToRun = New ServiceBase() {New DemoService}
ServiceBase.Run(servicesToRun)
End Sub
End Class
Basically, this creates a list of the services we want to run (which is only one in this case) and runs them. Easy, but required. Now we have to make sure the project is configured properly. Open the project properties and make sure it is set to be a Windows Forms application with Sub Main as the startup object, and double-check that "Enable application framework" is NOT checked. Now, we have to create the code that installs the process.
Creating Process Installers
To install a process, we need a
ServiceProcessInstaller to describe the parameters for the process that will run the service, and a
ServiceInstaller to describe the parameters for installing the service. These must be contained in an instance of the
Installer class that is marked with
RunInstallerAttribute. It's a lot easier than it sounds; add a class named [i]DemoServiceInstaller to your class:
Code:
Imports System.Configuration.Install
Imports System.ServiceProcess
<System.ComponentModel.RunInstaller(True)> _
Public Class DemoServiceInstaller
Inherits Installer
Private _processInstaller As ServiceProcessInstaller
Private _serviceInstaller As ServiceInstaller
Public Sub New()
MyBase.New()
_processInstaller = New ServiceProcessInstaller()
' LocalService can't access the filesystem, and we want to log to a file; provide a user
' and password during the install step.
_processInstaller.Account = ServiceAccount.User
_serviceInstaller = New ServiceInstaller()
_serviceInstaller.Description = "Logs any files created in the My Documents folder."
_serviceInstaller.DisplayName = "My Documents Watcher"
Me.Installers.Add(_processInstaller)
Me.Installers.Add(_serviceInstaller)
' Note this has to match the name defined by the service class exactly; I'm using an
' internal constant to guarantee this.
_serviceInstaller.ServiceName = DemoService.DemoServiceName
_serviceInstaller.StartType = ServiceStartMode.Manual
End Sub
End Class
It's pretty straightforward; each property is easy to figure out. The only thing to be aware of here is that I set the account type to "User", which means you'll need to give it a username and password later. I wanted to give it an account type of "LocalService", but that account is not allowed to write to the filesystem and I'm using a log file. Ideally, in this case you would create a very limited-privilege user for the service that can only access what it needs; I just used my own account when prompted.
Installing the process
Now that we've got the service, the runner, and the installer, we are ready to install the process. You
can manually edit the registry to do this, but it's a lot nicer to let tools do the work for you. The
InstallUtil tool is designed to find the service installers in an assembly and install the services for you. Open up the Visual Studio 2008 command prompt and navigate to the build output directory. Then, type
InstallUtil DemoService.exe and if everything goes well, the service should be installed (after prompting for a username and password, of course). Open the services applet from Control Panel and look for "My Documents Watcher" in the list; right-click it and start it. Now, when files are created in the My Documents folder, the log file should be updated.
You should do a little reading in the documentation about how services work, and probably read a few articles. All I really covered is how to get a service installed without using the VS tools, and I do not claim I'm following best practices.