Debugging Windows Services

Posted by

Paul Ballard posted recently about Debugging Windows Services, and I thought I should just add a few points to what he said.

Towards the end of his post, he said:

I tested every version of the Account property settings and no setting would prevent me from debugging the application. Therefore, you should not need to add any users or groups to the "Debugger Users" group on your machine. My testing on this was pretty quick though and so there could be some instances where this might be required.

There is one situation when you need to be a member of the Debugger Users group, and that's when you're trying to debug a process (any process, not just services) that's running under a different user account, and you're not a member of the Administrator group. This means if you're trying to write your software with LUA then you may run into problems debugging your service unless you're a member of the Debugger Users group.

But adding yourself to that group has two problems:

  1. It violates LUA, since it means you can attach to any process running under any account, and
  2. Even if you are a member of that group, only Administrators can start/stop services anyway so you're still up the creek!

There's a couple of ways to solve that second problem. The obvious one is to have a couple of batch files which you RunAs an administrators that just call NET START/NET STOP for you, and that's fine, but what I like to do is allow my server applications to run as both a Windows Service (for normal, production running) and as a regular Win32 process (for debugging).

It's actually quite simple to set up, and you can switch which one your server runs as by using the command-line arguments in the Main method. For example, here's some sample code:


public class MyService : ServiceBase
{
  static void Main(string[] args)
  {
    if (args.Length == 1 && args[0] == "-console")
    {
      RunFromConsole();
      return;
    }

    // Otherwise, normal service processing here
    ServiceBase.Run(new ServiceBase[] { new MyService() });
  }

  // Simulates running the service, even though we're
  // actually running from the console.
  static void RunFromConsole()
  {
    MyService service = new MyService();
    service.OnStart();
    // We need to wait for an "exit" event, just waiting for
    // a keypress is probably fine.
    Console.ReadLine();
    service.OnStart();
  }
}

Now, to be honest, I just wrote that from memory so it's probably not completely accurate, but you get the idea. And I'm sure your argument-parsing code would be much more robust than mine :)

This does present a couple of differences to running as a normal service, though. For example, you (obviously) can't run your service as the LOCAL SERVER, LOCAL SERVICE or NETWORK SERVICE accounts. However, for 95% of debugging purposes, it's perfect - especially since you can now just run your service directly from the IDE!

Slight Update to DblClk

Posted by

OK, due to popular demand (well, not really) I decided to recompile DblClk to statically link to the C runtime. This makes the compiled .exe a bit bigger (18KB instead of 6KB ), but it also means you don't need Visual Studio 2005 Beta 2 installed to run it (which is a bit of a plus).

So download version 1.01 of DblClk here and enjoy!

Introducing DblClk

Posted by

The Problem

The middle button on my mouse is actually the mouse wheel. When I click it, most applications go into this "scroll mode", which would be great if I didn't have a mouse wheel, or I suffered RSI and I couldn't use the mouse wheel. But for me, this mode is annoying and useless. So, I decided that I wanted to make it so that clicking the middle mouse button of my mouse would simulate a left-double-click. Now, there are applications out there which let me do that - for example, Cool Mouse does it. As does UltraMouse.

The thing is, both of those apps are shareware, and they both do much more than just simulate double-clicks. (Also, they both seem kind of dodgy, given that I had so much trouble finding the actual publisher's website - rather they seem to be plastered all over those 'shareware download' sites. In fact, I couldn't even find the publisher's site for UltraMouse!).

Enter DblClk

Now, simulating double-clicks is actually really easy using straight Win32 API calls. So I decided to create DblClk which is a very small program, and all it does is simulate left-double-clicks when you click the middle mouse button. That's it.

For anyone interested, the code is simple. A simple call to SetWindowsHookEx to install our hook, and another call to SendInput to simulate the double-click when a middle-button click comes in.

Here's some code which installs the hook:


  ::SetWindowsHookEx(WH_MOUSE_LL, HookProc, hinst, 0);

The first parameter tells Windows that we want all "low-level" mouse events. The difference between low-level mouse events and the regular kind is that low-level mouse events are posted to our queue before they're sent to the thread which "owns" mouse input. There's probably no reason why we wouldn't want to use WH_MOUSE, except that that requires a DLL, which gets inserted in every running process (only a big deal because it means we can't just build a single .exe).

