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, 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)
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(null, null);
                    }
                    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(null, null);
                    }
                    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一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值