Trial System Service

TrySvc documents the creation of a Windows System Service. System Services normally run at the System level and therefore have access to virtually everything in the system. They boot with the operating system and don't require that a user be logged in to execute.

SysSvc.h

/*********************************************************************************************
* Copyright (c) 2002 - 2010 David Cass Tyler, PO Box 1026, Willard, NM 87063, (505) 384-5342 *
*********************************************************************************************/

// CSysSvc Prototypes and Defines

#ifndef _SYSSVC_H_
#define _SYSSVC_H_

#include "Messages.h"   // Event message IDs

#define SERVICE_CONTROL_USER 128

class CSysSvc

   {

public:

   CSysSvc ( const char * pszServiceName, const char * pszDependencies = "\0\0" );

   virtual ~CSysSvc();

   BOOL                    IsInstalled();
   BOOL                    Install();
   BOOL                    Uninstall();
   BOOL                    StartService();
   BOOL                    Initialize();

   virtual void            Run();
   virtual BOOL            OnInit();
   virtual void            OnStop();
   virtual void            OnInterrogate();
   virtual void            OnPause();
   virtual void            OnContinue();
   virtual void            OnShutdown();

   virtual BOOL            OnUserControl     ( DWORD dwOpcode );

   BOOL                    ParseStandardArgs ( int argc, char * argv [] );
   void                    DebugMsg          ( const char * pszFormat, ... );
   void                    LogEvent          ( WORD wType, DWORD dwID, const char * pszS1 = NULL, const char * pszS2 = NULL, const char * pszS3 = NULL );
   void                    SetStatus         ( DWORD dwState );

   // Static Member Functions

   static void WINAPI      ServiceMain       ( DWORD dwArgc, LPTSTR * lpszArgv );
   static void WINAPI      Handler           ( DWORD dwOpcode );

   // Data Members

   const char              * m_pszServiceName;
   const char              * m_pszDependencies;

   int                     m_iMajorVersion;
   int                     m_iMinorVersion;

   SERVICE_STATUS_HANDLE   m_hServiceStatus;
   SERVICE_STATUS          m_Status;
   BOOL                    m_bIsRunning;

   // Static Data

private:

   static CSysSvc          * m_pThis;     // Nasty Hack to Get Object Ptr
   static bool             m_fDebugMode;  // Running in Debug Mode

   HANDLE                  m_hEventSource;

   };

#endif   // _NTSERVICE_H_

TrySvc.h

/*********************************************************************************************
* Copyright (c) 2008 - 2010 David Cass Tyler, PO Box 1026, Willard, NM 87063, (505) 384-5342 *
*********************************************************************************************/

#ifndef _TRYSVC_H
#define _TRYSVC_H

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

// Required Headers

#ifndef _SYSSVC_H_
#include "SysSvc.h"                    // The System Service Class Prototypes and Defines
#endif

#ifdef TRACE_HEADERS // Insert AFTER All Other #include Files
#pragma message ( "NOTE:  Included header file:  \"" __FILE__ "\"" )
#endif

/*********************
*  Helper Functions  *
*********************/

BOOL  GetUserAndDomainNames   ( LPTSTR UserName, LPDWORD cchUserName, LPTSTR DomainName, LPDWORD cchDomainName );
int   WriteCon                ( const char szFormat[], ... );

/************************************************************
*  The CTrySystemService Class, Prototypes and Defines   *
************************************************************/

class

#ifdef _AFXEXT
AFX_EXT_CLASS
#endif

CTrySystemService : public CSysSvc

   {

   public:

      CTrySystemService();          // The System Service Constructor
      virtual ~CTrySystemService(); // The System Service Destructor

      virtual void Run();           // The Open Thread Waiting for Close to be Called

   };

#endif   // _TRYSVC_H

WriteCon.h

/***************************************************************************************
* Copyright (c) 2002 - 2010 Cass Tyler, PO Box 1026, Willard, NM 87063, (505) 384-5342 *
***************************************************************************************/

#ifndef _WRITECON_H_
#define _WRITECON_H_

#if _MSC_VER > 1000
#pragma once
#endif /* _MSC_VER > 1000 */

/* Declare C Function Prototypes */

#ifdef  __cplusplus
extern "C" {
#endif   /* __cplusplus */

int   WriteCon             ( const char szFormat[], ... );

void  RedirectIoToConsole  ( const char * lpConsoleTitle );

#ifdef  __cplusplus
}
#endif   /* __cplusplus */

/* Declare C++ Function Prototypes */

#ifdef __AFX_H__
CString FormattedString ( const char szFormat[], ... );
#endif   /* __AFX_H__ */

#endif   /* _WRITECON_H_ */

Messages.mc

