Posted February 12, 2002 12:00AM by Robert Smith
Tagged: Coding, DotNet
The code pt2a: IPConnector.exe Service (continued from part 1) Creating a windows service is not a big deal ... so long as you or a friend have the IDE. There is a LOT of code under the hood, but happily you don't have to type much of it. When you choose Windows Service as the first project type in your solution the IDE churned out the hundreds of lines of plumbing for you. If you don't yet have the IDE then you could just ask a friend to create a new Service solution and pass you the text .vb files. Most of it is boilerplate so you can get it all rolling via the Framework console window after a few error-filled compiles to track down the correct namespaces and references. We're using the IDE so we have the pleasure of drag&drop to a non-gui designer (If you have Delphi experience, the .net IDE is very familiar, isn't it? <g>). Click on the IPConnector service project node to tell the IDE that you're working with that project now. When your service starts, it will be calling on the IPConnection.dll you created earlier. You can either manually type an Imports at the top of the service class or use the IDE to add a reference to that dll. Adding a reference with clicks starts off like VBClassic; Go to Project|Add Reference. Unlike VBClassic now you get to choose from systemwide .net options (GAC-ed assemblies), registered COM components or from a list of .net projects in the current solution. Use the third tab and pick the IPConnection.dll as shown below, make sure you doubleclick on it or highlight it and press "Select" and see it added to the bottom listbox or the reference won't be made. Time to make that reference useful. In the solution explorer, doubleclick on the IPConnector.vb node to bring the Service designer to view. Go to your toolbox, click on the Components "drawer" (Not the Windows Forms drawer), select the Timer component and drag & drop to the Service designer. 
Click on the timer icon to select it and hit F4 to make it current in the property sheet. Change the Interval property to "120000" (timer resolutions are in milliseconds, so each second is 1000, each minute is 60000 and our desired 2 minutes is 60000 x 2= 120000) then change the Name property to tmrDSLConnect. Now go back to the icon and doubleclick on it to bring up it's Elapsed event handler code. Type the following in the stub: Dim sStaticIP As String
Dim sNumAddresses As String
Dim oIP As New IPConnection.IPConnection()
Try
sStaticIP = System.Configuration.ConfigurationSettings.AppSettings.Get _
("MyStaticIP")
sNumAddresses = System.Configuration.ConfigurationSettings.AppSettings.Get _
("NumberOfAddresses")
Try
oIP.ConnectIfNot(sStaticIP, CInt(sNumAddresses))
Catch ex2 As Exception
System.Diagnostics.EventLog.WriteEntry _
("IPConnector", ex2.Message)
End Try
Catch ex As Exception
System.Diagnostics.EventLog.WriteEntry("IPConnector", ex.Message)
End Try
oIP = Nothing
You should be able to generally follow along with this one; We're creating an instance of our IPConnection object, getting the values for it's entry point method arguments, calling it and killing it. Once again, you see some very long, fully qualified Namespaces. Once again, I did this because for most of us the .net framework namespace hierarchies are not yet all committed to rote memory, so for the more esoteric objects it is good to see the whole qualified string as a help to learning. The Namespace we haven't seen before is "System.Configuration.ConfigurationSettings." We use this to read our dynamic values from an XML configuration file so we don't have to hard-code or lose them in the registry. If we go to a new ISP and get a new static, or add another adapter to our box we just change the value in the XML file and the service is updated. (We'll cover how to make that file in a later section) If you don't how like those calls look, you can just add "Imports System.Configuration.ConfigurationSettings" to the top of the class and replace that same text in the method with the more concise "Appsettings.Get(..." On the flipside, if you like typing a lot of characters, feel free to replace our age-old VB CInt() function with the fully qualified Microsoft.VisualBasic.Conversions.Int() method. Same result. The code pt2b: IPConnector.exe Installer SupportYou're almost done now. There're just a couple of things to add before you can compile and move your solution to your server ... the code that will add it to that machine's services. Again, the IDE does a lot of typing for you ... but perhaps not all that it could. Right-click your service app's designer and choose "Add Installer." In a second, you will get a new tab with a new designer and a new file added to the service project called "ProjectInstaller.vb" This time it's ok to just leave that file named the default, but there are other settings you'll have to tweak. 
Select the icon "ServiceInstaller1" and press F4 to load it's property page. Change the ServiceName property to "IPConnector" Now, select the icon "ServiceProcessInstaller1" and press F4 to load it's property page. Make sure that the Account property is set to "User." If you've read other articles on making windows services with VB.net, you have probably read that that you should almost always change the Account to "LocalSystem" ... this is one of those times when the almost comes into play. Because this application is going to be used to make connections to your ISP it has to run under an account that knows your logon username and password; The System account doesn't have that knowledge. This has a couple of ramifications but each one of those has a good workaround. We'll get into those details when we go over installation. The property sheet has entries for UserName and Password, but personally I'd leave those blank, there're still some concerns about decompilers so it's probably best to play it safe. You'll be prompted for that information when you install the service anyway. The StartMode property defaults to "Manual", keep it as-is so you can take your time setting up the initialization file on the server. At key places in our projects we've typed in lines that will write to the EventLog. These will help us when we later want to see how our service is doing. Under the hood, the ProjectInstaller has one of those lines too ... it's called when you install and the service and adds a Log entry of "Service1 Started". Depending on your version of the IDE this text may or may not be correctly set to the name of your service project. Best to check it before you compile. Doubleclick on the "ServiceInstaller1" icon and scroll up to the top of the code window. There's a collapsed region labelled "Component Designer generated code" click on it's box to open it up. Now look down to the collapsed subregion labelled "Private Sub InitializeComponent()" click on it's box (or the ellipses at the end of the line) to open it. Look for the following line:
Me.ServiceInstaller1.ServiceName = "Service1"
Change that default text to whatever you want. Now you can go ahead and build your solution. And get out a floppy, we're ready to move to the web server! InstallationIn my case I have the full framework on my web server and my .net dev box (with the full IDE) is not on my LAN (I still have a trust issue) so I use floppynet to transfer files. If your setup is different adjust the following to fit, ok? Navigate to the bin of your IPConnector project (typically it will be "C:\Documents and Settings\[user name]\My Documents\Visual Studio Projects\IPConnector\bin" unless you set your project options manually) send the files "IPConnector.exe" and "IPConnection.dll" to your floppy. Pop the floppy into your web server box. Create a new folder in the location of your choice, you can name it whatever you want but it's good to use a name that is easy to type and that you can remember. Personally, I put all my service apps in the same folder and I'll use "C:\MyServices\" in this example. Copy the exe and dll from the floppy to the folder. Now call up the .net console window, and navigate to your C:\MyServices. At the prompt, type the following:
InstallUtil.exe IPConnector.exe
You may have read that the InstallUtil is only available if the full framework is on the machine and not if only the runtimes are installed. However, I just ran this on a box that only had the runtimes and it worked. If you get an issue, try copying the InstallUtil.exe from your dev box (might work) or you might have to make a real installation (see WROX Press' "Professional VB.Net" for the details on that route). Assuming that InstallUtil started up for you, a ramification of using a "User" account for the service is that you will be prompted to provide the account name and password (obviously, the account you use must have local admin installation privileges). The trick here is that when you type in your username, preface it with the machine name or you'll get a security error and the install will rollback. As you see in the graphic below, I'm installing on the machine "KilnPerson" under the account "GreenieAnt" 
Now the service is installed ... but it's not running yet. Before we start it we have to take care of it's XMLconfiguration file so that it knows what ip address to look for. We'll cover that next. The XML Configuration fileThis will be a short section. If any .net exe needs configuration or initialization settings, forget the registry and just put the settings in an XML text file in the same folder as the executable. For example: <configuration>
<appSettings>
<add key="MyStaticIP" value="12.34.567.89" />
<add key="NumberOfAddresses" value="2" />
</appSettings>
<configuration>
The appSettings section holds name-value pairs, if you want to add a new setting, you just add a new line to the section. Save the file named exactly as your executable plus the extension .config and you're done. So in the case of our DSPConnector.exe service, we save the above into a file named "IPConnection.exe.config" If you're wondering why we didn't call the file "IPConnection.dll.config" and save outselves the trouble to getting these values in the service and passing them to the object, the answer is a curt "Because MS says no": Outside of ASP.net only exe's can have self-named config files, dlls can't. There are other defined childnodes of <configuration> that you can use in your config files, see the docs later to track them down, right now we're going to start up our service. Turning on the ServiceThe last part of this little tutorial is not really a .net thing, it's SOP for all services you have on your box. But since this might be the first time you've set up and started a user account service it might be helpful even for old-timers. Open your Services applet (On WServer it's in Start >> Administrative Tools, for WPro it's at Start >> Settings >> Control Panel >> Administrative Tools >> Services) and look for the entry "IPConnector". You'll see that, just as you specified, it's startmode is "Manual" and that it is is going to run under your user account. I want to just make a quick point here for your future reference: If you have to later remove the service (maybe you'll want to add some more EventLog writes to track down a bug) you should first use the Services applet to Stop the service, then close the Services applet (not just minimize), then run the InstallUtil commandline with the switch "/u" (no quotes). Attempting to uninstall the service while the Services applet is running might "confuse" you or your machine, as the Services subsystem will still be listing the service (and perhaps even trying to run it) even though it is gone. Now it's time to do a round of tests against the spec. Testing the serviceWe'll need a couple of other tools running to make sure that our service is working as expected. Press Start >> Run, type "cmd" (no quotes) in the box and hit Enter. This brings up a generic windows command prompt which we'll need to list our current IP addresses. Open up the Event Viewer, if it isn't already, and switch to view the Application Log. The first two items in the spec combined to look for a current connection to our static IP and, if found, go back to sleep. Because of how we added our EventLog writes these alone will not do anything obvious so we'll begin with the third item: - If the count of addresses is equal to the count of LAN and, for safety, if the address is not the static then we are not connected to the internet, so force a new connection
If you are currently connected to the internet, drop that connection. Bring your cmd window to the front and at the prompt type "ipconfig.exe" (no quotes). The number of adapters that are listed depends on how your machine is set up but if your internet connnection was dropped successfully you should not see your static IP in the list of adapter connections. That is our starting point. Go back to the Services applet and doubleclick on the "IPConnector" service to bring up it's properties. In the General tab's "Service status" section, click the "Start" button. In a second or two the status will change to "Started." Bring your EventViewer to the front and press F5 to refresh the Application log. If all is well you should see that the "IPConnector" service was started. Now sit back and wait for your systray clock to tick off a few minutes (you set the service to work every two, but remember that it is doing the ticks in milliseconds not on changes of the actual minute timepart). When you've waiting long enough, bring up the cmd window and press F3 (reloads the last typed command) and Enter. If all worked correctly you should now see your static IP in the list. Bring up your EventViewer and press F5 to refresh the Application Log, three New entries will be in the list. Doubleclick on the first one to bring up the details and you'll see, in order of occurance, "Computer is not connected", "Attempting to make connection" and "Connection Succeeded." If anything went wrong with the service then the Events will give you a hint to the location of the problem. If this is your situation you may want to go back to your code and add more "Try...Catches" and EventLog writes to your code to help track down the exact issues. (Please see the next section "User Accounts and Uninstalls" for helpful hints on this.) Assuming that your service woke up and made it's connection, you can check off that item in the spec (and the first item "Make a Windows Service that wakes up every two minutes and checks the computer's IP Addresses" because obviously it worked too!). Now we'll test item four: - If the static IP is not in the list but the count of addresses is equal to the count of LAN connection plus 1 then we are connected to the internet but not with the static. In this case force a disconnect from the internet and force a reconnect in an attempt to get hold of the static IP.
For this is is neccessary to create a poltergeist connection. With my ISP I can do this by dropping my connection on the server and starting a connection on another box. Whatever box gets the first connection, gets the static; all subsequent connections get a dynamic IP address. You'll have to create the scenario in a way that works on your system, but I'll use mine as a general example. With another computer connected to the static IP address, do a manual forced connection using the PPPOE software. Use ipconfig to verify that you are connected but not with the static. Now disconnect the static on the other box and wait for the service event. When the milliseconds have gone by, check the ipconfig again to make sure that the old connection was indeed dropped and the static is now connected. Refresh the EventLog to see that there are new entries for "Killing non-static ip connection", "Computer is not connected", "Attempting to make connection" and "Connection Succeeded." That covers the spec, since the other items were support oriented we were able to check them off as the result of the real functionality. Congratulations, you've just made a service that taught you a lot and has a real-world use. And becuase you didn't just download the compiled assemblies you can go in and work on the functionality as required or desired. In the next and final section, we'll tidy up a few loose ends and discuss how to deal with things if you didn't get your service completely running this first time User Accounts and UninstallsWe mentioned that because this particular service is being used to connect to your ISP, when it runs it has to be able to send your logon information. And it was for that reason that we had to make this service run under a User account. The thing about this setup is that it could fail in the future for a number of reasons totally unrelated to the code. For instance, if you change your windows logon password, the service will fail with a security issue because it is still set up to use your old password. Here are some tips for this type of service application: - Instead of installing under your own user account, create a new user account with Local Admin (install) rights and a non-expiring password. Log on to your computer using that account, set up PPPOE and tell IE to allow autodials, make sure that you can get a connection with that account and then install the service (and use it's username and password at the service installation prompts)
- When you are done testing and happy that the service is running as desired, go to the Services applet, doubleclick on the service entry to bring up it's properties and change the "Startup type" to "Automatic" so that you won't have to manually restart the service every time you reboot the computer.
We mentioned earlier that when you uninstall a service you should close the Services applet so you and the computer don't get "confused." Here's the full step by step of how to get rid of a .net service:
1) Open the Services applet and doubleclick on your service to bring up it's properties.
2) Press the "Stop" button
3) Close the properties window and the Service applet
4) Open a .net console session and navigate to the location of your service exe (such as "C:\MyServices\")
5) At the prompt type InstallUtil, the neame of your service exe and the uninstall switch "/u". For example, our service was named IPConnector.exe so we would use:
InstallUtil.exe IPConnector.exe /u
6) ReOpen the Services applet and make sure that the service is no longer in the list.
Now you can feel free to delete the exe and dll or replace them with updates. To reinstall, follow the steps we went over previously in this article. The WrapI don't know about you, but I learned a bunch of new things in making this project. In the article we moved along step by step but the truth is that in my day of banging out the code there were a number of false starts and blind alleys. For example, I had been under the impression that making this service would require creating a worker thread, or at least a new process. That wasn't the case, but I didn't know that at the time; it got me researching what exactly those things are and how, when and why you can or should do them in .net, and that gave me ideas and experiences that I otherwise wouldn't have had if I just sat around waiting for someone else to put the compiled assemblies up as shareware. But you know the most important thing I learned from this little project? I learned that MS did a great job on .net. Our VBClassic experience is going to give us an edge over the newbies (especially when it coes to COM integration) with the flip being that we old farts have to be on the lookout for times when we're thinking too hard out of habit about issues that can now be solved using the framework alone. If we want to get picky, the system we've made does not yet deal with poltergeist connections that reconnect to another dynamic address and, as written, it will take another timer event to get around to checking the new connection address. To fix that up we could do a bit of recursion on the AmConnected and keep looping till we get the static and that little feature will of course need support for restricting the number of tries within a run so that we don't end up looping forever (maybe there is a good reason to spawn a new process after all). Plus, instead of hard-coding the 120000 milliseconds we could use another line in our config file to specify the timer's Interval property. You can feel free to add those features and more. As we said in the beginning, this works and accomplishes the spec but it is only a starting point. I've loved VB since 3 came out (I own 1 and 2), and I'm going to miss VBClassic's more useful IDE (with SDI and Edit&Continue), but for all the new power that we have with the framework, I'm happy to be able to get legitimately flowery and say "VB is dead, long live VB". Robert Smith
Kirkland, WA |