Creating a Windows Service

Credit: Bill Froelich

Problem

You want to write a self-contained Ruby program for Windows that performs a task in the background.

Solution

Create a Windows service using the win32-service library, available as the win32-service gem.

Put all the service code below into a Ruby file called rubysvc.rb. It defines a service that watches for the creation of a file c:findme.txt; if it ever finds that file, it immediately renames it.

The first step is to register the service with Windows. Running ruby rubysrvc.rb register will create the service.

# rubysrvc.rb require ubygems require win32/service include Win32 SERVICE_NAME = "RubySvc" SERVICE_DISPLAYNAME = "A Ruby Service" if ARGV[0] == "register" # Start the service. svc = Service.new svc.create_service do |s| s.service_name = SERVICE_NAME s.display_name = SERVICE_DISPLAYNAME s.binary_path_name = C:InstantRails-1.3 ubyin uby + File.expand_path($0) s.dependencies = [] end svc.close puts "Registered Service - " + SERVICE_DISPLAYNAME

When you e all done, you can run rubysrvc.rb stop to stop the service and remove it from Windows:

elsif ARGV[0] == "delete" # Stop the service. if Service.status(SERVICE_NAME).current_state == "running" Service.stop(SERVICE_NAME) end Service.delete(SERVICE_NAME) puts "Removed Service - " + SERVICE_DISPLAYNAME else

If you run rubysrvc.rb with no arguments, nothing will happen, but it will remind you what parameters you can use:

if ENV["HOMEDRIVE"]!=nil # We are not running as a service, but the user didn provide any # command line arguments. Weve got nothing to do. puts "Usage: ruby rubysvc.rb [option]" puts " Where option is one of the following:" puts " register - To register the Service so it " + "appears in the control panel" puts " delete - To delete the Service from the control panel" exit end

But when Windows runs rubysrvc.rb as a service, the real action starts:

# If we got this far, we are running as a service. class Daemon def service_init # Give the service time to get everything initialized and running, # before we enter the service_main function. sleep 10 end def service_main fileCount = 0 # Initialize the file counter for the rename watchForFile = "c:\findme.txt" while state == RUNNING sleep 5 if File.exists? watchForFile fileCount += 1 File.rename watchForFile, watchForFile + "." + fileCount.to_s end end end end d = Daemon.new d.mainloop end

Once you run ruby rubysrvc.rb register, the service will show up in the Services Control Panel as "A Ruby Service". To see it, go to Start images/U2192.jpg border=0> ControlPanel images/U2192.jpg border=0> Administrative Tools images/U2192.jpg border=0> Services (Figure 20-1). Start the service by clicking the service name in the list and clicking the start button.

Figure 20-1. The Services Control Panel

To test the service, create a file in c: called findme.txt.

$ echo "test" > findme.txt

Within seconds, the file you just created will be renamed to findme.txt:

$ dir findme* # Volume in drive C has no label. # Volume Serial Number is 7C61-E72E # Directory of c: # 04/14/2006 02:29 PM 9 findme.txt.1

To remove the service, run ruby rubysrvc.rb delete.

Discussion

Theres no reason why the code that registers rubysrvc.rb as a Windows service has to be in rubysrvc.rb itself, but it makes things much simpler. When you run ruby rubysrvc.rb register, the script tells Windows to run rubysrvc.rb again, only as a service. The key is the binary_path_name defined on the Service object: this is the command for Windows to run as a service. In this case, its an invocation of the ruby interpreter with the service script passed as an input. But you could have run the same code from an irb session: then, rubysrvc.rb would only have been invoked once, by Windows, when running it as a service.

The code above assumes that your Ruby interpreter is located in c:InstantRails-1.3 ubyin uby. Of course, you can change this to point to your Ruby interpreter if its somewhere else: perhaps c: ubyin uby. If youve got the Ruby interpreter in your path, you just do this:

s.binary_path_name = uby + File.expand_path($0)

When you create a service, you specify both a service name and a display name. The service name is shorter, and is used when referring to the service from within Ruby code. The display name is the one shown in the Services Control Panel.

Our example service checks every five seconds for a file with a certain name. Whenever it finds that file, it renames it by appending a number to the filename. To keep things simple, it does no error checking to see if the new filename already exists; nor does it do any file locking to ensure that the file is completely written before renaming it. Real services should include at least some basic high-level error handling:

def service_main begin while state == RUNNING # Do my work end # Finish my work rescue StandardError, Interrupt => e # Handle the error end end

In addition to the service_main method, your service can define additional methods to handle the other service events (stop, pause, and restart). The win32-service gem comes with a useful example script, daemon_test.rb, which provides sample implementations of these methods.

See Also

Категории