/*-------------------------------------------------------------------------
 * fedsrv\AllSrvModule.CPP
 * 
 * This part of AllSrv contains Service-related functions and COM/ATL support.  
 * 
 * Owner: 
 * 
 * Copyright 1986-1998 Microsoft Corporation, All Rights Reserved
 *-----------------------------------------------------------------------*/



#include "pch.h"
#include "AllSrvModuleIDL_i.c"
#include <AGC.h>
#include <..\TCAtl\ObjectMap.h>
#include <..\TCLib\CoInit.h>

#if !defined(ALLSRV_STANDALONE)
  #include "AdminEventLoggerHook.h"
  CComObjectGlobal<CAdminEventLoggerHook> g_DBLoggingHook;
#endif // !defined(ALLSRV_STANDALONE)

#ifdef _DEBUG
  #include "FedSrvApp.h"
  #include <AGCDebugHook.h>
  CComObjectGlobal<CAGCDebugHook> g_DebugHook;
#endif // _DEBUG

// {E1A86098-DD81-11d2-8B45-00C04F681633}
static const CATID CATID_AllegianceAdmin = 
  {0xe1a86098,0xdd81,0x11d2,{0x8b,0x45,0x0,0xc0,0x4f,0x68,0x16,0x33}};

// The following two APP IDs must be the same
static const GUID APPID_AllSrv =
  {0xE4E8767E,0xDFDB,0x11d2,{0x8B,0x46,0x00,0xC0,0x4F,0x68,0x16,0x33}};

static const char *c_szAPPID_AllSrv = "{E4E8767E-DFDB-11d2-8B46-00C04F681633}";


/////////////////////////////////////////////////////////////////////////////
// Global Initialization

CAGCModule _AGCModule;
CServiceModule _Module;


/////////////////////////////////////////////////////////////////////////////
// External Declarations

extern Global g;


TC_OBJECT_EXTERN(CAdminSession)
TC_OBJECT_EXTERN_NON_CREATEABLE(CAdminGame)
TC_OBJECT_EXTERN_NON_CREATEABLE(CAdminGames)
TC_OBJECT_EXTERN_NON_CREATEABLE(CAdminServer)
TC_OBJECT_EXTERN_NON_CREATEABLE(CAdminUser)
TC_OBJECT_EXTERN_NON_CREATEABLE(CAdminUsers)
TC_OBJECT_EXTERN_NON_CREATEABLE(CAdminShip)
// End: External Declarations


/////////////////////////////////////////////////////////////////////////////
// Object Map

BEGIN_OBJECT_MAP(ObjectMap)
  TC_OBJECT_ENTRY_STD(AdminSession)
  TC_OBJECT_ENTRY_STD_NON_CREATEABLE(AdminGame)
  TC_OBJECT_ENTRY_STD_NON_CREATEABLE(AdminGames)
  TC_OBJECT_ENTRY_STD_NON_CREATEABLE(AdminServer)
  TC_OBJECT_ENTRY_STD_NON_CREATEABLE(AdminUser)
  TC_OBJECT_ENTRY_STD_NON_CREATEABLE(AdminUsers)
  TC_OBJECT_ENTRY_STD_NON_CREATEABLE(AdminShip)
END_OBJECT_MAP()




#ifdef SHOW_SPONSOR_INFO
  int cSponsor = 0;    // for debugging in AdminUtil.h
  int cLimb = 0;
  CDebugClass DebugClass;
#endif



/*-------------------------------------------------------------------------
 * PrintSystemErrorMessage()
 *-------------------------------------------------------------------------
 * Paramters:
 *    dwErrorCode: take a dwErrorCode and print what it means as text    
 * 
 */
void PrintSystemErrorMessage(LPCTSTR szText, DWORD dwErrorCode)
{
  LPVOID lpMsgBuf;

  FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | 
                FORMAT_MESSAGE_ALLOCATE_BUFFER | 
                FORMAT_MESSAGE_IGNORE_INSERTS, 
                NULL, 
                dwErrorCode, 
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
                (LPTSTR) &lpMsgBuf,
                0,
                NULL 
                );

  printf("%s", (LPCTSTR)szText);
  printf("%s", (LPCTSTR)lpMsgBuf);

  _AGCModule.TriggerEvent(NULL, AllsrvEventID_SystemError, "", -1, -1, -1, 2,
      "Text", VT_LPSTR, szText,
      "Message", VT_LPSTR, lpMsgBuf);

  LocalFree( lpMsgBuf );
}


                                                 