;// Copyright (c) 2008 - 2010 David Cass Tyler, PO Box 1026, Willard, NM 87063, (505) 384-5342
;//
;// This file contains messages for the TrySvc Application that are compiled using the Message Compiler.

MessageId=100
SymbolicName=EVMSG_INSTALLED
Language=English
The %1 service was installed.
.

MessageId=
SymbolicName=EVMSG_REMOVED
Language=English
The %1 service was removed.
.

MessageId=
SymbolicName=EVMSG_NOTREMOVED
Language=English
The %1 service could not be removed.
.

MessageId=
SymbolicName=EVMSG_CTRLHANDLERNOTINSTALLED
Language=English
The control handler could not be installed.
.

MessageId=
SymbolicName=EVMSG_FAILEDINIT
Language=English
The initialization process failed.
.

MessageId=
SymbolicName=EVMSG_STARTED
Language=English
The service was started.
.

MessageId=
SymbolicName=EVMSG_BADREQUEST
Language=English
The service received an unsupported request.
.

MessageId=
SymbolicName=EVMSG_DEBUG
Language=English
Debug: %1
.

MessageId=
SymbolicName=EVMSG_STOPPED
Language=English
The service was stopped.
.

MessageId=
SymbolicName=EVMSG_SHUTDOWN
Language=English
The system is shutting down.
.

SysSvc.cpp

/***************************************************************************************************************************
*                                                                                                                          *
*  Copyright (c) 2002 - 2010 David Cass Tyler, PO Box 1026, Willard, NM 87063, (505) 384-5342, David.Cass.Tyler@gmail.com  *
*                                                                                                                          *
*  I am not the original author of this code, but it has been extensively modified by me.  Its origins are long lost in    *
*  coding history.  If you feel that you were the original author of this code, please let me know and I will happily      *
*  acknowledge your authorship.  And thank you for having originally shared it.  It has been very useful.                  *
*                                                                                                                          *
*  CSysSvc is the base Windows System Service Class for building a system service and handles the generic messaging        *
*  with the service database.                                                                                              *
*                                                                                                                          *
***************************************************************************************************************************/

// Implementation of CSysSvc

#include "StdAfx.h"                    // The Standard MFC Classes, Prototypes and Defines

#include <STDIO.H>                     // Standard I/O Routine Prototypes and Defines
#include <STDLIB.H>                    // Standard Library Includes

#include "SysSvc.h"                    // CSysSvc Class Prototypes and Defines
#include "WriteCon.h"                  // WriteCon()

#define VERBOSE                        // Outputs DebugMsg Strings to Console

// Static Variables

CSysSvc * CSysSvc::m_pThis = NULL;
bool CSysSvc::m_fDebugMode = false;

CSysSvc::CSysSvc ( const char * pszServiceName, const char * pszDependencies )

   {

   // Copy the address of the current object so we can access it from
   // the static member callback functions.

   // WARNING: This limits the application to only one CSysSvc object.

   m_pThis = this;

   // Set the Default Service Name and the Dependencies

   m_pszServiceName  = pszServiceName;
   m_pszDependencies = pszDependencies;

   // Save the Version Info

   m_iMajorVersion   = 1;
   m_iMinorVersion   = 0;
   m_hEventSource    = NULL;

   // Set Up the Initial Service Status

   m_hServiceStatus                    = NULL;
   m_Status.dwServiceType              = SERVICE_WIN32_OWN_PROCESS
                                       | SERVICE_INTERACTIVE_PROCESS;
   m_Status.dwCurrentState             = SERVICE_STOPPED;
   m_Status.dwControlsAccepted         = SERVICE_ACCEPT_STOP
                                       | SERVICE_ACCEPT_PAUSE_CONTINUE
                                       | SERVICE_ACCEPT_SHUTDOWN;

   m_Status.dwWin32ExitCode            = 0;
   m_Status.dwServiceSpecificExitCode  = 0;
   m_Status.dwCheckPoint               = 0;
   m_Status.dwWaitHint                 = 10000LU;

   m_bIsRunning                        = FALSE;

   DebugMsg ( "CSysSvc::CSysSvc()" );

   }  // CSysSvc::CSysSvc ( const char * szServiceName )

CSysSvc::~CSysSvc()

   {

   DebugMsg ( "CSysSvc::~CSysSvc()" );

   if ( m_hEventSource )
      {
      ::DeregisterEventSource ( m_hEventSource );
      }

   }  // CSysSvc::~CSysSvc()

////////////////////////////////////////////////////////////////////////////////////////
// Default command line argument parsing

// Returns TRUE if it found an arg it recognised, FALSE if not
// Note: processing some arguments causes output to stdout to be generated.