The second parameter to SetWindowsHookEx we set to our procedure which gets called for every mouse event. More on that in a second. The third parameter is the HINSTANCE of our executable. The last parameter is the thread ID of the thread we want events for (though we set it to zero because we want events for all threads).

Now, the hook procedure is almost just as simple. Essentially we check for a WM_MBUTTONDOWN event and use the SendInput method to simulate a left button down, left button up, left button down combo. We don't send the final left button up yet because this way we can simulate the double-click-and-drag operation which is useful in some text editors. When we get a WM_MBUTTONUP then we send the final left button up message. Here's some code:


LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    // If nCode is < 0, we're supposed to return CallNextHookEx, according
    // to MSDN, at least...
    if (nCode < 0)
    {
        return ::CallNextHookEx(0, nCode, wParam, lParam);
    }

    MSLLHOOKSTRUCT *pHookStruct = (MSLLHOOKSTRUCT *)lParam;
    if (wParam == WM_MBUTTONDOWN)
    {
        // We need to simulate a left-button down, left-button up,
        // left-button down combo
        INPUT inputs[3];
        ::ZeroMemory(&amp;inputs, sizeof(inputs));
        inputs[0].type = INPUT_MOUSE;
        inputs[0].mi.dx = pHookStruct->pt.x;
        inputs[0].mi.dy = pHookStruct->pt.y;
        inputs[0].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
        inputs[0].mi.time = pHookStruct->time;
        inputs[1].type = INPUT_MOUSE;
        inputs[1].mi.dx = pHookStruct->pt.x;
        inputs[1].mi.dy = pHookStruct->pt.y;
        inputs[1].mi.dwFlags = MOUSEEVENTF_LEFTUP;
        inputs[1].mi.time = pHookStruct->time;
        inputs[2].type = INPUT_MOUSE;
        inputs[2].mi.dx = pHookStruct->pt.x;
        inputs[2].mi.dy = pHookStruct->pt.y;
        inputs[2].mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
        inputs[2].mi.time = pHookStruct->time;

        ::SendInput(3, inputs, sizeof(INPUT));
        return 1;
    }
    else if (wParam == WM_MBUTTONUP)
    {
        // We need to simulate the final left-button up
        INPUT inputs[1];
        ::ZeroMemory(&inputs, sizeof(inputs));
        inputs[0].type = INPUT_MOUSE;
        inputs[0].mi.dx = pHookStruct->pt.x;
        inputs[0].mi.dy = pHookStruct->pt.y;
        inputs[0].mi.dwFlags = MOUSEEVENTF_LEFTUP;
        inputs[0].mi.time = pHookStruct->time;

        ::SendInput(1, inputs, sizeof(INPUT));
        return 1;
    }

    // We didn't process the message, so just return CallNextHookEx
    return ::CallNextHookEx(0, nCode, wParam, lParam);
}

Now, I'm no Win32 expert, so I might not be doing everything right (and I haven't tested my code all that much), but this works great for me. The executable compiles down to just 6KB and when running uses just 1MB of memory (which is just our program stack). Much better than those shareware apps which try to do so much more :)

Download Now!

You can download version 1.0 of DblClk now. The zip file includes a compiled binary, which currently requires the MSVC.NET 2005 C++ runtime (msvcr80.dll). I don't know the redist rules for this dll, so for now you just have to have it installed already. If not, you can always compile the included source file to make your own binary :)

If I get enough requests, I'll compile a version which can be run without the dll, but for now this is mainly just for my own amusement.

Extra Features

