//+---------------------------------------------------------------------------- // // File: chimesvc.cpp // // Microsoft Server Appliance Kit // // Synopsis: Implements class CChimeSvc // Usage chimesvc -install : installs the ChimeService service // chimesvc -uninstall: uninstalls the service // net start chimeservice : starts chimeservice // // Copyright (c) Microsoft Corporation. All rights reserved. // // //+---------------------------------------------------------------------------- #include #include #include "ChimeSvc.h" #include "appsrvcs_i.c" #include "taskctx_i.c" CChimeSvc* CChimeSvc::m_pThis = NULL; const WCHAR SERVICE_NAME[] = L"ChimeService"; const WCHAR SERVICE_DISPLAY_NAME[] = L"Server Appliance Sample ChimeService"; const WCHAR CHIME_TASK_NAME[]=L"ChimeTask"; const WCHAR REG_CHIME_TYPE[]=L"ChimeType"; const WCHAR REG_CHIME_INTERVAL[]=L"ChimeInterval"; const WCHAR REG_RAISE_ALERT[]=L"RaiseAlert"; //default state for chime application const DWORD CHIME_MAX_INTERVAL=43200; //12 hour - in seconds, const DWORD CHIME_TYPE_NONE=0; const DWORD MILISECONDS_PER_MIN=60000; const DWORD MILISECONDS_PER_SEC=1000; const DWORD SECONDS=60; //seconds per minute //+---------------------------------------------------------------------------- // // Function: CChimeSvc::CChimeSvc // // Synopsis: Constructor // // Arguments: None // // Returns: Nothing // //+---------------------------------------------------------------------------- CChimeSvc::CChimeSvc() :m_hChimeChangeEvt(NULL),m_hStopSvcEvt(NULL),m_dwChimeInterval(CHIME_MAX_INTERVAL),m_dwChimeType(0) { // set up the initial service status m_hServiceStatus = NULL; m_Status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; m_Status.dwCurrentState = SERVICE_STOPPED; m_Status.dwControlsAccepted = SERVICE_ACCEPT_STOP; m_Status.dwWin32ExitCode = 0; m_Status.dwServiceSpecificExitCode = 0; m_Status.dwCheckPoint = 0; m_Status.dwWaitHint = 0; ASSERT(m_pThis == NULL); m_pThis = this; } //+---------------------------------------------------------------------------- // // Function: CChimeSvc::~CChimeSvc // // Synopsis: Destructor // // Arguments: None // // Returns: Nothing // //+---------------------------------------------------------------------------- CChimeSvc::~CChimeSvc() { m_pThis = NULL; } //+---------------------------------------------------------------------------- // // Function: CChimeSvc::CreateChimeRegKeys // // Synopsis: Creates registry keys used for the chime application. (usage "chimesvc -install" ) // // Arguments: None // // Returns: BOOL // //+---------------------------------------------------------------------------- BOOL CChimeSvc::CreateChimeRegKeys() { HKEY hkey=NULL; DWORD dwDisposition; long lResult=0; BOOL fRet=TRUE; do{ lResult=RegCreateKeyEx(HKEY_LOCAL_MACHINE, CHIME_REGISTRY_SUBKEY, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hkey, &dwDisposition); if(lResult!=ERROR_SUCCESS) { TRACE1("CChimeSvc::CreateChimeRegKeys() create chime reg subkey failed %d", GetLastError()); fRet=FALSE; break; } DWORD dwData=0; lResult=RegSetValueEx( hkey, // handle to key REG_CHIME_TYPE, // value name 0, // reserved REG_DWORD, // value type (BYTE *)(&dwData), // value data sizeof(dwData) // size of value data ); if(lResult!=ERROR_SUCCESS) { TRACE1("CChimeSvc::CreateChimeRegKeys() RegSetValueEx - ChimeType failed, value already exists?", GetLastError()); fRet=FALSE; break; } dwData=CHIME_MAX_INTERVAL; lResult=RegSetValueEx( hkey, // handle to key REG_CHIME_INTERVAL, // value name 0, // reserved REG_DWORD, // value type (BYTE *)&dwData, // value data sizeof(dwData) // size of value data ); if(lResult!=ERROR_SUCCESS) { TRACE1("CChimeSvc::CreateChimeRegKeys() RegSetValueEx - ChimeInterval failed, value already exists?", GetLastError()); fRet=FALSE; break; } dwData=1; lResult=RegSetValueEx( hkey, // handle to key REG_RAISE_ALERT, // value name 0, // reserved REG_DWORD, // value type (BYTE *)&dwData, // value data sizeof(dwData) // size of value data ); if(lResult!=ERROR_SUCCESS) { TRACE1("CChimeSvc::CreateChimeRegKeys() RegSetValueEx - RaiseAlert failed, value already exists?", GetLastError()); fRet=FALSE; break; } } while(false); if (NULL != hkey) { RegCloseKey(hkey); } return fRet; } //+---------------------------------------------------------------------------- // // Function: CChimeSvc::ReadChimeConfigFromRegistry // // Synopsis: Reads the chime settings from registry. When chime settings are changed // through the chime wizard on the web page SetChimeSettings task will signal a named even // (SA_Chime_Change_Event) and ChimeSvc will call ReadChimeConfigFromRegistry // to pick up the new settings. // // Arguments: reference to the pointer to task context // // Returns: Nothing // //+---------------------------------------------------------------------------- void CChimeSvc::ReadChimeConfigFromRegistry(CComPtr &pTaskContext) { TRACE("CChimeSvc::ReadChimeConfigFromRegistry()"); HKEY hkReg=NULL; TCHAR lpBuffer[1000]={0}; // buffer to receive reg value ULONG lcbValue; //count of bytes for the reg value. DWORD dwType; //type of reg key. long lResult=0; //holds return values. ASSERT(pTaskContext); lResult=RegOpenKeyEx(HKEY_LOCAL_MACHINE, CHIME_REGISTRY_SUBKEY, (DWORD)NULL, KEY_QUERY_VALUE, &hkReg); if(ERROR_SUCCESS == lResult) { dwType=0; lResult=RegQueryValueEx(hkReg, CHIME_REGISTRY_INTERVAL, NULL, &dwType, (LPBYTE)lpBuffer, &lcbValue); if(ERROR_SUCCESS == lResult) { switch(dwType) { case REG_DWORD: { const DWORD *pdwValue=(DWORD *)lpBuffer; if (*pdwValue != m_dwChimeInterval) { TRACE1("CChimeSvc::ReadChimeConfigFromRegistry() - Reading interval %d", *pdwValue); m_dwChimeInterval=*pdwValue; //i.e. SECONDS per minute } break; } default: TRACE("CChimeSvc::ReadChimeConfigFromRegistry() - ChimeInterval should be REG_DWORD"); break; } } else // could not RegQueryValueEx { TRACE1("CChimeSvc::ReadChimeConfigFromRegistry() - ChimeInterval regkey could not be queried, is it there?,lresult=%d,",lResult); m_dwChimeInterval=CHIME_MAX_INTERVAL; //initialize to some valid state... } //Read chime type dwType=0; lResult=RegQueryValueEx(hkReg, CHIME_REGISTRY_TYPE, NULL, &dwType, (LPBYTE)lpBuffer, &lcbValue); if(ERROR_SUCCESS == lResult) { switch(dwType) { case REG_DWORD: { const DWORD *pdwValue=(DWORD *)lpBuffer; if (*pdwValue != m_dwChimeType) { TRACE1("CChimeSvc::ReadChimeConfigFromRegistry() - Reading type %d", *pdwValue); m_dwChimeType=*pdwValue; } break; } default: TRACE("CChimeSvc::ReadChimeConfigFromRegistry() - ChimeType should be REG_DWORD"); break; } } else // could not RegQueryValueEx { TRACE1("CChimeSvc::ReadChimeConfigFromRegistry() - ChimType regkey could not be queried, is it there?,getlasterror=%d",GetLastError()); m_dwChimeType=CHIME_TYPE_NONE; } } else // could not RegOpenKeyEx { TRACE1("CChimeSvc::ReadChimeConfigFromRegistry() - Chime subkey could not be opened, is it there?,getlasterror=%d",GetLastError()); m_dwChimeType=CHIME_TYPE_NONE; //initialize to some valid state m_dwChimeInterval=CHIME_MAX_INTERVAL; //initialize to some valid state } // //Change the task context per new configuration // _variant_t vtChimeType=(long) m_dwChimeType; _bstr_t bstrChimeParam = L"ChimeType"; HRESULT hr = pTaskContext->SetParameter(bstrChimeParam, &vtChimeType); if (FAILED(hr)) { TRACE1("pTaskContext->SetParameters(bstrChimeParam,&vtChimeType) failed in ReadConfigFromRegistry), getlasterror: %d",GetLastError()); } RegCloseKey(hkReg); } //+---------------------------------------------------------------------------- // // Function: CChimeSvc::ComputeTimeToChime() // // Synopsis: Computes the time to first chime // // Arguments: None // // Returns: DWORD time to next chime in milliseconds. // //+---------------------------------------------------------------------------- DWORD CChimeSvc::ComputeTimeToChime() { SYSTEMTIME LocalTime; GetLocalTime(&LocalTime); DWORD dwMillisSinceLastHour = (LocalTime.wMinute*MILISECONDS_PER_MIN+ LocalTime.wSecond*MILISECONDS_PER_SEC); DWORD dwResult=(m_dwChimeInterval*MILISECONDS_PER_SEC- (dwMillisSinceLastHour % (m_dwChimeInterval*MILISECONDS_PER_SEC))); TRACE1("CChimeSvc::ComputeTimeToChime- returning %d", dwResult/1000); //ASSERT( (0 m_Status.dwCurrentState = SERVICE_START_PENDING; m_pThis->m_hServiceStatus = RegisterServiceCtrlHandler(m_pThis->m_szServiceName, Handler); if (m_pThis->m_hServiceStatus == NULL) { TRACE(("RegisterServiceCtrlHandler Failed")); return; } TRACE("Start the initialization"); if (m_pThis->InitializeService()) { // Do the real work. // When the Run function returns, the service has stopped. m_pThis->m_Status.dwWin32ExitCode = 0; m_pThis->m_Status.dwCheckPoint = 0; m_pThis->m_Status.dwWaitHint = 0; m_pThis->Run(); } // Tell the service manager we are stopped m_pThis->SetStatus(SERVICE_STOPPED); } //+---------------------------------------------------------------------------- // // Function: CChimeSvc::SetStatus // // Synopsis: call ::SetServiceStatus to set status // // Arguments: DWORD dwState - status to set // // Returns: Nothing // //+---------------------------------------------------------------------------- inline void CChimeSvc::SetStatus(DWORD dwState) { m_Status.dwCurrentState = dwState; ::SetServiceStatus(m_hServiceStatus, &m_Status); } //+---------------------------------------------------------------------------- // // Function: CChimeSvc::InitializeService // // Synopsis: Service initialization, called only if run as service // // Arguments: None // // Returns: BOOL - TRUE, if succeeded // //+---------------------------------------------------------------------------- BOOL CChimeSvc::InitializeService() { BOOL fRet=TRUE; do { // // Start the initialization // SetStatus(SERVICE_START_PENDING); // Perform the actual initialization fRet = CanLoad(); // Set final state m_Status.dwWin32ExitCode = GetLastError(); m_Status.dwCheckPoint = 0; m_Status.dwWaitHint = 0; if( FALSE == fRet ) { TRACE(("Can not start the service")); SetStatus(SERVICE_STOPPED); fRet=FALSE; break; } SetStatus(SERVICE_RUNNING); // //create the named event to be used to //communicate with the chimesettings task // if (!(m_hChimeChangeEvt=CreateEvent( NULL, // SD true, // reset type false, // initial state L"SA_Chime_Change_Event" // object name ))) { TRACE1("CChimeSvc::InitializeService CreateEvent SA_Chime_Change_Event failed, %d", GetLastError()); fRet=FALSE; break; } } while(false); return fRet; } //+---------------------------------------------------------------------------- // // Function: static CChimeSvc::Handler // // Synopsis: Control request handlers. Callback to handle commands from the // service control manager. It runs in a different thread // // Arguments: DWORD dwOpcode - control code // // Returns: Nothing // // //+---------------------------------------------------------------------------- /*static*/ void CChimeSvc::Handler(DWORD dwOpcode) { // ASSERT(m_pThis); switch (dwOpcode) { case SERVICE_CONTROL_STOP: // 1 m_pThis->SetStatus(SERVICE_STOP_PENDING); if(!m_pThis->OnStop()) { // Can not stop the service m_pThis->SetStatus(SERVICE_RUNNING); } return; // we return here, another SetStatus("STOPPED") might cause block break; case SERVICE_CONTROL_PAUSE: // 2 case SERVICE_CONTROL_CONTINUE: // 3 case SERVICE_CONTROL_INTERROGATE: // 4 case SERVICE_CONTROL_SHUTDOWN: // 5 break; default: m_pThis->OnControlMessage(dwOpcode); break; } // Report current status ::SetServiceStatus(m_pThis->m_hServiceStatus, &m_pThis->m_Status); } //+---------------------------------------------------------------------------- // // Function: CChimeSvc::StartService // // Synopsis: Service startup and registration. Called from WinMain. Will not // return until service is stopped. // // Arguments: const TCHAR* pszServiceName - Service Name // // Returns: BOOL - TRUE, if succeeded // // //+---------------------------------------------------------------------------- BOOL CChimeSvc::StartService(const TCHAR* pszServiceName) { lstrcpyn(m_szServiceName, pszServiceName, sizeof(m_szServiceName)/sizeof(m_szServiceName[0])); // // Necessary procedure for starting the service, setup SERVICE_TABLE_ENTRY with service name and ServiceMain // SERVICE_TABLE_ENTRY st[] = { {m_szServiceName, ServiceMain}, {NULL, NULL} }; // // StartServiceCtrlDispatcher will call ServiceMain // TRACE("StartService Control Disp"); if (!::StartServiceCtrlDispatcher(st)) { DWORD dwResult=GetLastError(); //find out what went wrong and log it in the tracelog TRACE1(("StartService Failed with %d"), dwResult); return FALSE; } return TRUE; } //+---------------------------------------------------------------------------- // // Function: CChimeSvc::Run // // Synopsis: Chime service body. // It calls the chimetask for "chime" functionality. Notice the event driven mechanism // in the function. // // 1. Read registry settings // 2. Start waiting on chime interval // 3. When we exit from WaitForMultipleObjects: // if service is stopped exit // if registry is changed go to step 1. // if timeout (chime interval) expired "CHIME" //// // Arguments: HRESULT // // Returns: Nothing // // //+---------------------------------------------------------------------------- HRESULT CChimeSvc::Run() { // // Run until the service is shut down // bool fServiceStopped=false; DWORD dwTimeToChime=CHIME_MAX_INTERVAL; _bstr_t bstrTaskName = CHIME_TASK_NAME; HRESULT hr=S_OK; TRACE("Entering Run"); hr=CoInitialize(NULL); //Initialize Com libraries do { if (FAILED(hr)) { TRACE1("CChimeSvc::Run CoInitialize failed, %X", hr); break; } // //interface pointers needed for firing an alert: IApplianceServices // ITaskContext // CComPtr pAppSrvcs; CComPtr pTaskContext; hr = CoCreateInstance( CLSID_ApplianceServices, NULL, CLSCTX_INPROC_SERVER, IID_IApplianceServices, (void**)&pAppSrvcs ); if ( SUCCEEDED(hr) ) { // Initialize() is called prior to using other component services. Performs // component initialization operations. hr=pAppSrvcs->Initialize(); if ( SUCCEEDED(hr) ) { hr = CoCreateInstance( CLSID_TaskContext, NULL, CLSCTX_INPROC_SERVER, IID_ITaskContext, (void**)& pTaskContext ); if (FAILED(hr)) { TRACE1("CChimeSvc::Run failed in CoCreateInstance(CLSID_TaskContext... Is appmgr running?, %X", hr); break; } } else { TRACE1("CChimeSvc::Run failed in CoCreateInstance(CLSID_TaskContext... Is appmgr running?, %X", hr); break; } } else { TRACE1("CChimeSvc::Run failed in CoCreateInstance(CLSID_ApplianceServices... Is appmgr running?, %X", hr); break; } HANDLE lpHandles[2]; lpHandles[0]=m_hStopSvcEvt; //stop service event lpHandles[1]=m_hChimeChangeEvt; //chime settings changed event //pass pTaskContext to the reg read routine to update the context per new settings ReadChimeConfigFromRegistry(pTaskContext); dwTimeToChime=ComputeTimeToChime(); //time returned in seconds TRACE1("Time to next chime is computed as (in seconds): %d", dwTimeToChime); DWORD dwResult; TRACE("Entering the main while loop"); while(!fServiceStopped) { dwResult=WaitForMultipleObjects(2, (CONST HANDLE *)lpHandles, false, dwTimeToChime); switch (dwResult) { case WAIT_OBJECT_0: //Chime Service is stopped, exit- fServiceStopped=true; break; case WAIT_OBJECT_0 + 1: //Chime config is changed, read new settings from registry TRACE("CChimeSvc::Run() WAIT_OBJECT_0 + 1 Chime config is changed, read new settings from registry"); ReadChimeConfigFromRegistry(pTaskContext); dwTimeToChime=ComputeTimeToChime(); //for new settings ResetEvent(m_hChimeChangeEvt); break; case WAIT_TIMEOUT: //Chime TRACE("CChimeSvc::Run() Run: WAIT_TIMEOUT - We Chimed"); pAppSrvcs->ExecuteTask(bstrTaskName, pTaskContext); dwTimeToChime=ComputeTimeToChime(); //for new settings break; case WAIT_FAILED: TRACE1("Run: inside main loop, WAIT_FAILED %d",GetLastError()); break; default: // should not be here TRACE1("CChimeSvc::Run() - WaitForMultipleObjects possible failed, getlasterror= %d",GetLastError()); break; } } if (m_hChimeChangeEvt) { CloseHandle(m_hChimeChangeEvt); m_hChimeChangeEvt=NULL; } if (m_hStopSvcEvt) { CloseHandle(m_hStopSvcEvt); m_hStopSvcEvt = NULL; } } while(false); CoUninitialize(); return hr; } //+---------------------------------------------------------------------------- // // Function: CChimeSvc::CanLoad // // Synopsis: // Initialization routine for the service. // If returns false, the service will not be started. // Otherwise, the service status will be changed to RUNNING // Do NOT try to start other services here. The service control manager // is holding a lock now. // // Arguments: None // // Returns: BOOL - TRUE if initialize successfully. // // //+---------------------------------------------------------------------------- BOOL CChimeSvc::CanLoad() { if (!(m_hStopSvcEvt = CreateEvent(NULL, FALSE, FALSE, NULL))) { TRACE1("CChimeSvc::CanLoad CreateEvent failed, %d", GetLastError()); return FALSE; } return TRUE; } //+---------------------------------------------------------------------------- // // Function: CSampleService::OnStop // // Synopsis: Called when the service control manager wants to stop the service // This function is called from the service control handler thread. // // Arguments: None // // Returns: BOOL - TRUE, if the service can be stopped // // //+---------------------------------------------------------------------------- BOOL CChimeSvc::OnStop() { // // Signal the event to stop the main thread. // SetEvent(m_hStopSvcEvt); return TRUE; } //+---------------------------------------------------------------------------- // // Function: GetCommandLineArg // // Synopsis: Get the command line arguments. // // Arguments: LPSTR &lpszCommandLine (command line string) // // Returns: BOOL // //+---------------------------------------------------------------------------- BOOL GetCommandLineArg(LPTSTR &lpszCommandLine) { //LPTSTR lpszCommandLine = ::GetCommandLine(); int nCmdLen=lstrlen(lpszCommandLine); LPTSTR lpszToken=new TCHAR[nCmdLen+1]; lpszToken[0]='\0'; if(lpszCommandLine == NULL) return false; // Skip past program name (first token in command line). // Check for and handle quoted program name. if(*lpszCommandLine == TEXT('\"')) { // Scan, and skip over, subsequent characters until // another double-quote or a null is encountered. do { lpszCommandLine++; } while((*lpszCommandLine != TEXT('\"')) && (*lpszCommandLine != TEXT('\0'))); if(*lpszCommandLine == TEXT('\"')) // If we stopped on a double-quote (usual case), skip over it. lpszCommandLine++; } else { int i=0; while(*lpszCommandLine > TEXT(' ')) { lpszToken[i++]=*lpszCommandLine++; } lpszToken[i]='\0'; if(0 == lstrcmp(lpszToken,TEXT("-install"))) { g_rgCmdLine[INSTALL]=true; return true; } else if(0 == lstrcmp(lpszToken,TEXT("-uninstall"))) { g_rgCmdLine[UNINSTALL]=true; return true; } else return false; } // Skip past any white space preceeding the second token. while(*lpszCommandLine && (*lpszCommandLine <= TEXT(' '))) { lpszCommandLine++; } delete []lpszToken; return false; } //+---------------------------------------------------------------------------- // // Function: wWinMainCRTStartup // // Synopsis: The real program entry point. This function is also implemented // by CRT // // Arguments: HINSTANCE hInstance - // HINSTANCE /*hPrevInstance*/ - // LPTSTR - // int /*nShowCmd*/ - // // Returns: Nothing // // //+---------------------------------------------------------------------------- extern "C" int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpCmdLine, int /*nShowCmd*/) { GetCommandLineArg(lpCmdLine); TRACE("Entering Chime Service"); CChimeSvc chimeSvc; if (g_rgCmdLine[INSTALL]) { BOOL fSuccess=chimeSvc.Install(SERVICE_NAME, SERVICE_DISPLAY_NAME, SERVICE_AUTO_START, SERVICE_WIN32_OWN_PROCESS); if (!fSuccess) { TRACE("WinMain: chime service installation failed"); } } if (g_rgCmdLine[UNINSTALL]) { chimeSvc.Uninstall(); } TRACE("Starting Chime Service"); chimeSvc.StartService(SERVICE_NAME); // When we get here, the service has been stopped return 1; }