Home > Back-end >  How Do I Use NotifyServiceStatusChange to Get Notified When a Service Is Deleted?
How Do I Use NotifyServiceStatusChange to Get Notified When a Service Is Deleted?

Time:08-13

How can I use NotifyServiceStatusChange properly so I can get notified when the service specified is deleted? My current code successfully stops the service and marks it for deletion. However, I want to be notified when the service is fully deleted.

Here are the main points of my code:

SC_HANDLE SCManager = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, 
     SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);

HANDLE EventHandle = CreateEventW(NULL, TRUE, FALSE, NULL);

SERVICE_NOTIFY ServiceNotify;
ServiceNotify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
ServiceNotify.pszServiceNames = ServiceName;
ServiceNotify.pContext = &EventHandle;
ServiceNotify.pfnNotifyCallback = (PFN_SC_NOTIFY_CALLBACK)CallbackFunction;
DWORD status = NotifyServiceStatusChangeW(SCManager, SERVICE_NOTIFY_DELETED, &ServiceNotify);

WaitForSingleObject(EventHandle, INFINITE);
CloseServiceHandle(SCManager);
CloseHandle(EventHandle);

(ServiceName is WCHAR*)

CallbackFunction code:

VOID CALLBACK CallbackFunction(IN PVOID pParameter) {
    SERVICE_NOTIFY* ServiceNotify = pParameter;
    HANDLE EventHandle = *(HANDLE*)ServiceNotify->pContext;
    SetEvent(EventHandle);
}

NotifyServiceStatusChange is returning ERROR_SUCCESS (0). However, my callback function is not being called at all. How can I fix this?

Edit: Here is minimal reproducible code:

void ErrorExit(char* FunctionName, unsigned long ErrorCode) {
    char* ErrorMessage;
    FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, ErrorCode, LANG_USER_DEFAULT, (LPTSTR)&ErrorMessage, 0, NULL);

    int MessageSize = (strlen(ErrorMessage)   strlen(FunctionName)   50) * sizeof(char);
    char* FullMessage = malloc(MessageSize);
    sprintf_s(FullMessage, MessageSize, "%s failed with error %d: %s", FunctionName, ErrorCode, ErrorMessage);

    MessageBoxA(NULL, FullMessage, "Error", MB_OK);
    ExitProcess(ErrorCode);
}

PFN_SC_NOTIFY_CALLBACK CallbackFunction(PVOID pParameter) {
    printf("CallbackFunction has been called.\r\n");
    SERVICE_NOTIFY* ServiceNotify = pParameter;
    HANDLE EventHandle = ServiceNotify->pContext;
    if (!SetEvent(EventHandle)) {
        ErrorExit("SetEvent", GetLastError());
    }
}

int main()
{
    WCHAR* ServiceName = L"SERVICE NAME"; // Input service name here

    SC_HANDLE SCManager = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
    if (!SCManager) { 
        ErrorExit("OpenSCManagerW", GetLastError()); 
    }

    SC_HANDLE ServiceHandle = OpenServiceW(SCManager, ServiceName,
        SERVICE_ENUMERATE_DEPENDENTS | SERVICE_STOP | DELETE);
    if (!ServiceHandle) {
        ErrorExit("ServiceHandle", GetLastError());
    }

    if (!DeleteService(ServiceHandle)) {
        ErrorExit("DeleteService", GetLastError());
    }

    if (!CloseServiceHandle(ServiceHandle)) {
        ErrorExit("CloseServiceHandle", GetLastError());
    }

    HANDLE EventHandle = CreateEventW(NULL, TRUE, FALSE, NULL);
    if (!EventHandle) { 
        ErrorExit("CreateEventW", GetLastError()); 
    }

    SERVICE_NOTIFY ServiceNotify;
    ServiceNotify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
    ServiceNotify.pszServiceNames = ServiceName;
    ServiceNotify.pContext = EventHandle;
    ServiceNotify.pfnNotifyCallback = CallbackFunction;

    DWORD status = NotifyServiceStatusChangeW(SCManager, SERVICE_NOTIFY_DELETED, &ServiceNotify);
    if (status != ERROR_SUCCESS) { 
        ErrorExit("NotifyServiceStatusChangeW", GetLastError()); 
    }

    status = WaitForSingleObjectEx(EventHandle, INFINITE, TRUE);
    if (status == WAIT_FAILED) {
        ErrorExit("WaitForSingleObjectEx", GetLastError());
    }
    printf("WaitForSingleObjectEx Result: %lu\r\n", status);
    system("pause");
    return 0;
}

