Winlogon notify的Vista移植
By MikeFeng
大家知道,在Windows XP和2000中,有个Winlogon notify的方法来接收logon,logoff事件。如果有些事情需要在登录注销时去做,那么使用notify技术可以很好的解决。但是,出于安全考虑,在vista下,原来的winlogon notify的功能被微软取消了。现在只能通过系统服务的方法来代替logon, logoff, sessionchange事件的接受与处理。在大部分情况下,这种方法可以当作把winlogon notify移植到vista的方法,并且这种方法是和原来的xp兼容的。但是有两个注意点:win2000是不支持sessionchange事件的,如果要将这个服务应用于win2000,将会出现莫名奇妙的问题。另外就是毕竟服务是在winlogon notify之后才起来的,如果这个logon/logoff/sessionchange事件必须在服务启动之前发生,那么就不能用这个方法了。
下面是一个用c++写的服务,它在logoff的时候写日志。这个服务可以用在xp,2000 SP4,vista中。但是处理SessionChange事件的服务只能用在xp,vista中。
//
SimService.cpp
//
#pragma comment (lib,"Secur32")
#define _WIN32_WINNT 0x6000
#include < windows.h >
#include < iostream >
#include < winuser.h >
using namespace std;
#define SERVICE_NAME "Vista Service For Logoff Event"
HANDLE terminateEvent = NULL;
SERVICE_STATUS_HANDLE serviceStatusHandle;
SERVICE_STATUS MyServiceStatus;
HANDLE threadHandle = NULL;
BOOL pauseService = FALSE;
BOOL runningService = FALSE;
BOOL InitService();
VOID terminate(DWORD error);
VOID ICSEventLogoff();
VOID ServiceMain(DWORD argc, LPTSTR * argv);
BOOL SendStatusToSCM(DWORD , DWORD , DWORD , DWORD , DWORD );
VOID HandlerEx(DWORD controlCode, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext);
void main( int argc, char * argv[])
{
BOOL success;
SERVICE_TABLE_ENTRY serviceTable[] =
{
{ SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
{ NULL, NULL }
};
//
// Register with the SCM
//
success = StartServiceCtrlDispatcher(serviceTable);
if ( ! success)
return ;
}
// ServiceMain is called when the SCM wants to
// start the service. When it returns, the service
// has stopped. It therefore waits on an event
// just before the end of the function, and
// that event gets set when it is time to stop.
// It also returns on any error because the
// service cannot start if there is an error.
VOID ServiceMain(DWORD argc, LPTSTR * argv)
{
BOOL success;
DWORD dwNum = 0 ;
MyServiceStatus.dwCurrentState = SERVICE_RUNNING;
serviceStatusHandle = RegisterServiceCtrlHandlerEx(SERVICE_NAME,
(LPHANDLER_FUNCTION_EX)HandlerEx, NULL);
if ( ! serviceStatusHandle) {terminate(GetLastError()); return ;}
// create the termination event
terminateEvent = CreateEvent ( 0 , TRUE, FALSE, 0 );
if ( ! terminateEvent) {terminate(GetLastError()); return ;}
// Start the service itself
success = InitService();
if ( ! success) {terminate(GetLastError()); return ;}
// The service is now running.
// Notify SCM of progress
MyServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP
| SERVICE_ACCEPT_SHUTDOWN
| SERVICE_ACCEPT_PAUSE_CONTINUE;
success = SendStatusToSCM(SERVICE_RUNNING, 0 , 0 , 0 , 0 );
if ( ! success) {terminate(GetLastError()); return ;}
MyServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP
| SERVICE_ACCEPT_SHUTDOWN
| SERVICE_ACCEPT_PAUSE_CONTINUE
| SERVICE_ACCEPT_SESSIONCHANGE;
success = SendStatusToSCM(SERVICE_RUNNING, 0 , 0 , 0 , 0 );
// Wait for stop signal, and then terminate
WaitForSingleObject (terminateEvent, INFINITE);
terminate( 0 );
}
// This function consolidates the activities of
// updating the service status with SetServiceStatus.
BOOL SendStatusToSCM(DWORD dwCurrentState,
DWORD dwWin32ExitCode,
DWORD dwServiceSpecificExitCode,
DWORD dwCheckPoint,
DWORD dwWaitHint)
{
BOOL success;
DWORD dwNum = 0 ;
// Fill in all of the SERVICE_STATUS fields
MyServiceStatus.dwServiceType = SERVICE_WIN32;
MyServiceStatus.dwCurrentState = dwCurrentState;
// Set the control codes the service can receive from SCM.
// Make sure that the code contains SERVICE_ACCEPT_SESSIONCHANGE.
// So service will can accept winlogon message.
/* if (dwCurrentState == SERVICE_START_PENDING)
MyServiceStatus.dwControlsAccepted = 0;
else
MyServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN|
SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SESSIONCHANGE; */
// if a specific exit code is defined, set up
// the win32 exit code properly
if (dwServiceSpecificExitCode == 0 )
MyServiceStatus.dwWin32ExitCode = dwWin32ExitCode;
else
MyServiceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
MyServiceStatus.dwServiceSpecificExitCode = dwServiceSpecificExitCode;
MyServiceStatus.dwCheckPoint = dwCheckPoint;
MyServiceStatus.dwWaitHint = dwWaitHint;
// Pass the status record to the SCM
success = SetServiceStatus (serviceStatusHandle, & MyServiceStatus);
return success;
}
DWORD ServiceThread(LPDWORD param)
{
while ( 1 )
{
Sleep( 1000 );
}
return 0 ;
}
// Initializes the service. Start a new thread
BOOL InitService()
{
DWORD id = 0 ;
int iTime = 0 ;
// Start the service's thread
while (NULL == threadHandle && iTime ++ < 10 )
{
threadHandle = CreateThread( 0 , 0 ,
(LPTHREAD_START_ROUTINE) ServiceThread, // callback function.
0 , 0 , & id );
}
if (threadHandle == 0 )
return FALSE;
else
{
runningService = TRUE;
return TRUE;
}
}
// Log..
void Log(LPCTSTR msg)
{
#define LOGFILE_PATH TEXT("C:/Log.txt")
HANDLE h = CreateFile(LOGFILE_PATH,
GENERIC_WRITE, FILE_SHARE_READ, 0 , OPEN_ALWAYS, 0 , 0 );
if (GetFileSize(h,NULL) == 0 )
{
byte b[ 2 ] = { 0xFF , 0xFE };
DWORD cb = 2 ;
WriteFile(h, b, 2 , & cb, 0 );
}
if (INVALID_HANDLE_VALUE != h)
{
if (INVALID_SET_FILE_POINTER != SetFilePointer(h, 0 , 0 , FILE_END)) {
DWORD cb = lstrlen(msg) * sizeof * msg;
WriteFile(h, msg, cb, & cb, 0 );
}
CloseHandle(h);
}
}
// Dispatches events received from the SCM
VOID HandlerEx(DWORD controlCode,
DWORD dwEventType,
LPVOID lpEventData,
LPVOID lpContext)
{
DWORD currentState = 0 ;
BOOL success;
DWORD dwNum = 0 ;
CHAR * tszWrite = NULL;
switch (controlCode)
{
case SERVICE_CONTROL_STOP:
// Stop the service
MyServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP
| SERVICE_ACCEPT_SHUTDOWN
| SERVICE_ACCEPT_PAUSE_CONTINUE;
success = SendStatusToSCM(SERVICE_STOP_PENDING, NO_ERROR, 0 , 1 , 5000 );
runningService = FALSE;
SetEvent(terminateEvent);
return ;
case SERVICE_CONTROL_PAUSE:
// Pause the service
if (runningService && ! pauseService)
{
success = SendStatusToSCM(SERVICE_PAUSE_PENDING, NO_ERROR, 0 , 1 , 1000 );
pauseService = TRUE;
SuspendThread(threadHandle);
currentState = SERVICE_PAUSED;
}
break ;
case SERVICE_CONTROL_CONTINUE:
// Resume from a pause
if (runningService && pauseService)
{
success = SendStatusToSCM(SERVICE_CONTINUE_PENDING, NO_ERROR, 0 , 1 , 1000 );
pauseService = FALSE;
ResumeThread(threadHandle);
currentState = SERVICE_RUNNING;
}
break ;
case SERVICE_CONTROL_SESSIONCHANGE:
switch (dwEventType)
{
case WTS_SESSION_LOGOFF:
// Logoff
Log( " Logoff event happened! " );
break ;
default :
break ;
}
break ;
case SERVICE_CONTROL_SHUTDOWN:
// Do nothing in a shutdown. Could do cleanup
// here but it must be very quick.
return ;
default :
break ;
}
SendStatusToSCM(currentState, NO_ERROR, 0 , 0 , 0 );
}
//
// Handle an error and stop service.
//
VOID terminate(DWORD error)
{
// Close terminateEvent.
if (terminateEvent) CloseHandle(terminateEvent);
// Send a message to the SCM to stop service.
if (serviceStatusHandle)
SendStatusToSCM(SERVICE_STOPPED, error, 0 , 0 , 0 );
// Close thread.
if (threadHandle) CloseHandle(threadHandle);
}
//
#pragma comment (lib,"Secur32")
#define _WIN32_WINNT 0x6000
#include < windows.h >
#include < iostream >
#include < winuser.h >
using namespace std;
#define SERVICE_NAME "Vista Service For Logoff Event"
HANDLE terminateEvent = NULL;
SERVICE_STATUS_HANDLE serviceStatusHandle;
SERVICE_STATUS MyServiceStatus;
HANDLE threadHandle = NULL;
BOOL pauseService = FALSE;
BOOL runningService = FALSE;
BOOL InitService();
VOID terminate(DWORD error);
VOID ICSEventLogoff();
VOID ServiceMain(DWORD argc, LPTSTR * argv);
BOOL SendStatusToSCM(DWORD , DWORD , DWORD , DWORD , DWORD );
VOID HandlerEx(DWORD controlCode, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext);
void main( int argc, char * argv[])
{
BOOL success;
SERVICE_TABLE_ENTRY serviceTable[] =
{
{ SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
{ NULL, NULL }
};
//
// Register with the SCM
//
success = StartServiceCtrlDispatcher(serviceTable);
if ( ! success)
return ;
}
// ServiceMain is called when the SCM wants to
// start the service. When it returns, the service
// has stopped. It therefore waits on an event
// just before the end of the function, and
// that event gets set when it is time to stop.
// It also returns on any error because the
// service cannot start if there is an error.
VOID ServiceMain(DWORD argc, LPTSTR * argv)
{
BOOL success;
DWORD dwNum = 0 ;
MyServiceStatus.dwCurrentState = SERVICE_RUNNING;
serviceStatusHandle = RegisterServiceCtrlHandlerEx(SERVICE_NAME,
(LPHANDLER_FUNCTION_EX)HandlerEx, NULL);
if ( ! serviceStatusHandle) {terminate(GetLastError()); return ;}
// create the termination event
terminateEvent = CreateEvent ( 0 , TRUE, FALSE, 0 );
if ( ! terminateEvent) {terminate(GetLastError()); return ;}
// Start the service itself
success = InitService();
if ( ! success) {terminate(GetLastError()); return ;}
// The service is now running.
// Notify SCM of progress
MyServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP
| SERVICE_ACCEPT_SHUTDOWN
| SERVICE_ACCEPT_PAUSE_CONTINUE;
success = SendStatusToSCM(SERVICE_RUNNING, 0 , 0 , 0 , 0 );
if ( ! success) {terminate(GetLastError()); return ;}
MyServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP
| SERVICE_ACCEPT_SHUTDOWN
| SERVICE_ACCEPT_PAUSE_CONTINUE
| SERVICE_ACCEPT_SESSIONCHANGE;
success = SendStatusToSCM(SERVICE_RUNNING, 0 , 0 , 0 , 0 );
// Wait for stop signal, and then terminate
WaitForSingleObject (terminateEvent, INFINITE);
terminate( 0 );
}
// This function consolidates the activities of
// updating the service status with SetServiceStatus.
BOOL SendStatusToSCM(DWORD dwCurrentState,
DWORD dwWin32ExitCode,
DWORD dwServiceSpecificExitCode,
DWORD dwCheckPoint,
DWORD dwWaitHint)
{
BOOL success;
DWORD dwNum = 0 ;
// Fill in all of the SERVICE_STATUS fields
MyServiceStatus.dwServiceType = SERVICE_WIN32;
MyServiceStatus.dwCurrentState = dwCurrentState;
// Set the control codes the service can receive from SCM.
// Make sure that the code contains SERVICE_ACCEPT_SESSIONCHANGE.
// So service will can accept winlogon message.
/* if (dwCurrentState == SERVICE_START_PENDING)
MyServiceStatus.dwControlsAccepted = 0;
else
MyServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN|
SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SESSIONCHANGE; */
// if a specific exit code is defined, set up
// the win32 exit code properly
if (dwServiceSpecificExitCode == 0 )
MyServiceStatus.dwWin32ExitCode = dwWin32ExitCode;
else
MyServiceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
MyServiceStatus.dwServiceSpecificExitCode = dwServiceSpecificExitCode;
MyServiceStatus.dwCheckPoint = dwCheckPoint;
MyServiceStatus.dwWaitHint = dwWaitHint;
// Pass the status record to the SCM
success = SetServiceStatus (serviceStatusHandle, & MyServiceStatus);
return success;
}
DWORD ServiceThread(LPDWORD param)
{
while ( 1 )
{
Sleep( 1000 );
}
return 0 ;
}
// Initializes the service. Start a new thread
BOOL InitService()
{
DWORD id = 0 ;
int iTime = 0 ;
// Start the service's thread
while (NULL == threadHandle && iTime ++ < 10 )
{
threadHandle = CreateThread( 0 , 0 ,
(LPTHREAD_START_ROUTINE) ServiceThread, // callback function.
0 , 0 , & id );
}
if (threadHandle == 0 )
return FALSE;
else
{
runningService = TRUE;
return TRUE;
}
}
// Log..
void Log(LPCTSTR msg)
{
#define LOGFILE_PATH TEXT("C:/Log.txt")
HANDLE h = CreateFile(LOGFILE_PATH,
GENERIC_WRITE, FILE_SHARE_READ, 0 , OPEN_ALWAYS, 0 , 0 );
if (GetFileSize(h,NULL) == 0 )
{
byte b[ 2 ] = { 0xFF , 0xFE };
DWORD cb = 2 ;
WriteFile(h, b, 2 , & cb, 0 );
}
if (INVALID_HANDLE_VALUE != h)
{
if (INVALID_SET_FILE_POINTER != SetFilePointer(h, 0 , 0 , FILE_END)) {
DWORD cb = lstrlen(msg) * sizeof * msg;
WriteFile(h, msg, cb, & cb, 0 );
}
CloseHandle(h);
}
}
// Dispatches events received from the SCM
VOID HandlerEx(DWORD controlCode,
DWORD dwEventType,
LPVOID lpEventData,
LPVOID lpContext)
{
DWORD currentState = 0 ;
BOOL success;
DWORD dwNum = 0 ;
CHAR * tszWrite = NULL;
switch (controlCode)
{
case SERVICE_CONTROL_STOP:
// Stop the service
MyServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP
| SERVICE_ACCEPT_SHUTDOWN
| SERVICE_ACCEPT_PAUSE_CONTINUE;
success = SendStatusToSCM(SERVICE_STOP_PENDING, NO_ERROR, 0 , 1 , 5000 );
runningService = FALSE;
SetEvent(terminateEvent);
return ;
case SERVICE_CONTROL_PAUSE:
// Pause the service
if (runningService && ! pauseService)
{
success = SendStatusToSCM(SERVICE_PAUSE_PENDING, NO_ERROR, 0 , 1 , 1000 );
pauseService = TRUE;
SuspendThread(threadHandle);
currentState = SERVICE_PAUSED;
}
break ;
case SERVICE_CONTROL_CONTINUE:
// Resume from a pause
if (runningService && pauseService)
{
success = SendStatusToSCM(SERVICE_CONTINUE_PENDING, NO_ERROR, 0 , 1 , 1000 );
pauseService = FALSE;
ResumeThread(threadHandle);
currentState = SERVICE_RUNNING;
}
break ;
case SERVICE_CONTROL_SESSIONCHANGE:
switch (dwEventType)
{
case WTS_SESSION_LOGOFF:
// Logoff
Log( " Logoff event happened! " );
break ;
default :
break ;
}
break ;
case SERVICE_CONTROL_SHUTDOWN:
// Do nothing in a shutdown. Could do cleanup
// here but it must be very quick.
return ;
default :
break ;
}
SendStatusToSCM(currentState, NO_ERROR, 0 , 0 , 0 );
}
//
// Handle an error and stop service.
//
VOID terminate(DWORD error)
{
// Close terminateEvent.
if (terminateEvent) CloseHandle(terminateEvent);
// Send a message to the SCM to stop service.
if (serviceStatusHandle)
SendStatusToSCM(SERVICE_STOPPED, error, 0 , 0 , 0 );
// Close thread.
if (threadHandle) CloseHandle(threadHandle);
}
另外在.net framework 1.1中,我们是没有办法支持OnSessionChange事件的,而在.net framework 2.0中的ServiceBase有了更好的扩展性,可以支持OnSessionChange。
使用Reflector观察.Net Framework 1.1.4322。打开%WINDIR%/Microsoft.NET/Framework/v1.1.4322/System.ServiceProcess.dll,查看System.ServiceProcess.ServiceBase类。它有以下两个私有成员函数,相当于C++ Service中的给RegisterServiceCtrlHandlerEx传递的回调函数(LPHANDLER_FUNCTION_EX)


































































































































从上面的代码可以知道,只有当服务控制码command不等于13的时候,我们才有机会用ServiceCommandCallback对其进行处理。而这种处理只能获得控制码command,对于SERVICE_CONTROL_SESSIONCHANGE(14)的消息来说,ServiceBase类没有办法获得到底是Logon,Logoff或是用户切换时候发生的。
因此对于以下C++代码是没有办法移植到Framework 1.1 上的:























在.Net Framework 2.0的ServiceBase中,回调函数在接受服务控制命令command之外,还将所有其他参数都传给了用户可以自由扩展的回调函数,多了OnSessionChange等包装好的函数,因此就没有这个问题了。同样,我们不用将这个服务用于不支持sessionchange的Win2000上。以下是C#写的服务:






















































安装服务可以用一个叫SRVINSTW.EXE的小工具,很方便。微软的instalutil.exe命令行真难用,bs一下。