I converted my auto email program to a Windows service. Windows services are interesting because they work well when they are console programs but an MFC based program that has a main window is tricky. Since the auto email program was an MFC application with a main window, I had to change it to compile as a console program and control the email handling thread from the service callback function. Fortunately, Windows programs, those with a window, are called by the OS using the winmain() function but console programs use the main() function. Both can be present and a simple change to the linker settings changes which is called.

Windows services are just regular programs that can be controlled by a callback function within them that the OS calls. Other than that and the required code to let that callback affect the other code, there is no special trick to writing a service. There are issues outside of a program that might become important. For instance, a service runs using a special built-in user account which does not have access to networking features. You cannot open a UNC path on another system using the built-in account. The service can be run using some other user account but it needs to be installed as a service with that account information. Still, none of this is tricky.

One thing I did encounter that is interesting is that a regular user account cannot access the service control manager. I normally write a service program in a way that allows it to run as a non-service console program and to be able to install, start, stop, and remove itself as a service. I found this cool function elsewhere to at least determine if the user account is not elevated, on Vista and Windows 7, to the proper administrator level:

bool IsElevated( void )
{
    bool bResult = false;
    HANDLE hToken    = NULL;

    if ( !::OpenProcessToken( 
                ::GetCurrentProcess(), 
                TOKEN_QUERY, 
                &hToken ) )
    {
        return false;
    }

    TOKEN_ELEVATION te = { 0 };
    DWORD dwReturnLength = 0;

    if ( ::GetTokenInformation(
                hToken,
                TokenElevation,
                &te,
                sizeof( te ),
                &dwReturnLength ) )
    {
        ASSERT( dwReturnLength == sizeof( te ) );

        bResult = te.TokenIsElevated != 0; 
    }

    ::CloseHandle( hToken );

    return bResult;
}

This seems to work although I have not tested it on more than my Windows 7 x64 system.

My next task will be to add a message broadcast feature to the web interface for this so that a message can be sent using the iPhone look-alike web interface. That will require more AJAX code and some email sending capability built into the PHP script on the server side. I don’t think I needs a fancy email system, just enough of the SMTP handling code to send a single message that the auto-emailer will act on. An alternative might be to have the auto-emailer check a local file for email-like operations to avoid the initial email send/receive process. The web interface PHP scripts and the auto email service run on the same system and they both knows the location of the auto emailer log files so that is a good option. Another option is to use a socket connection to talk directly to the email handler code although there should never be more than one browser accessing the web interface anyhow. At least not at the same time doing the same thing.