Introduction to Windows Services

Introduction to Windows Services

Microsoft Windows services, formerly known as NT services, enable you to create long-running executable applications that run in their own Windows sessions. Service applications can be set up to start when the computer boots, and they can be paused and restarted.

These services do not have any user interface, which makes converting our class to a service for use on a server (or wherever you need long-running functionality that does not interfere with other users working on the same computer) quite easy. You can also run services in the security context of a specific user account that is different from the logged-on user or the default computer account.

You create a service by creating an application that is installed as a service. If we want to use the sentinel class as a service, we can convert it to run on a server in the background. And because we used object-oriented programming, we can simply use the class as-is, giving real meaning to reusability.

The Life and Death of a Service

A service goes through several internal states in its lifetime. First the service is installed onto the system on which it will run, such as your Web server. This process executes the installers for the service project and loads the service into the Windows Service Control Manager (SCM) for that computer. The SCM is the central utility provided by Windows to administer services. It can be configured to run automatically when booted or can be started and stopped at will. A running service can exist in this state indefinitely until it is either stopped or paused or until the computer shuts down. A service can exist in one of three basic states: running, paused, or stopped.

Unlike some types of projects, for a service you must create installation components for the service application. The installation components install and register the service on the server and create an entry for your service with the SCM. Luckily, .NET makes this task very easy, and I'll cover the steps in detail.

Windows service applications run in a different Windows station—a secure object that contains a clipboard, a set of global atoms, and a group of desktop objects—than other applications. Because of this, dialog boxes raised from within a Windows service application will not be seen and will probably even cause your program to stop responding. Therefore, you want to be sure to write all messages, including error messages, to a file rather than raise any messages in the user interface by means of something such as a message box.

Building Our File Sentinel into a Windows Service

Start a new Visual Basic project, and this time select the Windows Service template, shown in Figure 9-8. Name the project vbFileMonitorService. Of course, the IDE will create a new directory with that name under the directory listed in the Location text box.

Figure 9-8

Use the Windows Service project icon to create a Windows service application.

When the project is created, you'll see a blank designer screen. Click the hyperlink that reads "click here to switch to code view," shown in Figure 9-9. The IDE adds the service template for us, but we'll need to add a few lines of code in order to include our sentinel class and also to write to an event log file.

Figure 9-9

Use the hyperlink to switch to code view.

Adding Our Sentinel Class to Our Service

We're going to add the sentinel class we built earlier in this chapter to this project to illustrate object-oriented class reusability. We will make this addition in the easiest way—using Windows Explorer to copy the file Sentinel.vb in the directory of your last project to the vbFileMonitorService directory. Sentinel.vb is the text file that contains the code with the namespace SystemObserver and the public class sentinel.

After the file is in the vbFileMonitorService directory, we want to add the file to our current project.

  1. From the Visual Studio .Net IDE, Select Project | Add Existing Item.

  2. When the Add Existing Item dialog box comes up, select Sentinel.vb to include our class file in the vbFileMonitorService project. Now the file is physically in our current project directory and is added to our project solution file.

  3. Click the Sentinel.vb tab to display the code for our sentinel class. Comment out the EnableRaisingEvent property line. We are going to add two properties to turn this event on and off. Also change the name of the log file to vbFMS.txt. With separate names, we can keep both log files on disk and use them for different purposes.

     AddHandler m_Watcher.Changed, AddressOf OnChanged AddHandler m_Watcher.Created, AddressOf OnChanged AddHandler m_Watcher.Deleted, AddressOf OnChanged AddHandler m_Watcher.Renamed, AddressOf OnRenamed AddHandler m_Watcher.Error, AddressOf onError ' m_Watcher.EnableRaisingEvents = True m_ObserveFileWrite = _ New StreamWriter("C:\vbFMS.txt", True)

Remember that the OnChanged event handler checks to see whether the log file was being modified. If it was, we ignored the event. Because we changed the name of our log file to vbFMS.txt, add this name in lowercase characters to the IndexOf property. This setting will prevent our log from recording each and every write to the file.

If (Len(sChange) > 0) Then If (e.FullPath.IndexOf("vbfms.txt") > 0) Then Exit Sub End If End If

Finally, add the following two public properties to the class. With these properties, when our service starts and stops, we can instruct the sentinel class to start and stop logging changes to files.