/*-------------------------------------------------------------------------
 * IsInstalled()
 *-------------------------------------------------------------------------
 * Returns:
 *    TRUE iff AllSrv was properly installed (either as Service or EXE)
 * 
 */
BOOL CServiceModule::IsInstalled()
{
    // Are we Service or Local Server
    CRegKey keyAppID;
    LONG lRes = keyAppID.Open(HKEY_CLASSES_ROOT, _T("AppID"), KEY_READ);
    if (lRes != ERROR_SUCCESS)
        return FALSE;

    CRegKey key;
    lRes = key.Open(keyAppID, c_szAPPID_AllSrv, KEY_READ);
    if (lRes != ERROR_SUCCESS)
        return FALSE;

    return TRUE;
}


/*-------------------------------------------------------------------------
 * IsInstalledAsService()
 *-------------------------------------------------------------------------
 * Returns:
 *    TRUE iff AllSrv is installed as an NT service.
 * 
 */
BOOL CServiceModule::IsInstalledAsService()
{
    if (IsWin9x())
      return false;

    // Are we Service or Local Server
    CRegKey keyAppID;
    LONG lRes = keyAppID.Open(HKEY_CLASSES_ROOT, _T("AppID"), KEY_READ);
    if (lRes != ERROR_SUCCESS)
        return FALSE;

    CRegKey key;
    lRes = key.Open(keyAppID, c_szAPPID_AllSrv, KEY_READ);
    if (lRes != ERROR_SUCCESS)
        return FALSE;

    TCHAR szValue[_MAX_PATH];
    DWORD dwLen = _MAX_PATH;
	// mdvalley: QueryStringValue isn't in my ATL, so edited slightly
    lRes = key.QueryValue(szValue, _T("LocalService"), &dwLen);

    if (lRes == ERROR_SUCCESS)
        return TRUE;

    return FALSE;
}

/*-------------------------------------------------------------------------
 * IsInServiceControlManager()
 *-------------------------------------------------------------------------
 * Returns:
 *    TRUE iff AllSrv is an NT service (by checking the Service Control 
 *    Manager).
 * 
 */
BOOL CServiceModule::IsInServiceControlManager()
{
    if (IsWin9x())
      return false;

    BOOL bResult = FALSE;

    SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, GENERIC_READ);

    if (hSCM != NULL)
    {
        SC_HANDLE hService = ::OpenService(hSCM, c_szSvcName, SERVICE_QUERY_CONFIG);
        if (hService != NULL)
        {
            bResult = TRUE;
            ::CloseServiceHandle(hService);
        }
        ::CloseServiceHandle(hSCM);
    }
    return bResult;
}



/*-------------------------------------------------------------------------
 * CServiceModule::Init
 *-------------------------------------------------------------------------
 * Purpose:
 *    Init some COM/ATL/AGC stuff
 * 
 */
HRESULT CServiceModule::Init(HINSTANCE hInst)
{
  // Initialize the ATL module
  HRESULT hr;
  ZSucceeded(hr = CComModule::Init(ObjectMap, hInst, &LIBID_ALLEGIANCESERVERLib));
  RETURN_FAILED(hr);

  // Indicate success
  return S_OK;
}


/*-------------------------------------------------------------------------
 * CServiceModule::Term
 *-------------------------------------------------------------------------
 * Purpose:
 *    Terminate some COM/ATL/AGC stuff
 * 
 */
void CServiceModule::Term()
{
  CComModule::Term();
}


