Winlogon notify的Vista移植

                      Winlogon notify的Vista移植

By MikeFeng  QQ: 76848502

大家知道,在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, 0000);
    
if (!success) {terminate(GetLastError()); return;}


    MyServiceStatus.dwControlsAccepted 
= SERVICE_ACCEPT_STOP 
        
| SERVICE_ACCEPT_SHUTDOWN
        
| SERVICE_ACCEPT_PAUSE_CONTINUE 
        
| SERVICE_ACCEPT_SESSIONCHANGE; 
    success 
= SendStatusToSCM(SERVICE_RUNNING, 0000);

    
// 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(00,
            (LPTHREAD_START_ROUTINE) ServiceThread,
//callback function.
            00&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, 00);

    
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, 00, 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, 015000);
        runningService
=FALSE;
        SetEvent(terminateEvent);
        
return;

    
case SERVICE_CONTROL_PAUSE:
        
// Pause the service
        if (runningService && !pauseService)
        {
            success 
= SendStatusToSCM(SERVICE_PAUSE_PENDING, NO_ERROR, 011000);
            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, 011000);
            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, 
000);
}

//
// 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, 
000);

    
// 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)
private int ServiceCommandCallbackEx(int command, int eventType, IntPtr eventData, IntPtr eventContext)
{
    
if (command != 13)
    
{
        
this.ServiceCommandCallback(command);
    }

    
else
    
{
        
try
        
{
            PowerBroadcastStatus powerStatus 
= (PowerBroadcastStatus) eventType;
            
bool flag = this.OnPowerEvent(powerStatus);
            
this.WriteEventLogEntry(Res.GetString("PowerEventOK"));
            
if (!flag)
            
{
                
return 0x424d5144;
            }

        }

        
catch (Exception exception)
        
{
            
this.WriteEventLogEntry(Res.GetString("PowerEventFailed"new object[] { exception.ToString() }), EventLogEntryType.Error);
        }

    }

    
return 0;
}


private unsafe void ServiceCommandCallback(int command)
{
    
fixed (NativeMethods.SERVICE_STATUS* service_statusRef = &this.status)
    
{
        
if (command == 4)
        
{
            NativeMethods.SetServiceStatus(
this.statusHandle, service_statusRef);
        }

        
else if (((this.status.currentState != 5&& (this.status.currentState != 2)) && ((this.status.currentState != 3&& (this.status.currentState != 6)))
        
{
            
switch (command)
            
{
                
case 1:
                
{
                    
int currentState = this.status.currentState;
                    
if ((this.status.currentState == 7|| (this.status.currentState == 4))
                    
{
                        
this.status.currentState = 3;
                        NativeMethods.SetServiceStatus(
this.statusHandle, service_statusRef);
                        
this.status.currentState = currentState;
                        
new DeferredHandlerDelegate(this.DeferredStop).BeginInvoke(nullnull);
                    }

                    
goto Label_0259;
                }

                
case 2:
                    
if (this.status.currentState == 4)
                    
{
                        
this.status.currentState = 6;
                        NativeMethods.SetServiceStatus(
this.statusHandle, service_statusRef);
                        
new DeferredHandlerDelegate(this.DeferredPause).BeginInvoke(nullnull);
                    }

                    
goto Label_0259;

                
case 3:
                    
if (this.status.currentState == 7)
                    
{
                        
this.status.currentState = 5;
                        NativeMethods.SetServiceStatus(
this.statusHandle, service_statusRef);
                        
try
                        
{
                            
this.OnContinue();
                            
this.WriteEventLogEntry(Res.GetString("ContinueSuccessful"));
                        }

                        
catch (Exception exception)
                        
{
                            
this.WriteEventLogEntry(Res.GetString("ContinueFailed"new object[] { exception.ToString() }), EventLogEntryType.Error);
                            
this.status.currentState = 7;
                            
goto Label_0259;
                        }

                        
this.status.currentState = 4;
                        NativeMethods.SetServiceStatus(
this.statusHandle, service_statusRef);
                    }

                    
goto Label_0259;

                
case 5:
                    
try
                    
{
                        
this.OnShutdown();
                        
this.WriteEventLogEntry(Res.GetString("ShutdownOK"));
                    }

                    
catch (Exception exception2)
                    
{
                        
this.WriteEventLogEntry(Res.GetString("ShutdownFailed"new object[] { exception2.ToString() }), EventLogEntryType.Error);
                    }

                    
goto Label_0259;
            }

            
try
            
{
                
this.OnCustomCommand(command);
                
this.WriteEventLogEntry(Res.GetString("CommandSuccessful"));
            }

            
catch (Exception exception3)
            
{
                
this.WriteEventLogEntry(Res.GetString("CommandFailed"new object[] { exception3.ToString() }), EventLogEntryType.Error);
            }

        }

    }

Label_0259:;
}

      從上面的代碼可以知道,只有當服務控制碼command不等於13的時候,我們纔有機會用ServiceCommandCallback對其進行處理。而這種處理只能獲得控制碼command,對於SERVICE_CONTROL_SESSIONCHANGE(14)的消息來說,ServiceBase類沒有辦法獲得到底是Logon,Logoff或是用戶切換時候發生的。
      因此對於以下C++代碼是沒有辦法移植到Framework 1.1 上的:

VOID HandlerEx(DWORD controlCode,
               DWORD dwEventType,
               LPVOID lpEventData,
               LPVOID lpContext)
{
    ...
    
case SERVICE_CONTROL_SESSIONCHANGE:
        
switch(dwEventType)
        
{
        
case WTS_SESSION_LOGOFF:
            
// Logoff
            ICSEventLogoff();
            
break;
        
default:
            
break;

        }

        
break;
    ...
}

      在.Net Framework 2.0的ServiceBase中,回調函數在接受服務控制命令command之外,還將所有其他參數都傳給了用戶可以自由擴展的回調函數,多了OnSessionChange等包裝好的函數,因此就沒有這個問題了。同樣,我們不用將這個服務用於不支持sessionchange的Win2000上。以下是C#寫的服務:

 

using System;
using System.ServiceProcess;
using System.Text;
using System.IO;

namespace ServiceTest
{
    
public partial class Service1 : ServiceBase
    
{
        
public Service1()
        
{
            InitializeComponent();
        }


        
protected override void OnStart(string[] args)
        
{
            
// TODO: Add code here to start your service.
            this.CanHandleSessionChangeEvent = true;
        }


        
protected override void OnStop()
        
{
            
// TODO: Add code here to perform any tear-down necessary to stop your service.
            this.CanHandleSessionChangeEvent = false;
            System.IO.FileStream fs 
= new System.IO.FileStream("c:/mysvc.txt",
            System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite, System.IO.FileShare.ReadWrite);
            UTF8Encoding asc 
= new UTF8Encoding();
            String log 
= "stop ";
            fs.Seek(
0, System.IO.SeekOrigin.End);
            fs.Write(asc.GetBytes(log), 
0, asc.GetByteCount(log));
            fs.Close();
        }


        
protected override void OnSessionChange(SessionChangeDescription changeDescription)
        
{
            
base.OnSessionChange(changeDescription);

            FileStream fs 
= new FileStream("c:/Log.txt",
                FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
            UTF8Encoding asc 
= new UTF8Encoding();
            String log 
= "On Session Change ";
            fs.Seek(
0, System.IO.SeekOrigin.End);
            fs.Write(asc.GetBytes(log), 
0, asc.GetByteCount(log));
            fs.Close();
        }

    }

}

      安裝服務可以用一個叫SRVINSTW.EXE的小工具,很方便。微軟的instalutil.exe命令行真難用,bs一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章