Public Sub StartLogging() m_Watcher.EnableRaisingEvents = True End Sub Public Sub StopLogging() m_Watcher.EnableRaisingEvents = False End Sub

With these minor changes, we're able to import our sentinel class and quickly change it from a Windows application to a Windows service.

Updating the Service1.vb File

As with other project templates, the Visual Studio .NET project template Windows Service does much of the work of building a service for you. It references the appropriate classes and namespaces, sets up the inheritance from the base class for services, and overrides several of the methods you're likely to want to override.

With this work done for you, at a minimum you must do the following to create a functional service:

  • Set the ServiceName property.

  • Create the necessary installers for your service application.

  • Override and specify code for the OnStart and OnStop methods to customize the ways in which your service behaves.

Click the Service1.vb tab to switch to the service template code generated by the IDE. In the interest of space, I'll simply highlight the lines of code that you have to add. Because we are referencing our File Sentinel program, we had to first add it to our project.

Imports System.ServiceProcess Imports vbFileMonitorService.SystemObserver.sentinel Public Class Service1 Inherits System.ServiceProcess.ServiceBase  Dim vbFMS As vbFileMonitorService.SystemObserver.sentinel #Region " Component Designer generated code " Public Sub New() MyBase.New() ' This call is required by the Component Designer. InitializeComponent() ' Add any initialization after the ' InitializeComponent() call  EventLog.EnableRaisingEvents = True Me.AutoLog = True Me.CanStop = True vbFMS = New _ vbFileMonitorService.SystemObserver.sentinel("C:\")  End Sub ' The main entry point for the process Shared Sub Main() Dim ServicesToRun() As _ System.ServiceProcess.ServiceBase ' More than one NT Service may run within the same ' process. To add another service to this process, ' change the following line to create a second ' service object. For example, ' ' ServicesToRun = New _ ' System.ServiceProcess.ServiceBase () ' {New Service1, New MySecondUserService} ' ServicesToRun = New _ System.ServiceProcess.ServiceBase () _ {New Service1} System.ServiceProcess.ServiceBase.Run(ServicesToRun) End Sub ' Required by the Component Designer Private components As System.ComponentModel.Container ' NOTE: The following procedure is required by the ' Component Designer. ' It can be modified using the Component Designer. ' Do not modify it using the code editor. <System.Diagnostics.DebuggerStepThrough()> _ Private Sub InitializeComponent() components = New System.ComponentModel.Container()  Me.ServiceName = "vbFileMonitorService" End Sub #End Region Protected Overrides Sub OnStart(ByVal args() As String) ' Add code here to start your service. This method ' should set things in motion so your service can ' do its work.  vbFMS.StartLogging() EventLog.WriteEntry("Started logging files.") End Sub Protected Overrides Sub OnStop() ' Add code here to perform any tear-down ' necessary to stop your service.  vbFMS.StopLogging() EventLog.WriteEntry("Stopped logging files.") End Sub End Class

How Our Service Works

The Main method for our service application must use the Run command for the services your project contains. The Run method loads the services into the SCM on the appropriate server. Because we used the Windows Services project template, the Run method is written for us. Keep in mind that loading a service is not the same operation as starting a service.

We first have to import our class that contains the File Sentinel. Because we are including the class in our vbFileMonitorService project, we add the following Imports statement:

Imports vbFileMonitorService.SystemObserver.sentinel

We now add a class-level variable that contains a reference to an instance of the sentinel class.

Dim vbFMS As vbFileMonitorService.SystemObserver.sentinel

Logging Events to the Event Viewer

Event logging in Microsoft Windows provides a standard, centralized way to have applications record important software and hardware events. For example, when an error occurs, the system administrator must determine what caused the error. It is helpful if applications, the operating system, and other system services record important events such as low-memory conditions or failed attempts to access a disk. The system administrator can then use the event log to help determine what conditions caused the error and the context in which it occurred.

Windows supplies a standard user interface for viewing these event logs, the Event Viewer, shown in Figure 9-10, and a programming interface for examining log entries. In Visual Basic 6, you could perform limited write operations to some event logs, but you could not easily read or interact with all the logs available to you.

Figure 9-10

The Windows Event Viewer.