HRESULT CServiceModule::InitAGC()
{
  // Initialize AGC
  HRESULT hr;
  ZSucceeded(hr = _AGCModule.Init());
  RETURN_FAILED(hr);

  _AGCModule.SetDebugBreakOnErrors(g.fWantInt3);

  #ifdef _DEBUG
    _AGCModule.GetAGCGlobal()->SetDebugHook(&g_DebugHook);
  #endif // _DEBUG

  // Create the set of available AGCEventID ranges
  CComPtr<IAGCEventIDRanges> spRanges;
  ZSucceeded(hr = spRanges.CoCreateInstance(L"AGC.EventIDRanges"));
  RETURN_FAILED(hr);

  // Add our ranges to it
  ZSucceeded(spRanges->AddByValues(EventID_AGC_LowerBound, EventID_AGC_UpperBound));
  ZSucceeded(spRanges->AddByValues(AllsrvEventID_Allsrv_LowerBound, AllsrvEventID_Allsrv_UpperBound));
  ZSucceeded(spRanges->AddByValues(EventID_Admin_LowerBound, EventID_Admin_UpperBound));

  // Set the ranges of available events
  GetAGCGlobal()->SetAvailableEventIDRanges(spRanges);

  // Create the event logger object
  ZSucceeded(hr = m_spEventLogger.CreateInstance("AGC.EventLogger"));
  RETURN_FAILED(hr);

  // Initialize the event logger object
  CComBSTR bstrEventSource(__MODULE__);
  CComBSTR bstrRegKey("HKLM\\" HKLM_FedSrv);
  IAGCEventLoggerPrivatePtr spPrivate(m_spEventLogger);
  ZSucceeded(hr = spPrivate->Initialize(bstrEventSource, bstrRegKey));
  RETURN_FAILED(hr);

  // Hook the event logger for DB event logging
  #if !defined(ALLSRV_STANDALONE)
  {
    ZSucceeded(hr = spPrivate->put_HookForDBLogging(&g_DBLoggingHook));
  }
  #endif // !defined(ALLSRV_STANDALONE)

  // Indicate success
  return S_OK;
}

void CServiceModule::TermAGC()
{
  // Shutdown the event logger object
  if (NULL != m_spEventLogger)
  {
    IAGCEventLoggerPrivatePtr spPrivate(m_spEventLogger);
    assert(NULL != spPrivate);

    // Terminate the event logger
    ZSucceeded(spPrivate->Terminate());

    // Unhook the event logger for DB event logging
    #if !defined(ALLSRV_STANDALONE)
    {
      ZSucceeded(spPrivate->put_HookForDBLogging(NULL));
    }
    #endif // !defined(ALLSRV_STANDALONE)

    // Release the event logger
    spPrivate = NULL;
    m_spEventLogger = NULL;
  }

  // Terminate AGC
  _AGCModule.Term();
}


/*-------------------------------------------------------------------------
 * CServiceModule::get_EventLog
 *-------------------------------------------------------------------------
 * Purpose:
 *    Gets the previously created event logger object.
 * 
 */
HRESULT CServiceModule::get_EventLog(IAGCEventLogger** ppEventLogger)
{
  CLEAROUT(ppEventLogger, (IAGCEventLogger*)NULL);
  (*ppEventLogger = m_spEventLogger)->AddRef();
  return S_OK;
}



/*-------------------------------------------------------------------------
 * RegisterCOMObjects
 *-------------------------------------------------------------------------
 * Purpose:
 *    Let COM know about our class objects 
 * 
 */
void CServiceModule::RegisterCOMObjects() 
{
  // Make sure we haven't already been here
  assert(m_shevtMTAReady.IsNull());
  assert(m_shevtMTAExit.IsNull());
  assert(m_shthMTA.IsNull());

  // Create thread synchronization objects
  m_shevtMTAReady = CreateEvent(NULL, false, false, NULL);
  m_shevtMTAExit  = CreateEvent(NULL, false, false, NULL);
  assert(!m_shevtMTAReady.IsNull());
  assert(!m_shevtMTAExit.IsNull());

  // Create the thread to keep the MTA alive and register the class objects
  DWORD dwID = 0;
  m_shthMTA = CreateThread(NULL, 8192, MTAKeepAliveThunk, this, 0, &dwID);
  assert(!m_shthMTA.IsNull());

  // Wait for the new thread to do its initialization work
  WaitForSingleObject(m_shevtMTAReady, INFINITE);

  // Close the 'Ready' event since we're done with it
  m_shevtMTAReady = NULL;

  // Check for failure
  if (FAILED(m_hrMTAKeepAlive))
    PrintSystemErrorMessage("Failed to register COM class objects for Admin Session.", m_hrMTAKeepAlive);

  ZSucceeded(m_hrMTAKeepAlive);
}


/*-------------------------------------------------------------------------
 * RevokeCOMObjects
 *-------------------------------------------------------------------------
 * Purpose:
 *    Let COM 'un-know' about our class objects.
 * 
 */