OK, I lied. There is one more feature that I added to the program. You see, normally when you install a low-level mouse hook, you need a running message queue to have events posted to. That means that the app, once started, is always running (at least until you log off). So I added one extra feature, and that's the ability to stop the application by running it again with the "-stop" command-line parameter. If you do that, it'll stop the currently-running instance and the middle-button will function as it had before.

Managed IIS SMTP Sink Wrapper: Message.CopyContentToStream

Posted by

It seems there's a bug in the Managed Sink Wrappers for SMTP and Transport Events, which I used to write SMTP Command and Transport sinks for IIS in C#. The bug is in the Message.CopyContentToStream method. The problem only occurs when a message's MIME content is a multiple of 1024 bytes long. It manifests itself as an EndOfStreamException being thrown when you call Message.CopyContentToStream.

Here's some replacement code that I've written which fixes the problem (note, I didn't modify the actual source to the managed wrappers; instead I wrote a separate method for doing the same thing. I didn't want to change the source in case it ever gets updated by Microsoft).


void CopyContentToStream(Message msg, Stream stream)
{
  const int BYTES_TO_READ = 1024;
 
  byte[] buffer;
  uint offset = 0;
  do
  {
    try
    {
      buffer = msg.ReadContent(offset, BYTES_TO_READ);
    }
    catch(EndOfStreamException)
    {
      // We get this exception when we try to read
      // from the stream, but we're already at the end.
      // This just means we can exit our loop now.
      break;
    }
 
    if (buffer.Length > 0)
    {
      stream.Write(buffer, 0, buffer.Length);
    }
    offset += ((uint) buffer.Length);
  } while (buffer.Length == BYTES_TO_READ);
 
  stream.Position = 0;
}

The code above is almost the same as the code in the wrapped CopyContentToStream, except I've added an exception handler to catch EndOfStreamException. The problem is that if you pass a dwOffset to ReadContent that's past the end of the stream, it throws the EndOfStreamException. But notice the condition of the while loop: it's while(buffer.Length == BYTES_TO_READ) - this means that as soon as ReadContent returns a value that's < 1024, we exit the loop and all is well.

However, if the content is a multiple of 1024 bytes long, the last chunk returns 1024, meaning the loop condition is still true, but the offset is then placed after the end of the stream, and the next time it calls ReadContent, it gets the exceptions.

So, in my code, I kept the loop condition the same, because it saves us getting an exception when the content is not a multiple of 1024 bytes (which is most of the time, of course). But on those rare(-ish) occasions when the content is a multiple of 1024, the exception will be thrown, and we just break out of the loop.

Hello, Hotmail?

Posted by

Why does Hotmail (and Yahoo! as well) serve up it's pages as ISO-8859-1? If I send an email to a Hotmail account, and my email is encoded as UTF-8, Hotmail is able to properly decode my email (I send it encoded using quoted-printable to fool any 7-bit MTAs on the way) but when it serves up the content, it comes back telling my browser that it's encoded as "iso-8859-1"1, which means anything outside of US-ASCII is garbage.

Why, oh, why, in this internationally-aware day and age, do they still use a non-Unicode encoding? I mean, if it's really such a hassle to convert their site to UTF-8, then the least they could do, surely, is convert my emails from UTF-8 to iso-8859-1! I'd much rather have unknown characters show up as ?'s than have to tell my users to "Click on the View menu, then on Encoding then on Unicode (UTF-8) to be able to view this email properly" - especially since it looks OK for everybody not using Hotmail (or Yahoo)

The only thing I can think of for people who want to use a web-based email client is to switch to Gmail, which serves up their pages as UTF-8 at least. Not only that, but if you send a gmail user an email encoded as something else (e.g. iso-8859-1) it'll automatically convert it to utf-8 before serving it up! Nice work, gmail!

1 Actually, I have a feeling it doesn't tell the browser any such thing, and the browser just uses the default defined by the standard...