BOOL CSysSvc::ParseStandardArgs ( int argc, char * argv[] )

   {

   // See if We Have Any Command Line Args We Recognise

   if ( argc <= 1 )
      return FALSE;

   if ( argv [ 1 ] [ 0 ] == '/' ) argv [ 1 ] [ 0 ] = '-';

   // Spit Out Version Info

   if ( _stricmp ( argv [ 1 ], "-version" ) == 0 )
      {

      WriteCon (

         "%s Version %d.%d\n",

         m_pszServiceName,
         m_iMajorVersion,
         m_iMinorVersion

      );

      WriteCon (

         "The service is %s installed\n",

         IsInstalled() ? "currently" : "not"

      );

      return TRUE;   // Say We Processed the Argument
      }

   // Request to Install.

   else if ( _stricmp ( argv [ 1 ], "-install" ) == 0 )
      {

      if ( IsInstalled() )
         {
         WriteCon ( "%s is already installed\n", m_pszServiceName );
         }

      // Try and Install the Copy that's Running

      else
         {

         if ( Install() )
            {
            WriteCon ( "%s installed\n", m_pszServiceName );
            }

         else
            {
            WriteCon ( "%s failed to install. Error %d\n", m_pszServiceName, GetLastError() );
            }
         }

      return TRUE;   // Say We Processed the Argument
      }

   // Request to Uninstall.

   else if ( _stricmp ( argv [ 1 ], "-remove" ) == 0 )
      {

      if ( ! IsInstalled() )

         {
         WriteCon ( "%s is not installed\n", m_pszServiceName );
         }

      // Try and Remove the Copy that's Installed

      else
         {

         if ( Uninstall() )
            {

            // Get the Executable File Path

            char szFilePath [ _MAX_PATH ];

            ::GetModuleFileName ( NULL, szFilePath, sizeof ( szFilePath ) );

            WriteCon ( 
               "%s removed. (You must delete the file (%s) yourself.)\n",
               m_pszServiceName,
               szFilePath
            );

            }

         else
            {
            WriteCon ( "Could not remove %s. Error %d\n", m_pszServiceName, GetLastError() );
            }
         }

      return TRUE;   // Say We Processed the Argument

      }

   // Run the Service in Debug Mode

   else if ( _stricmp ( argv [ 1 ], "-debug" ) == 0 )
      {

      m_bIsRunning = TRUE;

      m_fDebugMode = true;

      if ( ! m_pThis->OnInit() )
         return FALSE;

      m_pThis->Run();
      m_pThis->OnStop();

      m_fDebugMode = false;

      return TRUE;
      }

   // Output Help Information

   else if ( _stricmp ( argv [ 1 ], "-?" ) == 0 )
      {
      WriteCon ( 

         "Parameters are:\n"
         "\t-debug   to debug the system service\n"
         "\t-install to install the system service\n"
         "\t-remove  to remove  the system service\n"
         "\t-version to retrieve the version information\n"
         "\t-?\n"

      );

      return TRUE;
      }

   // I Don't recognise the args

   return FALSE;

   }  // BOOL CSysSvc::ParseStandardArgs ( int argc, char * argv[] )

////////////////////////////////////////////////////////////////////////////////////////
// Install/Uninstall Routines

// Test if the Service is Currently Installed

BOOL CSysSvc::IsInstalled()

   {
   BOOL bResult = FALSE;

   // Open the Service Control Manager

   SC_HANDLE hSCM = ::OpenSCManager (

      NULL,                   // Local Machine
      NULL,                   // ServicesActive Database
      SC_MANAGER_ALL_ACCESS   // Full Access

   );

   // Try to Open the Service

   if ( hSCM )
      {

      SC_HANDLE hService = ::OpenService (

         hSCM,
         m_pszServiceName,
         SERVICE_QUERY_CONFIG

      );

      if ( hService )
         {
         bResult = TRUE;

         ::CloseServiceHandle ( hService );
         }

      ::CloseServiceHandle ( hSCM );
      }

   return bResult;

   }  // BOOL CSysSvc::IsInstalled()