void CServiceModule::RevokeCOMObjects() 
{
  // Make sure that we have registered correctly
  assert(m_shevtMTAReady.IsNull());
  assert(!m_shevtMTAExit.IsNull());
  assert(!m_shthMTA.IsNull());
  assert(WAIT_TIMEOUT == WaitForSingleObject(m_shthMTA, 0));

  // Signal the MTA keep-alive thread to exit
  SetEvent(m_shevtMTAExit);

  // Wait for the MTA keep-alive thread to exit
  HANDLE hth = m_shthMTA;
  while (WAIT_OBJECT_0 != MsgWaitForMultipleObjects(1, &hth, false, INFINITE, QS_ALLINPUT))
  {
    MSG msg;
    while (PeekMessage(&msg, NULL, 0, 0, true))
      DispatchMessage(&msg);
  }

  // Clear the thread handle
  m_shthMTA = NULL;

  // Close the 'Exit' event since we're done with it
  m_shevtMTAExit = NULL;
}


#ifdef MONOLITHIC_DPLAY
  void RegisterMonolithicDPlay()
  {
    HKEY hKey = NULL;
    const char * szKey = "CLSID\\{DA9CABC6-C724-4265-A61D-6E78EB2042B4}\\InprocServer32";
    // check to see if it's already registered, and if so, do nothing, otherwise register it
    if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_CLASSES_ROOT, szKey, 0, KEY_READ, &hKey))
    {
      // Get the module path
      TCHAR szFilePath[_MAX_PATH * 2], szDrive[_MAX_DRIVE], szDir[_MAX_DIR * 2];
      ::GetModuleFileName(NULL, szFilePath, sizeofArray(szFilePath));
      _tsplitpath(szFilePath, szDrive, szDir, NULL, NULL);
      _tmakepath(szFilePath, szDrive, szDir, NULL, NULL);
      int cch = _tcslen(szFilePath);
      if (TEXT('\\') == szFilePath[cch - 1])
        szFilePath[cch - 1] = TEXT('\0');

      // Update the registry from the DPMono.rgs
      USES_CONVERSION;
      _ATL_REGMAP_ENTRY regmap[] =
      {
        {L"MODULE_PATH", T2COLE(szFilePath)},
        {NULL          ,               NULL},
      };
      _Module.UpdateRegistryFromResource(IDR_DPMONO, true, regmap);
    }
    else
    {
      RegCloseKey(hKey);
    }
  }
#endif


/*-------------------------------------------------------------------------
 * RegisterServer
 *-------------------------------------------------------------------------
 * Purpose:
 *    Put module into the registry.
 *
 * Paramters: 
 *    bReRegister:  are we registering after an autoupdate?
 *    bRegTypeLib:  should we register the COM Type Lib?
 *    bService:     if TRUE, then module is placed in the service control manager
 *    argc, argv:   arguments passed via command-line (used by Install Service)
 */

HRESULT CServiceModule::RegisterServer(BOOL bReRegister, BOOL bRegTypeLib, BOOL bService, int argc, char * argv[])
{
    // Enter the COM MTA
    TCCoInit init(COINIT_MULTITHREADED);
    if (init.Failed())
      return init;

    // Initialize the ATL module
    HRESULT hr = _Module.Init(g.hInst); // init COM and/or NT service stuff
    ZSucceeded(hr);

    if (IsWin9x())
      bService = false; // Windows 9x doesn't support services

    // Remove any previous service since it may point to
    // the incorrect file
    if (IsInServiceControlManager() && !bReRegister)
      RemoveService();

    // Add service entries
    _Module.UpdateRegistryFromResource(IDR_AllSrv, true,
      _AGCModule.GetRegMapEntries());

    #ifdef MONOLITHIC_DPLAY
      // Register custom dplay bits
      RegisterMonolithicDPlay();
    #endif

    // Create the component category manager
    CComPtr<ICatRegister> spCatReg;
    hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_ALL,
      IID_ICatRegister, (void**)&spCatReg);
    ZSucceeded(hr);

    if (SUCCEEDED(hr))
    {
      // Determine the LCID for US English
      const LANGID langid = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
      const LCID lcid = MAKELCID(langid, SORT_DEFAULT);

      // Register the component category
      CATEGORYINFO catinfo;
      catinfo.catid = CATID_AllegianceAdmin;
      catinfo.lcid = lcid;
      wcscpy(catinfo.szDescription, L"Allegiance Admin Objects");
      hr = spCatReg->RegisterCategories(1, &catinfo);
      ZSucceeded(hr);

      // Explicitly release the smart pointer
      spCatReg = NULL;
    }

    if(!bReRegister)
    {
      // Adjust the AppID for Local Server or Service
      CRegKey keyAppID;

      DWORD lRes = keyAppID.Open(HKEY_CLASSES_ROOT, _T("AppID"), KEY_WRITE);
      if (lRes != ERROR_SUCCESS)
          return lRes;

      CRegKey key;
      lRes = key.Open(keyAppID, _T("{E4E8767E-DFDB-11d2-8B46-00C04F681633}"), KEY_WRITE);
      if (lRes != ERROR_SUCCESS)
          return lRes;

      key.DeleteValue(_T("LocalService"));
    
      if (bService)
      {
		  // mdvalley: I hate my ATL libraries sometimes (SetStringValue)
          key.SetValue(__MODULE__, _T("LocalService"));
          key.SetValue(_T("-Service"), _T("ServiceParameters"));
          // Create service
          InstallService(argc, argv);
      }
    }

    // Add object entries
    hr = _Module.CComModule::RegisterServer(bRegTypeLib);

    // Terminate the ATL module
    _Module.Term();
    return hr;
}

