2025-11-28 00:35:46 +09:00

802 lines
25 KiB
C

//
// 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 <Windows.h>
#include <malloc.h>
#include <MI.h>
#include <assert.h>
#include <string.h>
#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;
}