In .NET, however, we simply use the EventLog component, which allows us to connect to event logs on both local and remote computers and then write entries. You can also read entries from existing logs and create your own custom event logs. In our class, we will simply write to the standard application log. As our service executes, we can have it write events to the application log for any event we deem important. By default, the event type is set to Information if you do not specify otherwise. However, you can set the type of event by using a parameter on an overloaded form of the WriteEntry method of the EventLog object.

By double-clicking one of the events in the Event Viewer, you can see the event's Event Properties dialog box. In Figure 9-11, you can see one of our custom messages logged to the Application log.

Figure 9-11

The Event Properties dialog box.

The EnableRaisingEvents property determines whether the EventLog object raises events when entries are written to the log. When the property is True, components receiving the EventWritten event will receive notification any time an entry is written to the log. If EnableRaisingEvents is False, no events are raised. In our example, we set this property to True but don't check for the event. I did it this way in the example simply to illustrate the concept and syntax.

EventLog.EnableRaisingEvents = True

You must be prudent when writing to the log file because it can easily become so filled with messages that the messages become meaningless to whoever is viewing them. In full production services, we want to write entries for resource problems. For example, if your application is in a low-memory situation (caused by a code bug or inadequate memory) that degrades performance, logging a warning event when memory allocation fails might provide a clue about what went wrong. We can also log information events. A server-based application (such as a database server) might want to record a user logging on, opening a database, or starting a file transfer. The server can also log error events it encounters (failed file access, host process disconnected, and so on), corruptions in the database, or whether a file transfer was successful.

By default, all Windows Service projects can interact with the Application event log by writing information and exceptions to it. Use the AutoLog property to indicate whether you want this built-in functionality in your application. By default, logging is turned on for any service you create with the Windows Service project template. In our project, we use a static form of the EventLog class to write service information to a log. We don't have to create an instance of an EventLog component or manually register a source.

If you want to write to an event log other than the Application log, you must set the AutoLog property to False, create your own custom event log within your services code, and register your service as a valid source of entries for that log.

Me.AutoLog = True

At times, we might want to stop logging file changes. In case of maintenance or normal file access, we can simply turn off our service when needed. When Stop is called on a service, the SCM verifies whether the service accepts Stop commands using the value of CanStop. For most services, the value of CanStop is True, but some operating system services do not allow the user to stop them.

If CanStop is True, the Stop command is passed to the service and the OnStop method is called, as it is in our service. However, if we don't define an OnStop method, the SCM handles the Stop command through the empty base class method ServiceBase.OnStop.

Me.CanStop = True

The next line should be familiar. With it we instantiate a new instance of our sentinel class. The root directory of drive C is passed into the constructor as a parameter. Because Windows services do not have a user interface, we have to pass in a value manually and not from a UI. You might want to code this parameter as the root directory that holds your Web pages or some other location of importance. But for now, we will monitor all files in the root directory.