/*-------------------------------------------------------------------------
 * UnregisterServer
 *-------------------------------------------------------------------------
 * Purpose:
 *    remove module from registry and remove service
 *    from service control manager.
 *
 */

HRESULT CServiceModule::UnregisterServer()
{
    // Enter the COM MTA
    TCCoInit init(COINIT_MULTITHREADED);
    if (init.Failed())
      return init;

    // Initialize the ATL module
    HRESULT hr = _Module.Init(g.hInst); // init COM and/or NT service stuff
    ZSucceeded(hr);

    // Create the component category manager
    CComPtr<ICatRegister> spCatReg;
    hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_ALL,
      IID_ICatRegister, (void**)&spCatReg);
    ZSucceeded(hr);

    if (SUCCEEDED(hr))
    {
      // Unregister our component category
      CATID catid = CATID_AllegianceAdmin;
      spCatReg->UnRegisterCategories(1, &catid);
      spCatReg = NULL;
    }

    // Remove service entries
    UpdateRegistryFromResource(IDR_AllSrv, FALSE);
    // uninstall service
    if (IsInServiceControlManager())
    {
      RemoveService();
    }
    // Remove object entries
    CComModule::UnregisterServer(TRUE);

    // Terminate the ATL module
    _Module.Term();
    return hr;
}



////////////////////////////////////////////////////////////////////////
// InstallService - installs this service into the NT service manager
//
//

BOOL CServiceModule::InstallService(int argc, char * argv[])
{
    SC_HANDLE schMgr;
    SC_HANDLE schSvc;
    char szPath[512];
    char * szUserName;
    char * szPassword;


    schMgr = OpenSCManager(NULL,NULL,SC_MANAGER_CREATE_SERVICE);

    if (!schMgr)
    {
        printf("Unable to open SCManager.  Service not installed.\n");
        return FALSE;
    }

    GetModuleFileName(NULL,szPath,sizeof(szPath));

    if (argc > 3)
    {
        szUserName = argv[2];
        szPassword = argv[3];
    } else
    {
        szUserName = NULL;
        szPassword = NULL;
    }

    schSvc = CreateService(schMgr,
                           c_szSvcName,
                           "MS Allegiance Game Server",
                           SERVICE_ALL_ACCESS,
                           SERVICE_WIN32_OWN_PROCESS,
                           SERVICE_AUTO_START,
                           SERVICE_ERROR_NORMAL,
                           szPath,
                           NULL,
                           NULL,
                           _T("RPCSS\0"),
                           szUserName,
                           szPassword);

    if (!schSvc)
    {
      char szBuf[MAX_PATH];
      DWORD dwErrorCode(GetLastError());
      sprintf(szBuf, "Unable to create service [0x%08x].  Service not installed.\n", dwErrorCode);
      PrintSystemErrorMessage(szBuf, dwErrorCode);

      CloseServiceHandle(schMgr);
      return FALSE;
    }

    CloseServiceHandle(schSvc);
    CloseServiceHandle(schMgr);
    printf("%s service installed.\n", c_szSvcName);

    if (szUserName)
    {
        TCUserAccount acct;
        acct.Init(szUserName);  // example: szUserName == Redmond\federat which was passed in on the cmdline
        if (S_OK != acct.HasRight(SE_SERVICE_LOGON_NAME))
        {
          acct.SetRight(SE_SERVICE_LOGON_NAME);
          printf("The account %ls\\%ls has been granted the Logon As A Service right.", acct.GetDomainNameW(), acct.GetUserNameW());
        }
    }

    return TRUE;
}

