// // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved // #include #include #include #include #include #include "WindowsService.h" #include "MSFT_WindowsServiceStarted.h" #include "MSFT_WindowsServiceStopped.h" // Define the polling interval value (in milliseconds) #define POLLING_INTERVAL 5000 // A thread used to polling all services to detect // service stopped and started event (i.e, indication) HANDLE g_hPollingThread = NULL; // A critical section used to protect the modification of g_hPollingThread CRITICAL_SECTION g_csPollingThread; // An event used to shutdown the polling thread HANDLE g_hStopPollingEvent = NULL; // Remember the Namespace into which this provider is loaded volatile LPWSTR g_lpwszNamespace = NULL; // Query template used to build query static LPCWSTR g_pQueryTemplate = L"select * from MSFT_WindowsService where Name='"; SIZE_T g_nQueryTemplateLength = 0; // // A count to remember how many indication classes are relying on // polling thread now. Since we only have 2 indication class, thus // the number could be 0, 1, 2. // // Once the number became from 1 to 0, current thread need to shutdown the // polling thread by signal g_hStopPollingEvent; // // Once the number became from 0 to 1, current thread need to // creat an polling thread. // volatile LONG g_nActiveIndicationClass = 0; // // Polling thread rely on a snapshot of services to detect any status change of // service, g_ServiceHeader is a linked list which hold a snapshot of all // windows services of the last polling. With each polling, the Polling thread // will update the status of corresponding service and trigger the corresponding // indication (event). // WindowsService g_ServiceHeader; // // Asssert Helper function // #define MY_DEBUG void MyAssert(BOOL exp) { if (exp == FALSE) { #ifdef MY_DEBUG *(int *)(0) = 0; #else assert(exp); #endif } } // // Helper function of allocating memory from process heap // // Argument: // dwBytes number of bytes to allocate. // // Return value: // allocated memory address // LPVOID AllocateMemory(SIZE_T dwBytes) { return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBytes); } // // Helper function of freeing memory // // Argument: // lpMem memory address to free. // void FreeMemory(LPVOID lpMem) { HeapFree(GetProcessHeap(), 0, lpMem); } // // Initialize global variables, which will be invoked once this provider was // being loaded, see module.c : Load // // Return value: // MI_RESULT_OK means success, otherwise failed // MI_Result Initialize() { memset(&g_ServiceHeader, 0, sizeof(WindowsService)); InitializeCriticalSection(&g_csPollingThread); g_hStopPollingEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (g_hStopPollingEvent == NULL) { return MI_RESULT_FAILED; } g_nQueryTemplateLength = wcslen(g_pQueryTemplate); return MI_RESULT_OK; } // // Finalize global variables, which will be invoked once this provider was // being unloaded, see module.c : Unload // // Return value: // MI_RESULT_OK means success, otherwise failed // MI_Result Finalize() { MyAssert(g_hStopPollingEvent != NULL); { WindowsService *pService = g_ServiceHeader.pNextService; while (pService != NULL) { FreeMemory(pService->pName); { LPVOID pTemp = pService; pService = pService->pNextService; FreeMemory(pTemp); } } memset(&g_ServiceHeader, 0, sizeof(WindowsService)); } if (g_lpwszNamespace != NULL) { FreeMemory(g_lpwszNamespace); g_lpwszNamespace = NULL; } if(g_hStopPollingEvent) { CloseHandle(g_hStopPollingEvent); g_hStopPollingEvent = NULL; } DeleteCriticalSection(&g_csPollingThread); return MI_RESULT_OK; } // // Global PollingThreadArgument. MSFT_WindowsServiceStarted and // MSFT_WindowsServiceStopped rely on it to passing indication context to // polling thread; Polling thread uses the argument to post the indication // event to client through the context object. // PollingThreadArgument g_Arg; // // helper function used to start polling thread. // it will create the polling thread if and only if // the polling thread is not created yet, which can be // tracked by the value of g_nActiveIndicationClass // void StartPollingThread() { EnterCriticalSection(&g_csPollingThread); { g_nActiveIndicationClass ++; if (g_nActiveIndicationClass == 1) { // This is the first indication class, need to // create the polling thread to start the polling MyAssert(g_hPollingThread == NULL); g_hPollingThread = CreateThread(NULL, 0, PollingThreadProc, &g_Arg, 0, 0); // SetThreadPriority(g_hPollingThread, THREAD_PRIORITY_LOWEST); } } LeaveCriticalSection(&g_csPollingThread); } // // helper function used to shutdown polling thread. // it will shutdown the polling thread if and only if // there is no indication class rely on polling thread to generate indications, // which can be tracked by the value of g_nActiveIndicationClass // void StopPollingThread() { EnterCriticalSection(&g_csPollingThread); { g_nActiveIndicationClass --; if (g_nActiveIndicationClass == 0) { // there is the no active indication class, // need to shutdown the polling thread if (g_hPollingThread != NULL) { // Notify the polling thread to shutdown SetEvent(g_hStopPollingEvent); { HANDLE hPollingThread = g_hPollingThread; g_hPollingThread = NULL; // Wait until the polling thread to shutdown WaitForSingleObject(hPollingThread, INFINITE); CloseHandle(hPollingThread); } } } } LeaveCriticalSection(&g_csPollingThread); } // // Helper function used to set namespace into which the provider is loaded // // Argument: // lpwszNamespace The namespace that loaded provider. // // Return value: // MI_RESULT_OK means set namespace successfully, otherwise failed. // MI_Result SetNamespace(_In_opt_z_ const MI_Char *lpwszNamespace) { if (InterlockedCompareExchangePointer((PVOID*)(&g_lpwszNamespace), NULL, NULL) == NULL) { if (lpwszNamespace) { SIZE_T nNamespaceLength = wcslen(lpwszNamespace) + 1; LPWSTR lpwszNamespaceTemp = (LPWSTR)AllocateMemory(nNamespaceLength * sizeof(wchar_t)); if (lpwszNamespaceTemp == NULL) { return MI_RESULT_SERVER_LIMITS_EXCEEDED; } wcscpy_s(lpwszNamespaceTemp, nNamespaceLength, lpwszNamespace); // set g_lpwszNamespace threadsafely if (InterlockedCompareExchangePointer( (PVOID*)(&g_lpwszNamespace), lpwszNamespaceTemp, NULL) != NULL) { FreeMemory(lpwszNamespaceTemp); } } } return MI_RESULT_OK; } // // Helper function used to enable ServiceStarted indication // // Argument: // context The context used to post indication to client. // // Return value: // Enable result // MI_Result EnableServiceStartedIndication(__in MI_Context *context, _In_opt_z_ const MI_Char *lpwszNamespace) { MI_Result r = SetNamespace(lpwszNamespace); if (r != MI_RESULT_OK) { return r; } g_Arg.contextForStarted = context; StartPollingThread(); return MI_RESULT_OK; } // // Helper function used to disable ServiceStarted indication // // Argument: // context The context used to post indication to client. // // Return value: // Disable result // MI_Result DisableServiceStartedIndication(__in MI_Context *context) { MI_UNREFERENCED_PARAMETER(context); MyAssert(g_Arg.contextForStarted == context); g_Arg.contextForStarted = NULL; StopPollingThread(); return MI_RESULT_OK; } // // Helper function used to enable ServiceStopped indication // // Argument: // context The context used to post indication to client. // // Return value: // Enable result // MI_Result EnableServiceStoppedIndication(__in MI_Context *context, _In_opt_z_ const MI_Char *lpwszNamespace) { MI_Result r = SetNamespace(lpwszNamespace); if (r != MI_RESULT_OK) { return r; } g_Arg.contextForStopped = context; StartPollingThread(); return MI_RESULT_OK; } // // Helper function used to disable ServiceStopped indication // // Argument: // context The context used to post indication to client. // // Return value: // Disable result // MI_Result DisableServiceStoppedIndication(__in MI_Context *context) { MI_UNREFERENCED_PARAMETER(context); MyAssert(g_Arg.contextForStopped == context); g_Arg.contextForStopped = NULL; StopPollingThread(); return MI_RESULT_OK; } // // Helper functions used to search windows service entry from snapshot. // // Parameter: // pServiceName name of the service to search, which is unique on windows machine. // pFound output parameter, which indicates whether a service was // found in snapshot. // ppService optional output parameter, which is the found service object. // // Return value: // FALSE means not found, TRUE means found and return the notepad // through ppService parameter. // DWORD FindAndAddIfNotFound( __in_z LPCWSTR pServiceName, __out BOOL * pFound, __deref_out_opt WindowsService** ppService) { WindowsService * pService; MyAssert(pServiceName != NULL); MyAssert(ppService != NULL); MyAssert(pFound != NULL); *ppService = NULL; *pFound = FALSE; // search the given service from snapshot (linked list) pService = g_ServiceHeader.pNextService; while (pService != NULL) { if (_wcsicmp(pService->pName, pServiceName) == 0) { // found the service object, return *ppService = pService; *pFound = TRUE; return ERROR_SUCCESS; } pService = pService->pNextService; } // not found, create a new service object pService = (WindowsService*)AllocateMemory(sizeof(WindowsService)); if (pService == NULL) { // out of memory, return return ERROR_NOT_ENOUGH_MEMORY; } { size_t nNameLength = wcslen(pServiceName) + 1; pService->pName = (wchar_t*)AllocateMemory(nNameLength * sizeof(wchar_t)); if (pService->pName == NULL) { FreeMemory(pService); return ERROR_NOT_ENOUGH_MEMORY; } wcscpy_s(pService->pName, nNameLength, pServiceName); } // add the new service object to the snapshot { WindowsService *pNext = g_ServiceHeader.pNextService; g_ServiceHeader.pNextService = pService; pService->pNextService = pNext; } // return the new service object *ppService = pService; return ERROR_SUCCESS; } // // Enumeration of the Service Status, see WinSvc.h // #define SERVICE_STOPPED 0x00000001 // #define SERVICE_START_PENDING 0x00000002 // #define SERVICE_STOP_PENDING 0x00000003 // #define SERVICE_RUNNING 0x00000004 // #define SERVICE_CONTINUE_PENDING 0x00000005 // #define SERVICE_PAUSE_PENDING 0x00000006 // #define SERVICE_PAUSED 0x00000007 // Service Status values static MI_Char *SERVICE_STATUS_VALUE[] = { MI_T("Unknown"), MI_T("Stopped"), MI_T("Starting"), MI_T("Stopping"), MI_T("Running"), MI_T("ContinuePending"), MI_T("PausePending"), MI_T("Paused") }; // // Helper function used to read current system time // void GetSystemDateTime(__inout MI_Datetime * pDateTime) { MyAssert(pDateTime != NULL); { SYSTEMTIME systemTime; GetSystemTime(&systemTime); pDateTime->isTimestamp = MI_TRUE; pDateTime->u.timestamp.year = systemTime.wYear; pDateTime->u.timestamp.month = systemTime.wMonth; pDateTime->u.timestamp.day = systemTime.wDay; pDateTime->u.timestamp.hour = systemTime.wHour; pDateTime->u.timestamp.minute = systemTime.wMinute; pDateTime->u.timestamp.second = systemTime.wSecond; pDateTime->u.timestamp.microseconds = systemTime.wMilliseconds; pDateTime->u.timestamp.utc = 0; } } // // Helper function used to query MSFT_WindowsService instance and post back // generated indication. // void SendIndication( __in MI_Context *miContext, __in DWORD dwIndicationType, // 0 - started indication, 1 -stopped indication __in DWORD dwOriginalState, _In_z_ const wchar_t *namespaceName, _In_z_ const wchar_t *serviceName) { MI_Result miResult = MI_RESULT_OK; MI_Operation miOperation = MI_OPERATION_NULL; MI_Boolean moreResults; const MI_Char *errorString = NULL; const MI_Instance *errorDetails = NULL; MI_Session miSession; LPWSTR lpQueryBuffer = NULL; SIZE_T nQueryBufferLength = 0; SIZE_T nServiceNameLength = 0; MyAssert(serviceName != NULL); MI_UNREFERENCED_PARAMETER(serviceName); miResult = MI_Context_GetLocalSession(miContext, &miSession); if (miResult != MI_RESULT_OK) { // do not send indication if cannot get locale session return; } nServiceNameLength = wcslen(serviceName); nQueryBufferLength = nServiceNameLength + g_nQueryTemplateLength + 2; lpQueryBuffer = (LPWSTR)AllocateMemory(nQueryBufferLength * sizeof(wchar_t)); if (lpQueryBuffer == NULL) { return; } wcscpy_s(lpQueryBuffer, nQueryBufferLength, g_pQueryTemplate); wcscpy_s(lpQueryBuffer + g_nQueryTemplateLength, nQueryBufferLength - g_nQueryTemplateLength, serviceName); lpQueryBuffer[nQueryBufferLength - 2] = L'\''; lpQueryBuffer[nQueryBufferLength - 1] = L'\0'; // Query the MSFT_WindowsService instance based on service name, // since both indication event contains the instance object // as embedded property. In order to send indication event back // to client, it is mandatory to set both sourceInstance and // previousInstance properties. MI_Session_QueryInstances(&miSession, 0, NULL, namespaceName, MI_T("WQL"), lpQueryBuffer, NULL, &miOperation); do { MI_Instance *miInstance; MI_Result _miResult; _miResult = MI_Operation_GetInstance(&miOperation, &miInstance, &moreResults, &miResult, &errorString, &errorDetails); if (_miResult != MI_RESULT_OK) { /* This means cannot read the service instance, skip issuing indication. */ break; } if (miInstance) { if (dwIndicationType == 0) { MSFT_WindowsServiceStarted started; _miResult = MSFT_WindowsServiceStarted_Construct(&started, miContext); if (_miResult != MI_RESULT_OK) break; do { // set sourceInstance property of indication object MI_Value v; MI_Datetime systemTime = {0}; v.string = MI_T("Running"); _miResult = MI_Instance_SetElement(miInstance, MI_T("Status"), &v, MI_STRING, 0); if (_miResult != MI_RESULT_OK) { break; } _miResult = MSFT_WindowsServiceStarted_Set_SourceInstance(&started, miInstance); if (_miResult != MI_RESULT_OK) { break; } // set previousInstance property of indication object v.string = SERVICE_STATUS_VALUE[dwOriginalState]; _miResult = MI_Instance_SetElement(miInstance, MI_T("Status"), &v, MI_STRING, 0); if (_miResult != MI_RESULT_OK) { break; } _miResult = MSFT_WindowsServiceStarted_Set_PreviousInstance(&started, miInstance); if (_miResult != MI_RESULT_OK) { break; } GetSystemDateTime(&systemTime); _miResult = MSFT_WindowsServiceStarted_Set_IndicationTime(&started, systemTime); if (_miResult != MI_RESULT_OK) { break; } // post indication back to client MSFT_WindowsServiceStarted_Post(&started, miContext, 0, L""); } while (FALSE); MSFT_WindowsServiceStarted_Destruct(&started); } else if (dwIndicationType == 1) { MSFT_WindowsServiceStopped stopped; _miResult = MSFT_WindowsServiceStopped_Construct(&stopped, miContext); if (_miResult != MI_RESULT_OK) break; do { // set sourceInstance property of indication object MI_Value v; MI_Datetime systemTime = {0}; v.string = MI_T("Stopped"); _miResult = MI_Instance_SetElement(miInstance, MI_T("Status"), &v, MI_STRING, 0); if (_miResult != MI_RESULT_OK) { break; } _miResult = MSFT_WindowsServiceStopped_Set_SourceInstance(&stopped, miInstance); if (_miResult != MI_RESULT_OK) { break; } // set previousInstance property of indication object v.string = SERVICE_STATUS_VALUE[dwOriginalState]; _miResult = MI_Instance_SetElement(miInstance, MI_T("Status"), &v, MI_STRING, 0); if (_miResult != MI_RESULT_OK) { break; } _miResult = MSFT_WindowsServiceStopped_Set_PreviousInstance(&stopped, miInstance); if (_miResult != MI_RESULT_OK) { break; } GetSystemDateTime(&systemTime); _miResult = MSFT_WindowsServiceStopped_Set_IndicationTime(&stopped, systemTime); if (_miResult != MI_RESULT_OK) { break; } // post indication back to client MSFT_WindowsServiceStopped_Post(&stopped, miContext, 0, L""); } while(FALSE); MSFT_WindowsServiceStopped_Destruct(&stopped); } } } while (FALSE); // we only expect one result instance FreeMemory(lpQueryBuffer); MI_Operation_Close(&miOperation); } // // Helper function used to generate indication instance based on service status // and post indication back to client. // void GenerateIndication( __in PollingThreadArgument * pArg, __in DWORD dwOriginalState, __in ENUM_SERVICE_STATUS * pServiceStatus) { MyAssert(pArg != NULL); MyAssert(pServiceStatus != NULL); // generate the event if necessary if (pArg->contextForStarted != NULL) { if ((dwOriginalState != SERVICE_RUNNING) && (pServiceStatus->ServiceStatus.dwCurrentState == SERVICE_RUNNING)) { // trigger service started indication // SourceInstance & PreviousInstance should read from service // provider directly. SendIndication(pArg->contextForStarted, 0, dwOriginalState, g_lpwszNamespace, pServiceStatus->lpServiceName); } } if (pArg->contextForStopped != NULL) { if ((dwOriginalState != SERVICE_STOPPED) && (pServiceStatus->ServiceStatus.dwCurrentState == SERVICE_STOPPED)) { // trigger service stopped indication // SourceInstance & PreviousInstance should read from service // provider directly. SendIndication(pArg->contextForStopped, 1, dwOriginalState, g_lpwszNamespace, pServiceStatus->lpServiceName); } } } // // This function is invoked from polling thread, which poll all services status // and generate MSFT_WindowsServiceStarted and MSFT_WindowsServiceStopped indication // on demand. // // Argument: // lpParameter parameter passed to polling thread // // Return value: // Thread execution result // DWORD WINAPI PollingThreadProc(__in LPVOID lpParameter) { DWORD errorToReturn = ERROR_SUCCESS; SC_HANDLE hSvcCtlMgr; BOOL bShutdown = FALSE; PollingThreadArgument * pArg = (PollingThreadArgument *)lpParameter; assert (pArg != NULL); hSvcCtlMgr = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_CONNECT); if (hSvcCtlMgr == NULL) { // Cannot get access to SCManager object and fail. return GetLastError(); } do { DWORD dwServiceIndex, dwBytesNeeded, dwServiceCount, dwResumeHandle = 0; ENUM_SERVICE_STATUS * lpServiceArray = NULL; // Enumerate all services installed on current machine BOOL returnValue = EnumServicesStatus( hSvcCtlMgr, SERVICE_WIN32, SERVICE_STATE_ALL, NULL, 0, &dwBytesNeeded, &dwServiceCount, &dwResumeHandle); if (!returnValue) { DWORD lastError = GetLastError(); if ((lastError == ERROR_INSUFFICIENT_BUFFER) || (lastError == ERROR_MORE_DATA)) { lpServiceArray = (ENUM_SERVICE_STATUS *)AllocateMemory(dwBytesNeeded); if (lpServiceArray == NULL) { errorToReturn = ERROR_NOT_ENOUGH_MEMORY; break; } dwResumeHandle = 0; returnValue = EnumServicesStatus( hSvcCtlMgr, SERVICE_WIN32, SERVICE_STATE_ALL, lpServiceArray, dwBytesNeeded, &dwBytesNeeded, &dwServiceCount, &dwResumeHandle); if (!returnValue) { errorToReturn = GetLastError(); FreeMemory(lpServiceArray); break; } } else { errorToReturn = lastError; break; } } else { errorToReturn = ERROR_INVALID_ENVIRONMENT; break; } // Take snapshot of all services, and compare the current status of each service // with previous status, then issue indication event if status was changed // since last polling. for(dwServiceIndex = 0; dwServiceIndex < dwServiceCount; dwServiceIndex++) { WindowsService * pService = NULL; BOOL found = FALSE; DWORD dwResult; // try to find the service in snapshot dwResult = FindAndAddIfNotFound(lpServiceArray[dwServiceIndex].lpServiceName, &found, &pService); if (dwResult != ERROR_SUCCESS) { continue; } else if (found && (pService != NULL)) { // generate indication GenerateIndication(pArg, pService->dwState, &lpServiceArray[dwServiceIndex]); } // update the snapshot status of the service if (pService != NULL) { pService->dwState = lpServiceArray[dwServiceIndex].ServiceStatus.dwCurrentState; } } FreeMemory(lpServiceArray); // polling every POLLING_INTERVAL (ms), if stop event was signaled, then stop polling thread { DWORD dwWaitResult = WaitForSingleObject(g_hStopPollingEvent, POLLING_INTERVAL); switch(dwWaitResult) { case WAIT_OBJECT_0: // shutdown the polling thread ResetEvent(g_hStopPollingEvent); bShutdown = TRUE; break; default: break; } } } while (!bShutdown); CloseServiceHandle(hSvcCtlMgr); return errorToReturn; }