When I run this, no other service depends on the service being deleted, and the service being deleted is already stopped. My error handling function "ErrorExit" is never called. Nothing is printed on the screen. My program simply pauses, which I assume is from WaitForSingleObjectEx.

I know the service is being deleted because I have ProcessHacker open, and it is giving me notifications that the service is being deleted.

CodePudding user response:

NotifyServiceStatusChange is returning ERROR_SUCCESS (0). However, my callback function is not being called at all.

The NotifyServiceStatusChangeW documentation says:

When the service status changes, the system invokes the specified callback function as an asynchronous procedure call (APC) queued to the calling thread. The calling thread must enter an alertable wait (for example, by calling the SleepEx function) to receive notification. For more information, see Asynchronous Procedure Calls.

So, make sure you are actually processing APC notifications while you wait. WaitForSingleObject() will not do that for you.

Use WaitForSingleObjectEx() instead. It has a bAlertable parameter you can set to TRUE. You will have to call it in a loop, since it will return when any APC call is processed by the calling thread, which may not be the one you are expecting.

You also need to call NotifyServiceStatusChangeW() in a loop, too. The documentation does not mention this, but the callback will be called only 1 time per use. Once the callback is called, you need to call NotifyServiceStatusChangeW() again to receive another notification if the current one is not the event you are expecting.

With that said, try something more like this:

struct MyCallbackInfo {
    HANDLE EventHandle;
    LPCWSTR pszServiceName;
    bool bDeleted;
};

...

VOID CALLBACK CallbackFunction(PVOID pParameter) {
    SERVICE_NOTIFYW* ServiceNotify = (SERVICE_NOTIFYW*) pParameter;
    MyCallbackInfo *ci = (MyCallbackInfo*) ServiceNotify->pContext;

    if (ServiceNotify->dwNotificationStatus == ERROR_SUCCESS) {
        LPWSTR pServiceName = ServiceNotify->pszServiceNames;
        while (*pServiceName != L'\0') {
            if (lstrcmpW(pServiceName, ci->pszServiceName) == 0) {
                ci.bDeleted = true;
                break;
            }
            pServiceName  = (lstrlenW(pServiceName)   1);
        }
        LocalFree(ServiceNotify->pszServiceNames);
    }

    SetEvent(ci->EventHandle);
}

...

MyCallbackInfo ci;
ci.EventHandle = CreateEventW(NULL, TRUE, FALSE, NULL);
ci.pszServiceName = ServiceName;
ci.bDeleted = false;

if (!ci.EventHandle) {
    // error handling...
}

SC_HANDLE SCManager = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASE, 
     SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
if (!SCManager) {
    // error handling...
}

SERVICE_NOTIFYW ServiceNotify = {};
ServiceNotify.dwVersion = SERVICE_NOTIFY_STATUS_CHANGE;
ServiceNotify.pContext = &ci;
ServiceNotify.pfnNotifyCallback = CallbackFunction;

DWORD status;
do {
    status = NotifyServiceStatusChangeW(SCManager, SERVICE_NOTIFY_DELETED, &ServiceNotify);
    if (status != ERROR_SUCCESS) {
        // error handling...
    }

    while ((status = WaitForSingleObjectEx(ci.EventHandle, INFINITE, TRUE)) == WAIT_IO_COMPLETION);

    if (status != WAIT_OBJECT_0) {
        // error handling...
    }

    if (ci.bDeleted) {
        // service has been deleted ...
        break;
    }

    ResetEvent(ci.EventHandle);
}
while (true);

CloseServiceHandle(SCManager);
CloseHandle(ci.EventHandle);
  • Related