////////////////////////////////////////////////////////////////////////
// RemoveService - uninstalls this service from the NT service manager
//
//

BOOL CServiceModule::RemoveService(void)
{
    SC_HANDLE schMgr;
    SC_HANDLE schSvc;
    SERVICE_STATUS ss;

    schMgr = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);

    if (!schMgr)
    {
        printf("Unable to open SCManager.  Service not removed.\n");
        return FALSE;
    }

    schSvc = OpenService(schMgr, c_szSvcName, SERVICE_ALL_ACCESS);

    if (!schSvc)
    {
        printf("Unable to open %s service.  Service not removed.\n", c_szSvcName);
        CloseServiceHandle(schMgr);
        return FALSE;
    }

    QueryServiceStatus(schSvc,&ss);

    if (ss.dwCurrentState!=SERVICE_STOPPED)
    {
        printf("Unable to remove %s service while it is running.\n", c_szSvcName);
        CloseServiceHandle(schSvc);
        CloseServiceHandle(schMgr);
        return FALSE;
    }

    if (DeleteService(schSvc))
      printf("%s removed (as an NT Service).\n", c_szSvcName);
    else
    {
      char szBuf[MAX_PATH];
      sprintf(szBuf, "Unable to delete %s service.\n", c_szSvcName);

      DWORD dwErrorCode(GetLastError());
      PrintSystemErrorMessage(szBuf, dwErrorCode);
    }

    CloseServiceHandle(schSvc);
    CloseServiceHandle(schMgr);

    return TRUE;
}


// for when running as EXE
BOOL WINAPI HandlerRoutine(
  DWORD dwCtrlType   //  control signal type
)
{
    if (dwCtrlType == CTRL_CLOSE_EVENT)
    {
        SetEvent(g.hKillReceiveEvent);
        return TRUE;
    }

    return FALSE;
}

void RunAsWindow()
{
  // Create an invisible window
  HWND hwnd = ::CreateWindow("static", "AllSrv", WS_OVERLAPPED,
    0, 0, 0, 0, NULL, NULL, ::GetModuleHandle(NULL), NULL);

  // Load the small application icon
  HICON hIcon = reinterpret_cast<HICON>(::LoadImage(::GetModuleHandle(NULL),
    MAKEINTRESOURCE(ICO_FEDSRV), IMAGE_ICON, ::GetSystemMetrics(SM_CXSMICON),
    ::GetSystemMetrics(SM_CYSMICON), 0));

  // Set the taskbar icon
  const UINT wm_ShellNotifyIcon = WM_APP;
  NOTIFYICONDATA nid = {sizeof(nid), hwnd, 0, NIF_ICON | NIF_MESSAGE | NIF_TIP,
    wm_ShellNotifyIcon, hIcon, "Allegiance Server"};
  Shell_NotifyIcon(NIM_ADD, &nid);

  // Enter a message loop
  while (true)
  {
    DWORD dwWait = ::MsgWaitForMultipleObjects(1, &g.hKillReceiveEvent,
      false, INFINITE, QS_ALLINPUT);
    if (WAIT_OBJECT_0 == dwWait)
      break;

    MSG msg;
    while (::PeekMessage(&msg, NULL, 0, 0, true))
    {
      ::TranslateMessage(&msg);
      switch (msg.message)
      {
        case WM_QUIT:
          ::SetEvent(g.hKillReceiveEvent);
          break;
        default:
          ::DispatchMessage(&msg);
      }
    }
  }

  // Remove the taskbar icon
  Shell_NotifyIcon(NIM_DELETE, &nid);

  // Shutdown the application
  FedSrv_Terminate();
}


////////////////////////////////////////////////////////////////////////
// RunAsExecutable - for debugging, this service can be run from the
//                   command-line.
//
//

