Do you still use the MessageBox API in your Windows Service?

Or do you display any type of User Interface?

Starting with Windows Vista and above, user interfaces generated by Windows services can’t be seen. And even worst, your service could be stuck waiting for some user input that the user cannot give as she does not see anything!

Why did we do that? Well it’s all explained in Impact of Session 0 Isolation on Services and Drivers in Windows Vista:

In Microsoft® Windows® XP, Microsoft Windows Server™ 2003, and earlier versions of the Windows operating system, all services run in the same session as the first user who logs on to the console. This session is called Session 0. Running services and user applications together in Session 0 poses a security risk because services run at elevated privilege and therefore are targets for malicious agents who are looking for a way to elevate their own privilege level.

The Microsoft Windows Vista™ operating system mitigates this security risk by isolating services in Session 0 and making Session 0 noninteractive. In Windows Vista, only system processes and services run in Session 0. The first user logs on to Session 1, and subsequent users log on to subsequent sessions. This means that services never run in the same session as users’ applications and are therefore protected from attacks that originate in application code.

You can no longer use the MB_SERVICE_NOTIFICATION flag when calling the MessageBox API. It’s useless on Vista and higher.

So how do you display a simple message from your Windows Service?

For simple interactions, services can display a message box in the user's session by calling WTSSendMessage. For more complex interactions, services must use an approach such as calling CreateProcessAsUser to create a UI process in the user's session. That process handles user interaction and communicates with the service through RPC or named pipes.

I will illustrate the simple case in this blog entry.

In the SimpleService I’m attaching, here is how I display a message when the event named "Global\\SimpleService.HelloWithWtsSendMessage" is set:

DWORD physicalConsoleSession = WTSGetActiveConsoleSessionId();

if (0xFFFFFFFF != physicalConsoleSession) {

   wostringstream b;

   b << L"Hello!" << endl

     << L"The active console session ID is 0x" << std::hex << physicalConsoleSession << L'.' << endl;

   LPWSTR title = const_cast<wchar_t *>(ServiceTable[0].lpServiceName);

   DWORD titleLength = static_cast<DWORD>(wcslen(title) * sizeof(*title));

   std::wstring messageString(b.str());

   LPWSTR message = const_cast<wchar_t *>(messageString.c_str());

   DWORD messageLength = static_cast<DWORD>(wcslen(message) * sizeof(*message));

   DWORD response;

   WTSSendMessage(WTS_CURRENT_SERVER_HANDLE,

                  physicalConsoleSession,

                  title, titleLength,

                  message, messageLength,

                  MB_OK | MB_ICONINFORMATION,

   0,

                  & response,

                  FALSE);

 

After building the service, don’t forget to give access to the executable (i.e. SimpleService.exe) to LOCAL SERVICE so that the SCM can reach it when you indicate that you want to start it!

You might have noticed that the event name is create in the global namespace. As the document clearly explains:

Because services run in Session 0, named objects created or opened by services are usually in \BaseNamedObjects\. However, if a user application assumes that it is running in the same session as the service and synchronizes with the service by creating or opening objects with the Local\ prefix (or no prefix, which defaults to Local\), the application no longer works as expected. This is because the create or open request is specific to that session (due to the Local\ prefix) and the objects that the application creates or opens are in \Sessions\<n>\BaseNamedObjects instead of \BaseNamedObjects\. The correct way for user applications to synchronize with a service is to explicitly use the Global\ prefix when creating or opening objects in \BaseNamedObjects\.

I also had to change the security around the event: only LocalService could touch it and I wanted applications running under other principals to be able to set/reset it:

// We give EVENT_MODIFY_STATE (0x0002) access to the Interactive User

// and

// GENERIC_ALL (GA) to the Local Service (LS) under which this service is installed/running by default

// Resources:

// - Security Descriptor Definition Language: https://msdn.microsoft.com/en-us/library/aa379567(VS.85).aspx

// - ConvertStringSecurityDescriptorToSecurityDescriptor: https://msdn.microsoft.com/en-us/library/aa376401%28VS.85%29.aspx

// - Synchronization Object Security and Access Rights: https://msdn.microsoft.com/en-us/library/ms686670%28VS.85%29.aspx

if (!ConvertStringSecurityDescriptorToSecurityDescriptor(L"D:(A;;0x00000002;;;IU)(A;;GA;;;LS)", SDDL_REVISION_1, & securityDescriptorPointer, NULL)) {

   goto cleanup;

}

SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), securityDescriptorPointer, FALSE};

// The name can have a "Global\" or "Local\" prefix to explicitly create the object in the global or session name space.

// The remainder of the name can contain any character except the backslash character (\).

serviceContext.HelloWithWtsSendMessageEvent = CreateEvent(&sa, FALSE, FALSE, L"Global\\SimpleService.HelloWithWtsSendMessage");

serviceContext.HelloWithMessageBoxEvent = CreateEvent(&sa, FALSE, FALSE, L"Global\\SimpleService.HelloWithMessageBox");

LocalFree(securityDescriptorPointer);

 

There’s a little application to test:

int wmain() {

   //Sleep(30000);

   HANDLE e = OpenEvent( EVENT_MODIFY_STATE, FALSE, L"Global\\SimpleService.HelloWithWtsSendMessage");

   if (e)

      SetEvent(e);

   else

      wcout << L"Error " << GetLastError() << L" calling OpenEvent()" << endl;

   return 0;

}

 

You can uncomment the Sleep call and once launched, hurry up and switch to another user. You can also change the event name in the test to "Global\\SimpleService.HelloWithMessageBox" and watch your service freeze…

Pay attention to my usage of _InterlockedAnd and _InterlockedOr: those can be very helpful.

As usual, any comment is welcomed.

À demain si on le veut bien.

SimpleService.zip