vbFMS = New _ vbFileMonitorService.SystemObserver.sentinel("C:\")

In the next line of code from the InitializeComponent procedure, we change the service name to something that is meaningful to our program.

Me.ServiceName = "vbFileMonitorService"

We then add two one-line methods to our file sentinel. The methods, StartLogging and StopLogging, will turn on and off the EnableRaisingEvents property of the FileSystemMonitor object. When the service is started, we set the property to True, and when it's stopped, we toggle the value to False.

After each call to our class for turning on and off the events, we write to the event log to track when our service starts and ends. We use the WriteEntry method of the static EventLog. It will automatically time stamp the entry for us.

Protected Overrides Sub OnStart(ByVal args() As String) ' Add code here to start your service. This method ' should set things in motion so your service can ' do its work. vbFMS.StartLogging() EventLog.WriteEntry("Started logging files.") End Sub Protected Overrides Sub OnStop() ' Add code here to perform any tear-down ' necessary to stop your service vbFMS.StopLogging() EventLog.WriteEntry("Stopped logging files.") End Sub

The Main method for the vbFileSystemMonitor service application issues the Run command for the services your project contains. Because we used the Windows Services project template, this method is provided for us. Once the service is loaded, we will manually start and stop the service from the SCM.

Shared Sub Main() Dim ServicesToRun() As System.ServiceProcess.ServiceBase ServicesToRun = New _ System.ServiceProcess.ServiceBase () _ {New Service1} System.ServiceProcess.ServiceBase.Run(ServicesToRun) End Sub

Adding an Installer to Our Windows Service

We have to add an installer to our Windows service, which we don't have to do with a standard Windows program. The installer is responsible for doing the heavy lifting required to register our new service with the SCM. The ServiceInstaller class does work specific to the service with which it is associated. It is used by the installation utility we will add to the service to write registry values associated with the service to a subkey within the HKEY_LOCAL_MACHINE\ System\CurrentControlSet\Services registry key.

The service is identified by its ServiceName within this subkey. The subkey also includes the name of the executable or DLL to which the service belongs. If you are interested, use Regedit.exe to examine the registry entry of the service after we install it. You can see in Figure 9-12 that the registry contains the fully qualified name of our service in the ImagePath key.

Figure 9-12

Viewing our registered service in the Registry Editor.

Adding the ServiceInstaller

Adding a ServiceInstaller to our Windows service is pretty straightforward. Go to the Service1.vb [Design] tab, and then right-click. On the pop-up menu, select the Add Installer option, shown in Figure 9-13.

Figure 9-13

Adding a ServiceInstaller.

As you can see in Figure 9-14, the IDE adds both a ServiceProcessInstaller and a ServiceInstaller. Most of the code for these objects will be added for us. When we run the installer program, InstallUtil.exe, it will read this code and install the vbService as a Windows service for us.

Figure 9-14

The IDE adds a ServiceProcessInstaller and a ServiceInstaller.

Double-click both ServiceProcessInstaller1 and the ServiceInstaller1 on the ProjectInstaller.vb [Design] tab in the IDE. The template code does the bulk of the work, but we need to add a few lines of code to make the installer functional for our program. Again, in the interest of space, only the lines you need to add are highlighted.

Imports System.ComponentModel Imports System.Configuration.Install <RunInstaller(True)> Public Class ProjectInstaller Inherits System.Configuration.Install.Installer #Region " Component Designer generated code " Public Sub New() MyBase.New() ' This call is required by the Component Designer. InitializeComponent() ' Add any initialization after the ' InitializeComponent() call End Sub Friend WithEvents ServiceProcessInstaller1 As _ System.ServiceProcess.ServiceProcessInstaller Friend WithEvents ServiceInstaller1 As _ System.ServiceProcess.ServiceInstaller ' Required by the Component Designer Private components As System.ComponentModel.Container ' NOTE: The following procedure is required by the ' Component Designer ' It can be modified using the Component Designer. ' Do not modify it using the code editor. <System.Diagnostics.DebuggerStepThrough()> _ Private Sub InitializeComponent() Me.ServiceProcessInstaller1 = New _ System.ServiceProcess.ServiceProcessInstaller() Me.ServiceInstaller1 = New _ System.ServiceProcess.ServiceInstaller() ' ' ServiceProcessInstaller1 '  Me.ServiceProcessInstaller1.Account = _ System.ServiceProcess.ServiceAccount.LocalSystem Me.ServiceProcessInstaller1.Password = Nothing Me.ServiceProcessInstaller1.Username = Nothing ' ' ServiceInstaller1 ' Me.ServiceInstaller1.ServiceName = _  "vbFileMonitorService" ' ' ProjectInstaller ' Me.Installers.AddRange(New _ System.Configuration.Install.Installer() _ {Me.ServiceProcessInstaller1, Me.ServiceInstaller1}) End Sub #End Region Private Sub ServiceInstaller1_AfterInstall( _ ByVal sender As System.Object, _ ByVal e As _ System.Configuration.Install.InstallEventArgs) _ Handles ServiceInstaller1.AfterInstall End Sub Private Sub ServiceProcessInstaller1_AfterInstall( _ ByVal sender As System.Object, _ ByVal e As _ System.Configuration.Install.InstallEventArgs) _ Handles ServiceProcessInstaller1.AfterInstall End Sub End Class

How the Installation Code Works

In this code, we are telling the ServiceProcessInstaller which local account process space under which to run. By default, the service will start manually. Another option is to have the service start automatically when the machine boots. When you get your service up and running, you might want to change this setting. For now, a manual start is what we want.

Me.ServiceProcessInstaller1.Account = _ System.ServiceProcess.ServiceAccount.LocalSystem

As before, we give our service the name that we want to show up in the Services window.

Me.ServiceInstaller1.ServiceName = _ "vbFileMonitorService"

When logging is turned on, the installer for our service registers the service as a valid source of events with the Application log on the computer where the service is installed. The service logs information each time the service is started, stopped, paused, resumed, installed, or uninstalled. It also logs any failures that occur. You do not need to add any code to write entries to the log when using the default behavior. The service handles these details for you. Pretty cool, eh?

Installing Our Service

Now we're ready to install our service. Build the project by selecting Build | Build from the IDE. This creates the file vbFileMonitorService in the \bin directory.

After you create and build the application, you install the service by running the command-line utility InstallUtil.exe and passing the path to the service's executable file. The easiest way to do this is to use Windows Explorer to find InstallUtil.exe on your drive. Copy the file to the directory where your service executable is located. On my drive, the file is located under C:\Chapter 9\vbFileMonitorService\bin. If you install the companion disc files to the default location, your path will be C:\Coding Techniques for Visual Basic .NET\Chap09\vbFileMonitorService\bin.

Bring up an MS-DOS command prompt window, run installutil, and pass it the full name of the service. You will see several messages printed to the command window. Here's what the installation process will look like:

C:\Chapter 9\vbFileMonitorService\bin>installutil vbfilemonitorservice.exe Microsoft (R) .NET Framework Installation utility Copyright (C) Microsoft Corp 2001. All rights reserved. Running a transacted installation. Beginning the Install phase of the installation. See the contents of the log file for the C:\Chapter 8\vbFileMonitorService\bin\vbfilemonitorservice.exe assembly's progress. The file is located at C:\Chapter 9\vbFileMonitorService\bin\vbfilemonitorservice.InstallLog. Call Installing. on the C:\Chapter 9\vbFileMonitorService\bin\vbfilemonitorservice.exe assembly. Affected parameters are: assemblypath = C:\Chapter 9\vbFileMonitorService\bin\vbfilemonitorservice.exe logfile = C:\Chapter 9\vbFileMonitorService\bin\vbfilemonitorservice.InstallLog Installing service vbFileMonitorService... Service vbFileMonitorService has been successfully installed. Creating EventLog source vbFileMonitorService in log Application... The Install phase completed successfully, and the Commit phase is beginning. See the contents of the log file for the C:\Chapter 9\vbFileMonitorService\bin\vbfilemonitorservice.exe assembly's progress. The file is located at C:\Chapter 9\vbFileMonitorService\bin\vbfilemonitorservice.InstallLog. Call Committing. on the C:\Chapter 9\vbFileMonitorService\bin\vbfilemonitorservice.exe assembly. Affected parameters are: assemblypath = C:\Chapter 9\vbFileMonitorService\bin\vbfilemonitorservice.exe logfile = C:\Chapter 9\vbFileMonitorService\bin\vbfilemonitorservice.InstallLog The Commit phase completed successfully. The transacted install has completed.

note

To uninstall the service, you must first close the Service Management Console. If the console is open, it must be closed and reopened to allow the uninstall to complete. Use the syntax installutil /u vbFileMonitorService.exe to perform this operation.

Looking at vbMonitorService in the Services Window

Now that the service has been installed, we can start to use it.

  1. Bring up the Services window by clicking Start | Programs | Administrative Tools | Services on the Windows task bar. (In Windows 2000 Professional, open the Control Panel, double-click Administrative Tools, and then double-click Services.) In the Services dialog box, shown in Figure 9-15, you'll see our new service registered with the other system services.

    Figure 9-15

    Our service among the others in the Services window.

  2. Double-click vbMonitorService to display the properties dialog box, shown in Figure 9-16. Add the name File Sentinel to the Description text box. This name will show up in the Description column of the Services window. Note that if you are running Windows XP, the description text cannot be changed.

    Figure 9-16

    The properties dialog box for vbFileMonitorService.

  3. Click Apply to add the description to the service.

  4. Now we're ready to start running our new service and put it to work. Click the Start Service button. A Service Control dialog box with a progress bar will be displayed for a few seconds while our service is started, as shown in Figure 9-17.

    Figure 9-17

    A progress bar is displayed while our service starts running.

By examining the Services window, shown in Figure 9-18, you can see that the description of our service has been added and that it is also now running. As the service runs, it will monitor any changes to files in the root directory of the C drive.

Figure 9-18

Our service is running.

We can examine the vbFMS.txt file and see whether any suspicious file modifications occurred. A quick look at the text file might reveal some interesting changes.

Monday, July 30, 2001 9:33:13 PM File: C:\passwords.exe renamed to C:\trash.exe Monday, July 30, 2001 9:33:13 PM File: C:\trash.exe Changed Monday, July 30, 2001 9:33:18 PM File: C:\trash.exe Changed Monday, July 30, 2001 9:33:39 PM File: C:\trash.exe Deleted Monday, July 30, 2001 9:34:08 PM File: C:\salaries.xls Changed Monday, July 30, 2001 9:34:08 PM File: C:\salaries.xls Changed Monday, July 30, 2001 9:34:18 PM File: C:\passwords.txt Changed

When we want to stop our service, we simply double-click vbFileMonitorService in the Services window and then click Stop. A Service Control dialog box is displayed again (see Figure 9-19), and the service is stopped for us. Now you can see why we wanted to add those two methods, StartLogging and StopLogging, to the sentinel class. These methods make turning our service on and off pretty trivial.

Figure 9-19

Progress is made while our service is stopped.

Debugging a Windows Service

As you've just seen, after we finish writing our service, the compiled executable file must be installed on the computer where it will run before the project can function in a meaningful way. In addition, you cannot debug or run a service application by pressing F5 or F11 in the IDE. Because of the nature of services, you cannot immediately run a service or step into its code.

Because a service must be run within the context of the SCM rather than within Visual Studio .NET, debugging a service is not as straightforward as debugging other Visual Studio application types. To debug a service, you must start the service and then attach a debugger to the process in which it is running. You can then debug your application by using all the standard debugging functionality of the Visual Studio IDE.

note

You should not attach to a process unless you know what the process is and understand the consequences of attaching to and possibly killing that process. For example, if you attach to the WinLogon process and then stop debugging, the system will halt because it cannot operate without WinLogon.

You can only attach the debugger to a running service. As you might expect, the attachment process interrupts the functioning of your service instead of stopping or pausing the service's processing—that is, if your service is running when you begin debugging it, the service is still technically in the started state as you debug it, but its processing has been suspended.

Attaching a debugger to the service's process allows you to debug most but not all of the service's code. For example, because the service has already been started, you cannot debug the code in the service's OnStart method, nor can the code in the Main method that is used to load the service be debugged. Both procedures have already been executed to get the service loaded.

One way to work around this limitation is to create a temporary second service within your service application that exists only to aid in debugging. You can install both services and then start the dummy service to load the service process. After the temporary service has started the process, you can use the Debug menu in Visual Studio .NET to attach to the service process.

After attaching to the process, you can set breakpoints and use these to debug your code. Once you exit the dialog box you use to attach to the process, you are effectively in debug mode. You can use the SCM to start, stop, pause, and continue your service, thus hitting the breakpoints you've set. Remove the dummy service later after debugging is successful.

When you are ready to start debugging your service, use the SCM to start the service. Once the service is running, you can start debugging.

  1. Select Debug | Process from the IDE to display the Processes dialog box. Be sure to check the Show System Processes check box, which is cleared by default. Because we wrote a system process, if this option were not checked we would not see the process. Of course, our service does not have a user interface, so there is no title for our application to display in the Title column, as you can see in Figure 9-20.

    Figure 9-20

    Attaching to a process to debug our service.

  2. Double-click the vbFileMonitorService process to display the Attach To Process dialog box, shown in Figure 9-21. Check the Common Language Runtime option, and then click OK to attach the debugger to this process.

    Figure 9-21

    The Attach To Process dialog box.

  3. We've now successfully attached the debugger to our vbFileMonitorService process, as you can see in Figure 9-22. Click Close to dismiss the Processes dialog box.

    Figure 9-22

    Success—we've attached the debugger to our service's process.

You are now in debug mode. Go ahead and set any breakpoints you want to use in your code. Run the SCM and work with your service, sending stop, pause, and continue commands to hit your breakpoints.



Coding Techniques for Microsoft Visual Basic. NET
Coding Techniques for Microsoft Visual Basic .NET
ISBN: 0735612544
EAN: 2147483647
Year: 2002
Pages: 123
Authors: John Connell

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net