VOID CServiceModule::RunAsExecutable()
{
    HRESULT hr;

    ZVersionInfo vi;
    printf("%s\n%s\n\n",
      (LPCSTR)vi.GetFileDescription(), (LPCSTR)vi.GetLegalCopyright());

    if (IsWinNT())
      printf("Running as an executable.\n");

    printf("Initializing...\n");

    hr = FedSrv_Init();

    #if !defined(_CONSOLE)
        if (SUCCEEDED(hr))
        {
            RunAsWindow();
            return;
        }
    #endif


    if (SUCCEEDED(hr))
    {
        HANDLE hConsole;
                                  
        printf("\rType 'Q' to Quit.\n");
        printf("\rType 'P' to Pause.\n");
        printf("\rType 'C' to Continue.\n");

        hConsole = GetStdHandle(STD_INPUT_HANDLE);
        SetConsoleMode(hConsole,0);

        SetConsoleCtrlHandler(HandlerRoutine, true);

        assert(g.hKillReceiveEvent);

        HANDLE hEventArray[] = { g.hKillReceiveEvent, hConsole };

        DWORD dwAwaker;

        //
        // Wait until Receive Thread dies; if Q is pressed, kill Receive Thread
        //
        do
        {
            dwAwaker = MsgWaitForMultipleObjects(2, hEventArray, FALSE, INFINITE, 0);

            if (dwAwaker == WAIT_OBJECT_0 + 1)
            {
                INPUT_RECORD Key;
                DWORD dwRead;

                ReadConsoleInput(hConsole, &Key, 1, &dwRead);

                if (Key.EventType == KEY_EVENT && 
                    Key.Event.KeyEvent.wVirtualKeyCode == 'Q')
                {
                     debugf("Q pressed\n");
                     SetEvent(g.hKillReceiveEvent);
                }
                if (Key.EventType == KEY_EVENT && 
                    Key.Event.KeyEvent.wVirtualKeyCode == 'P')
                {
                     debugf("P pressed\n");
                     FedSrv_Pause();
                }
                if (Key.EventType == KEY_EVENT && 
                    Key.Event.KeyEvent.wVirtualKeyCode == 'C')
                {
                     debugf("C pressed\n");
                     FedSrv_Continue();
                }
            }

        } while (dwAwaker != WAIT_OBJECT_0);

        FedSrv_Terminate();
    } 
    else
    {
        printf("\rInitialization failed. (%x)\n", hr);
    }
}



////////////////////////////////////////////////////////////////////////
// SetServiceStatus - more useful version of this function than the NT
// one.
//
//

void CServiceModule::SetSvcStatus(
  DWORD state,
  DWORD exitcode
)
{
   if (g.ssh)
   {
       SERVICE_STATUS ss;
       static DWORD s_cp = 1;

       ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
       ss.dwCurrentState = state;
       ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
       ss.dwWin32ExitCode = exitcode;
       ss.dwServiceSpecificExitCode = 0;

       switch (state)
       {
           case SERVICE_RUNNING:
           case SERVICE_STOPPED:
           case SERVICE_PAUSED:
               ss.dwCheckPoint = s_cp++;
               ss.dwWaitHint = 1000;
               break;

         case SERVICE_START_PENDING:
               ss.dwCheckPoint = s_cp++;
               ss.dwWaitHint = 30000;       // we take about 30 seconds to start
            break;

         case SERVICE_STOP_PENDING:
               ss.dwCheckPoint = s_cp++;
               ss.dwWaitHint = 30000;       // we take about 30 seconds to shutdown
            break;

           default:
               ss.dwCheckPoint = 0;
               ss.dwWaitHint = 0;
               break;
       }

       SetServiceStatus(g.ssh,&ss);
   }
}
       
DWORD WINAPI CServiceModule::ServerTerminateThread(DWORD dwUnused)
{
    FedSrv_Terminate();
    SetSvcStatus(SERVICE_STOPPED,0);
    return 0;
}      

void WINAPI CServiceModule::StartServerTerminateThread()
{
    DWORD dwId;
    HANDLE hthrPending = CreateThread(NULL, 8192, (LPTHREAD_START_ROUTINE) ServerTerminateThread, 0, 0, &dwId);
    CloseHandle(hthrPending);
}
       