BOOL CSysSvc::Install()

   {

   // Open the Service Control Manager

   SC_HANDLE hSCM = ::OpenSCManager (

      NULL,                   // Local Machine
      NULL,                   // ServicesActive Database
      SC_MANAGER_ALL_ACCESS   // Full Access

   );

   if ( ! hSCM )
      return FALSE;

   // Get the Executable File Path

   char szFilePath [ _MAX_PATH ];

   ::GetModuleFileName ( NULL, szFilePath, sizeof ( szFilePath ) );

   // Create the Service

   SC_HANDLE hService = ::CreateService (

      hSCM,                         // SCManager Database
      m_pszServiceName,             // Name of Service
      m_pszServiceName,             // Name to Display
      SERVICE_ALL_ACCESS,           // Desired Access
      SERVICE_WIN32_OWN_PROCESS     // Service Type
   |  SERVICE_INTERACTIVE_PROCESS,  // Interactive Service (Allow Service to Interact with the Desktop)
      SERVICE_DEMAND_START,         // Start Type
      SERVICE_ERROR_NORMAL,         // Error Control Type
      szFilePath,                   // Service's Binary
      NULL,                         // No Load Ordering Group
      NULL,                         // No Tag Identifier
      m_pszDependencies,            // Dependencies
      NULL,                         // Local System Account
      NULL                          // No Password
   );

   if ( ! hService )
      {
      ::CloseServiceHandle ( hSCM );
      return FALSE;
      }

   // Make Registry Entries to Support Logging Messages
   // Add the Source Name as a SubKey Under the Application
   // Key in the EventLog Service Portion of the Registry.

   char  szKey [ 256 ];
   HKEY  hKey = NULL;

   strcpy ( szKey, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\" );
   strcat ( szKey, m_pszServiceName );

   if ( ::RegCreateKey ( HKEY_LOCAL_MACHINE, szKey, &hKey) != ERROR_SUCCESS )

      {
      ::CloseServiceHandle ( hService );
      ::CloseServiceHandle ( hSCM );
      return FALSE;
      }

   // Add the Event ID Message-File Name to the 'EventMessageFile' Subkey.

   ::RegSetValueEx (

      hKey,
      "EventMessageFile",
      0,
      REG_EXPAND_SZ,
      ( CONST BYTE * ) szFilePath,
      strlen ( szFilePath ) + 1

   );

   // Set the Supported Types Flags.

   DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;

   ::RegSetValueEx (

      hKey,
      "TypesSupported",
      0,
      REG_DWORD,
      ( CONST BYTE * ) & dwData,
      sizeof ( DWORD )

   );

   ::RegCloseKey ( hKey );

   LogEvent ( EVENTLOG_INFORMATION_TYPE, EVMSG_INSTALLED, m_pszServiceName );

   // Tidy Up

   ::CloseServiceHandle ( hService );
   ::CloseServiceHandle ( hSCM );

   return TRUE;

   }  // BOOL CSysSvc::Install()

BOOL CSysSvc::Uninstall()

   {

   // Open the Service Control Manager

   SC_HANDLE hSCM = ::OpenSCManager (

      NULL,                   // Local Machine
      NULL,                   // ServicesActive Database
      SC_MANAGER_ALL_ACCESS   // Full Access
   );

   if ( ! hSCM )
      return FALSE;

   BOOL bResult = FALSE;

   SC_HANDLE hService = ::OpenService (

      hSCM,
      m_pszServiceName,
      DELETE

   );

   if ( hService )
      {

      if ( ::DeleteService ( hService ) )
         {
         LogEvent ( EVENTLOG_INFORMATION_TYPE, EVMSG_REMOVED, m_pszServiceName );
         bResult = TRUE;
         }

      else
         {
         LogEvent ( EVENTLOG_ERROR_TYPE, EVMSG_NOTREMOVED, m_pszServiceName );
         }

      ::CloseServiceHandle ( hService);
      }

   ::CloseServiceHandle ( hSCM );

   return bResult;

   }  // BOOL CSysSvc::Uninstall()

///////////////////////////////////////////////////////////////////////////////////////
// Logging Functions

// This Function Makes an Entry Into the Application Event Log

void CSysSvc::LogEvent (   WORD        wType, 
                           DWORD       dwID,

                           const char  * pszS1,
                           const char  * pszS2,
                           const char  * pszS3 )

   {
   const char * ps [ 3 ] = { pszS1, pszS2, pszS3 };

   WORD iStr = 0;

   for ( int i = 0; i < 3; i++ )
      {
      if ( ps[i] != NULL )
         iStr++;
      }

   // Check if the Event Source Has Been Registered and If Not Then Register It Now

   if ( ! m_hEventSource )
      {

      m_hEventSource = ::RegisterEventSource (

         NULL,             // Local Machine
         m_pszServiceName  // Source Name

      );

      }

   if ( m_hEventSource )
      {

      ::ReportEvent (

         m_hEventSource,
         wType,
         0,
         dwID,
         NULL, // SID
         iStr,
         0,
         ps,
         NULL

      );

      }

   }  // void CSysSvc::LogEvent ( WORD wType, DWORD dwID, const char * pszS1, const char * pszS2, const char * pszS3 )

//////////////////////////////////////////////////////////////////////////////////////////////
// Service Startup and Registration

BOOL CSysSvc::StartService()

   {
   SERVICE_TABLE_ENTRY st [] = {

      { ( char * ) m_pszServiceName, ServiceMain },
      { NULL, NULL }

   };

   DebugMsg ( "Calling StartServiceCtrlDispatcher()" );

   BOOL fResult = ::StartServiceCtrlDispatcher ( st );

   DebugMsg ( "Returned from StartServiceCtrlDispatcher()" );

   return fResult;

   }  // BOOL CSysSvc::StartService()

// Static Member Function (Callback)

void CSysSvc::ServiceMain ( DWORD dwArgc, LPTSTR * lpszArgv )

   {
   UNREFERENCED_PARAMETER ( dwArgc );
   UNREFERENCED_PARAMETER ( lpszArgv );

   // Get a Pointer to the C++ CSysSvc Object

   CSysSvc * pService = m_pThis;

   pService->DebugMsg ( "Entering CSysSvc::ServiceMain()" );

   // Register the Control Request Handler

   pService->m_Status.dwCurrentState = SERVICE_START_PENDING;

   pService->m_hServiceStatus = RegisterServiceCtrlHandler (

      pService->m_pszServiceName,
      Handler

   );

   if ( pService->m_hServiceStatus == NULL )
      {
      pService->LogEvent ( EVENTLOG_ERROR_TYPE, EVMSG_CTRLHANDLERNOTINSTALLED );

      return;
      }

   // Start the Initialisation

   if ( pService->Initialize() )
      {

      // Do the Real Work - When the Run Function Returns, the Service Has Stopped.

      pService->m_bIsRunning              = TRUE;

      pService->m_Status.dwWin32ExitCode  = 0;
      pService->m_Status.dwCheckPoint     = 0;
      pService->m_Status.dwWaitHint       = 10000LU;

      pService->Run();
      }

   // Tell the Service Manager We are Stopped

   pService->SetStatus ( SERVICE_STOPPED );

   pService->DebugMsg ( "Leaving CSysSvc::ServiceMain()" );

   }  // void CSysSvc::ServiceMain ( DWORD dwArgc, LPTSTR * lpszArgv )

///////////////////////////////////////////////////////////////////////////////////////////
// Status Functions

void CSysSvc::SetStatus ( DWORD dwState )

   {

   DebugMsg ( "CSysSvc::SetStatus ( %lu, %lu )", m_hServiceStatus, dwState );

   m_Status.dwCurrentState = dwState;

   ::SetServiceStatus ( m_hServiceStatus, &m_Status );

   }  // void CSysSvc::SetStatus ( DWORD dwState )

///////////////////////////////////////////////////////////////////////////////////////////
// Service Initialization

BOOL CSysSvc::Initialize()

   {

   DebugMsg ( "Entering CSysSvc::Initialize()" );

   // Start the Initialization

   SetStatus ( SERVICE_START_PENDING );

   // Perform the Actual Initialization

   BOOL bResult = OnInit();

   // Set the Final State

   m_Status.dwWin32ExitCode   = GetLastError();
   m_Status.dwCheckPoint      = 0;
   m_Status.dwWaitHint        = 10000LU;

   if ( ! bResult )
      {
      LogEvent ( EVENTLOG_ERROR_TYPE, EVMSG_FAILEDINIT );

      SetStatus ( SERVICE_STOPPED );

      return FALSE;
      }

   LogEvent ( EVENTLOG_INFORMATION_TYPE, EVMSG_STARTED );

   SetStatus ( SERVICE_RUNNING );

   DebugMsg ( "Leaving CSysSvc::Initialize()" );

   return TRUE;

   }  // BOOL CSysSvc::Initialize()

///////////////////////////////////////////////////////////////////////////////////////////////
// Main Function to Do the Real Work of the Service

// This function Performs the Main Work of the Service - When this Function Returns the Service has Stopped

void CSysSvc::Run()

   {

   DebugMsg ( "Entering CSysSvc::Run()" );

   // Bump to the Maximum Process Priority

   if ( ! SetPriorityClass ( GetCurrentProcess(), REALTIME_PRIORITY_CLASS ) )
      {
      DebugMsg ("SetPriorityClass() returned %d!\n\a", GetLastError() );
      return;
      }

   while ( m_bIsRunning )

      {
      DebugMsg ( "Sleeping..." );
      Sleep ( 5000 );
      }

   // Nothing More to Do

   DebugMsg ( "Leaving CSysSvc::Run()" );

   }  // void CSysSvc::Run()

//////////////////////////////////////////////////////////////////////////////////////
// Control Request Handlers

// Static Member Function (Callback) to Handle Commands from the Service Control Manager

void CSysSvc::Handler ( DWORD dwOpcode )

   {

   // Get a Pointer to the Object

   CSysSvc * pService = m_pThis;

   pService->DebugMsg ( "CSysSvc::Handler ( %lu )", dwOpcode );

   switch ( dwOpcode )

      {
      case SERVICE_CONTROL_STOP:          // 1

         pService->SetStatus ( SERVICE_STOP_PENDING );
         pService->OnStop();
         pService->m_bIsRunning = FALSE;
         pService->LogEvent ( EVENTLOG_INFORMATION_TYPE, EVMSG_STOPPED );
         break;

      case SERVICE_CONTROL_PAUSE:         // 2

         pService->OnPause();
         break;

      case SERVICE_CONTROL_CONTINUE:      // 3

         pService->OnContinue();
         break;

      case SERVICE_CONTROL_INTERROGATE:   // 4

         pService->OnInterrogate();
         break;

      case SERVICE_CONTROL_SHUTDOWN:      // 5

         pService->SetStatus ( SERVICE_STOP_PENDING );
         pService->OnShutdown();
         pService->m_bIsRunning = FALSE;
         pService->LogEvent ( EVENTLOG_INFORMATION_TYPE, EVMSG_SHUTDOWN );
         break;

      default:

         if ( dwOpcode >= SERVICE_CONTROL_USER )
            {
            if ( ! pService->OnUserControl ( dwOpcode ) )
               {
               pService->LogEvent ( EVENTLOG_ERROR_TYPE, EVMSG_BADREQUEST );
               }
            }

         else
            {
            pService->LogEvent ( EVENTLOG_ERROR_TYPE, EVMSG_BADREQUEST );
            }

         break;

      }

   // Report the Current Status

   pService->DebugMsg (

      "Updating status ( %lu, %lu )",

       pService->m_hServiceStatus,
       pService->m_Status.dwCurrentState

    );

   ::SetServiceStatus ( pService->m_hServiceStatus, &pService->m_Status );

   }  // void CSysSvc::Handler ( DWORD dwOpcode )

// Called When the Service is First Initialized

BOOL CSysSvc::OnInit()

   {

   DebugMsg ( "CSysSvc::OnInit()" );

   return TRUE;

   }  // BOOL CSysSvc::OnInit()

// Called When the Service Control Manager Wants to Stop the Service

void CSysSvc::OnStop()

   {

   DebugMsg ( "CSysSvc::OnStop()" );

   }  // void CSysSvc::OnStop()

// Called When the Service is Interrogated

void CSysSvc::OnInterrogate()

   {

   DebugMsg ( "CSysSvc::OnInterrogate()" );

   }  // void CSysSvc::OnInterrogate()

// Called When the Service is Paused

void CSysSvc::OnPause()

   {

   DebugMsg ( "CSysSvc::OnPause()" );

   }  // void CSysSvc::OnPause()

// Called When the Service is Continued

void CSysSvc::OnContinue()

   {

   DebugMsg ( "CSysSvc::OnContinue()" );

   }  // void CSysSvc::OnContinue()

// Called When the Service is Shut Down

void CSysSvc::OnShutdown()

   {

   DebugMsg ( "CSysSvc::OnShutdown()" );

   }  // void CSysSvc::OnShutdown()

// Called When the Service Gets a User Control Message

BOOL CSysSvc::OnUserControl ( DWORD dwOpcode )

   {

   DebugMsg ( "CSysSvc::OnUserControl ( %8.8lXH )", dwOpcode );

   return FALSE;  // Say Not Handled

   }  // BOOL CSysSvc::OnUserControl ( DWORD dwOpcode )

////////////////////////////////////////////////////////////////////////////////////////////
// Debugging Support

void CSysSvc::DebugMsg ( const char * pszFormat, ... )

   {

#ifndef _DEBUG

   UNREFERENCED_PARAMETER ( pszFormat );

#else // _DEBUG

#ifndef VERBOSE

   return;

#endif   // VERBOSE

   char buf [ 1024 ];

   sprintf ( buf, "[%s](%8.8x):  ", m_pszServiceName, GetCurrentThreadId() );

   va_list arglist;

   va_start ( arglist, pszFormat );

   vsprintf ( &buf [ strlen ( buf ) ], pszFormat, arglist );

   va_end ( arglist );

   strcat ( buf, "\n" );

   OutputDebugString ( buf );

   WriteCon ( "\r%s", buf );

#endif   // _DEBUG

   }  // void CSysSvc::DebugMsg ( const char * pszFormat, ... )

TrySvc.cpp

/*********************************************************************************************
* Copyright (c) 2008 - 2010 David Cass Tyler, PO Box 1026, Willard, NM 87063, (505) 384-5342 *
*********************************************************************************************/

#include "StdAfx.h"                    // The Standard MFC Classes, Prototypes and Defines

#include "WriteCon.h"                  // WriteCon()

#include "TrySvc.h"                    // The CSysSvc Derived CTrySystemService Class

/**************************************************************************************
* Detect and Honor all of the Normal Shutdown Events, Including Ctrl+C and Ctrl+Break *
**************************************************************************************/

BOOL fShutDownRequested = FALSE;

static BOOL WINAPI ControlHandler ( DWORD dwCtrlType )

   {

   switch ( dwCtrlType )
      {

      // Shutdown on Ctrl+C, Ctrl+Break, X Button (upper right hand corner) and System Shutdown

      case CTRL_C_EVENT:               /* Ctrl+C from Keyboard             */
      case CTRL_BREAK_EVENT:           /* Ctrl+Break from Keyboard         */
      case CTRL_CLOSE_EVENT:           /* Close from System Menu or Button */
      case CTRL_SHUTDOWN_EVENT:        /* System Shutdown Event            */

         fShutDownRequested = TRUE;    /* Set Shutdown Requested           */

         return TRUE;

      case CTRL_LOGOFF_EVENT:          /* User Logoff Event                */

         return FALSE;

      default:

         WriteCon ( "ControlHandler() Received %lu\n", dwCtrlType );

         return FALSE;
      }

   }  /* static BOOL WINAPI ControlHandler ( DWORD dwCtrlType ) */

//*********************************************************
// The CTrySystemService Class Implementation Starts Here *
//*********************************************************

// The CTrySystemService Constructor

CTrySystemService::CTrySystemService() : CSysSvc ( "TrySvc" )

   {

   DebugMsg ( "CTrySystemService::CTrySystemService()" );

   }  // CTrySystemService()

// The CTrySystemService Destructor

CTrySystemService::~CTrySystemService()

   {

   DebugMsg ( "CTrySystemService::~CTrySystemService()" );

   }  // ~CTrySystemService()

// The CTrySystemService Execution Thread

void CTrySystemService::Run()

   {
   unsigned long  ulPass   = 0;
   char           szShow[] = "|/-\\";

   // Allow Terminate on Ctrl+C and Ctrl+Break

   SetConsoleCtrlHandler ( ControlHandler, TRUE );

   // Announce That We've Come Online

   WriteCon ( "TrySvc Online!\n" );

   // Do Normal Processing Here

   while ( m_bIsRunning && ! fShutDownRequested )
      {
      WriteCon ( "\r%c", szShow [ ( ulPass++ ) % ( sizeof szShow - 1 ) ] );
      Sleep ( 100LU );
      }

   // Announce That We've Gone Offline

   WriteCon ( "\rTrySvc Offline!\n" );

   }  // Run()

WriteCon.cpp

/***************************************************************************************
* Copyright (c) 2002 - 2010 Cass Tyler, PO Box 1026, Willard, NM 87063, (505) 384-5342 *
***************************************************************************************/

#include "StdAfx.h"

#include <winbase.h>                   // SwitchToThread()

#include <IO.H>                        // _open_osfhandle()
#include <FCNTL.H>                     // _O_TEXT
#include <conio.h>                     // _cprintf()
#include <stdio.h>                     // fprintf(), _vsnprintf(), stderr

#include "WriteCon.h"                  // The WriteCon() Prototype

static volatile LONG lSpinCount  = 0L; // The SpinLock

// Write a Message to the System Console.  This function is Thread Synchronized
// by the lSpinCount SpinLock to prevent interleaved messages.  Usinge _vsnprintf()
// to encode the message to a buffer allows the possibility of expanding this function to
// duplicate the message to a log file in the future.  A maximum of 8K - 1 Bytes can be
// output with a single write.

extern "C" int WriteCon ( const char szFormat[], ... )

   {
   char     szBuffer [ 1024 * 8 ]   = { 0 };

   va_list  pArgPtr                 = NULL;
   size_t   cbBuffer                = sizeof szBuffer;
   int      nBytes                  = 0;

   // Use the Format and Argument List to Encode the Buffer

   va_start ( pArgPtr, szFormat );
   nBytes = _vsnprintf ( szBuffer, cbBuffer - 1, szFormat, pArgPtr );
   va_end   ( pArgPtr );

   if ( nBytes )
      {

      // Wait for Critical Section

      while ( InterlockedIncrement ( ( LPLONG ) &lSpinCount ) != 1 )
         {
         InterlockedDecrement ( ( LPLONG ) &lSpinCount );
         SwitchToThread();             // #include <winbase.h> and #define _WIN32_WINNT 0x0400 in StdAfx.h
      // Sleep ( 0LU );                // Use SwitchToThread() if Available
         }

      // Critical Section Begins

      __try
         {
         __try
            {

            // Output the Buffer to the Console

            _cprintf ( szBuffer );
            }
         __finally
            {
            InterlockedDecrement ( ( LPLONG ) &lSpinCount );
            }
         }
      __except ( EXCEPTION_EXECUTE_HANDLER )
         {
         DWORD dwException = GetExceptionCode();
#ifdef __AFX_H__  // Only Include in the AFX Version
         TRACE ( "WriteCon():  An Exception ( 0x%lX ) Occurred!\n", dwException );
#else    // __AFX_H__
         fprintf ( stderr, "WriteCon():  An Exception ( 0x%lX ) Occurred!\n", dwException );
#endif   // __AFX_H__         return 0;
         }
      }

   return nBytes;

   }  // extern int WriteCon ( const char szFormat[], ... )

/******************************************************
*  Allocate a Console and Redirect our I/O to it.     *
*  see:  http://www.halcyon.com/~ast/dload/guicon.htm *
******************************************************/

void RedirectIoToConsole ( LPCTSTR lpConsoleTitle )

   {
   static const WORD          wMaxConsoleLines           = 25U;
   int                        hConsoleHandle             = 0;
   long                       lnStdHandle                = 0L;
   CONSOLE_SCREEN_BUFFER_INFO stConsoleScreenBufferInfo  = { 0LU };
   FILE                       * pFile                    = NULL;

   // Allocate a Console for This App

   AllocConsole();

   // Set the Console Title

   if ( lpConsoleTitle )
      SetConsoleTitle ( lpConsoleTitle );

   // Set the Screen Buffer to the Normal 25 Lines

   GetConsoleScreenBufferInfo ( GetStdHandle ( STD_OUTPUT_HANDLE ), &stConsoleScreenBufferInfo );

   stConsoleScreenBufferInfo.dwSize.Y = wMaxConsoleLines;

   SetConsoleScreenBufferSize ( GetStdHandle ( STD_OUTPUT_HANDLE ), stConsoleScreenBufferInfo.dwSize );

   // Set the Text Color

// SetConsoleTextAttribute ( GetStdHandle ( STD_OUTPUT_HANDLE ), FOREGROUND_GREEN | FOREGROUND_INTENSITY );

   // Redirect Unbuffered STDOUT to the Console

   lnStdHandle = ( long ) GetStdHandle ( STD_OUTPUT_HANDLE );

   hConsoleHandle = _open_osfhandle ( lnStdHandle, _O_TEXT );

   pFile = _fdopen ( hConsoleHandle, "w" );

   * stdout = * pFile;

   setvbuf ( stdout, NULL, _IONBF, 0 );

   // Redirect Unbuffered STDIN to the Console

   lnStdHandle = ( long ) GetStdHandle ( STD_INPUT_HANDLE );

   hConsoleHandle = _open_osfhandle ( lnStdHandle, _O_TEXT );

   pFile = _fdopen ( hConsoleHandle, "r" );

   * stdin = * pFile;

   setvbuf ( stdin, NULL, _IONBF, 0 );

   // Redirect Unbuffered STDERR to the Console

   lnStdHandle = ( long ) GetStdHandle ( STD_ERROR_HANDLE );

   hConsoleHandle = _open_osfhandle ( lnStdHandle, _O_TEXT );

   pFile = _fdopen ( hConsoleHandle, "w" );

   * stderr = * pFile;

   setvbuf ( stderr, NULL, _IONBF, 0 );

   }  // void RedirectIoToConsole ( LPCTSTR lpConsoleTitle )

#ifdef __AFX_H__  // Only Include in the AFX Version

CString FormattedString ( const char szFormat[], ... )

   {
   char     szBuffer [ 256 ]  = { 0 };

   va_list  pArgPtr              = NULL;
   size_t   cbBuffer             = sizeof szBuffer;
   int      nBytes               = 0;

   // Use the Format and Argument List to Encode the Buffer

   va_start ( pArgPtr, szFormat );
   nBytes = _vsnprintf ( szBuffer, cbBuffer - 1, szFormat, pArgPtr );
   va_end   ( pArgPtr );

   return CString ( szBuffer );

   }  // CString FormattedString ( const char szFormat[], ... )

#endif   // __AFX_H__

main.cpp

/*********************************************************************************************
* Copyright (c) 2008 - 2010 David Cass Tyler, PO Box 1026, Willard, NM 87063, (505) 384-5342 *
*********************************************************************************************/

#include "StdAfx.h"

#include "TrySvc.h"  // The Try System Service Class

int main ( int argc, char * argv [], char * /* envp */ [] )

   {

   // This Will Allocate a Console if Run as a System Service - Must Happen Before Creating the Try System Service

   if ( argc == 1 )
      AllocConsole();

   // Create the Try System Service

   CTrySystemService TrySystemService;

   // Parse for standard arguments ( install, uninstall, version etc. )

   if ( ! TrySystemService.ParseStandardArgs ( argc, argv ) )
      TrySystemService.StartService(); // Didn't Find Standard args so Start the System Service

   // When we get here, the service has been stopped

   return TrySystemService.m_Status.dwWin32ExitCode;

   }  // int main ( int argc, char * argv [], char * envp [] )