////////////////////////////////////////////////////////////////////////
// ServiceControl
//
// Service control handler which is registered with NT (using
// RegisterServiceCtrlHandler) as the callback for the service events.
// Right now the service does not support PAUSE/CONTINUE functionality,
// but this could be added here if required.
//
//
void WINAPI CServiceModule::ServiceControl(DWORD dwCode)
{
    switch (dwCode)
    {
        case SERVICE_CONTROL_STOP:
            // If it can take us a long time to stop our service as we wait for operations to complete,
            // the SCM doesn't like the ServiceControl() function to take a long time, so we spin up another
            // thread to terminate us.
            SetSvcStatus(SERVICE_STOP_PENDING, 0);
            StartServerTerminateThread();
            break;

        case SERVICE_CONTROL_PAUSE:
            FedSrv_Pause();
            SetSvcStatus(SERVICE_PAUSED, 0);
            
            break;

        case SERVICE_CONTROL_CONTINUE:
            FedSrv_Continue();
            SetSvcStatus(SERVICE_RUNNING, 0);
            break;

        case SERVICE_CONTROL_SHUTDOWN:
            // System is shutting down.  Memory and resources should *not*
            // be freed in this case due to limited time available for
            // shutdown.  Instead, only urgent non-volatile (i.e. disk)
            // structures should be flushed.
            break;

        case SERVICE_CONTROL_INTERROGATE:
            break;

    }

}




////////////////////////////////////////////////////////////////////////
// ServiceMain
//
// Pointer to this function is in the SERVICE_TABLE_ENTRY of the
// array passed to StartServiceCtrlDispatcher.

void WINAPI CServiceModule::ServiceMain(
  DWORD dwArgc,
  LPSTR *lpszArgv)
{
    HRESULT hr;

    g.ssh = RegisterServiceCtrlHandler(c_szSvcName, ServiceControl);

    if (!g.ssh)
    {
        SetSvcStatus(SERVICE_STOPPED,GetLastError());
        return;
    }

    SetSvcStatus(SERVICE_START_PENDING,NO_ERROR);

    hr = FedSrv_Init();

    if (SUCCEEDED(hr))
        SetSvcStatus(SERVICE_RUNNING,NO_ERROR);
    else
        SetSvcStatus(SERVICE_STOPPED,GetLastError());

}

void WINAPI _ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv)
{
    _Module.ServiceMain(dwArgc, lpszArgv);
}


/*-------------------------------------------------------------------------
 * StopAllsrv
 *-------------------------------------------------------------------------
 * Purpose:
 *    cause allsrv to terminate 
 */

void CServiceModule::StopAllsrv()
{ 
  if(_Module.IsInstalledAsService())
    _Module.ServiceControl(SERVICE_CONTROL_STOP);
  else
  {
    if(g.hKillReceiveEvent)
      SetEvent(g.hKillReceiveEvent);
//    if(m_hEventStopRunningAsEXE)
//        SetEvent(m_hEventStopRunningAsEXE);
  }
}


/*-------------------------------------------------------------------------
 * MTAKeepAliveThunk
 *-------------------------------------------------------------------------
 * Purpose:
 *    static thunk function to reinterpret the thread parameter and call
 *    through it as a 'this' pointer.
 */
DWORD WINAPI CServiceModule::MTAKeepAliveThunk(void* pvThis)
{
  CServiceModule* pThis = reinterpret_cast<CServiceModule*>(pvThis);
  pThis->MTAKeepAliveThread();
  return 0;
}


/*-------------------------------------------------------------------------
 * MTAKeepAliveThread
 *-------------------------------------------------------------------------
 * Purpose:
 *    Thread controlling-function that initializes the COM MTA, registers
 *    the server's externally-creatable class objects and sleeps. When the
 *    quit event is signaled, the thread wakes up, revokes the class
 *    objects, and uninitializes the COM MTA.
 */
void CServiceModule::MTAKeepAliveThread()
{
  // Enter the COM MTA
  TCCoInit init(COINIT_MULTITHREADED);
  m_hrMTAKeepAlive = init;

  // Register the externally-creatable class objects
  if (init.Succeeded())
    m_hrMTAKeepAlive = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER,
      REGCLS_MULTIPLEUSE);

  // Signal the 'Ready' event
  assert(!m_shevtMTAReady.IsNull());
  SetEvent(m_shevtMTAReady);
  RETURN_FAILED_VOID(m_hrMTAKeepAlive);

  // Wait for the 'Exit' event
  assert(!m_shevtMTAExit.IsNull());
  WaitForSingleObject(m_shevtMTAExit, INFINITE);

  // Note: the following call was moved here from the ReceiveThread function
  // in FedSrv.cpp so that it will always be called from within the MTA.
  // Destroy all sessions now--before GetAGCGlobal() becomes invalid
  CAdminSession::DestroyAllSessions();

  // Revoke the class objects
  _Module.RevokeClassObjects();

  // CoUninitialize called automatically upon leaving scope (using TCCoInit)
}