Wednesday, July 6, 2011

ISensLogon без использования ATL

На RSDN есть статья, описывающая отлов событий Logon/Logoff при помощи механизма Winlogon. Недостаток подхода в том, что Winlogon не поддерживается, начиная с Windows Vista.

Другой способ, который работает во всех версиях Windows, начиная с Windows 2000 и включая Windows 7, использует службу уведомлений о системных событиях SENS (System Event Notification Services). Пример в msdn показывает реализацию с использованием ATL, а я ниже привожу отлова событий, описываемых интерфейсом ISensLogon, без использования ATL. В моем случае приложение работает в качестве службы Windows. Оно отлавливает события входа/выхода пользователя, запуска оболочки, блокировки/разблокирования экрана, запуска/остановки хранителя экрана. В тестовом примере при возникновении этих событий просто делаются записи в лог с названиями этих событий.

SensLogonHandler.h
class MyISensLogon;
class IEventSystem;
class IEventSubscription;

class CSensLogonHandler
{
    MyISensLogon *pISensLogon;
    IEventSystem *pIEventSystem;
    IEventSubscription* pIEventSubscription;
public:
    SensLogonHandler();
    virtual ~SensLogonHandler();
private:
    void Register();
    void Unregister();
};

SensLogonHandler.cpp
#include "SensLogonHandler.h"
#include <windows.h>
#include <eventsys.h>
#include <Sensevts.h>

void LogWrite(const char *str)
{
    FILE *fp = fopen("c:\\sens.log", "a");
    if (fp) {
        fwrite(str, 1, strlen(str), fp);
        fclose(fp);
    }
}

//************************* Message handling interface *****************************
interface MyISensLogon : public ISensLogon//IDispatch
{
private:
    long ref;
  
public:
    MyISensLogon()
    {
        ref = 1;
    }

    STDMETHODIMP QueryInterface(REFIID iid, void ** ppv)
    {
        if (IsEqualIID(iid, IID_IUnknown) || IsEqualIID(iid, IID_IDispatch) || IsEqualIID(iid, IID_ISensLogon)) {
            *ppv = this;
            AddRef();
            return S_OK;
        }

        *ppv = NULL;
        return E_NOINTERFACE;
    }

    ULONG STDMETHODCALLTYPE AddRef()
    {
        return InterlockedIncrement(&ref);
    }

    ULONG STDMETHODCALLTYPE Release()
    {
        int tmp = InterlockedDecrement(&ref);
        return tmp;
    }

    HRESULT STDMETHODCALLTYPE GetTypeInfoCount(unsigned int FAR* pctinfo)
    {
        return E_NOTIMPL;
    }

    HRESULT STDMETHODCALLTYPE GetTypeInfo(unsigned int iTInfo, LCID lcid, ITypeInfo FAR* FAR* ppTInfo)
    {
        return E_NOTIMPL;
    }

    HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, OLECHAR FAR* FAR* rgszNames, unsigned int cNames, LCID lcid, DISPID FAR* rgDispId)
    {
        return E_NOTIMPL;
    }

    HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR* pDispParams, VARIANT FAR* parResult, EXCEPINFO FAR* pExcepInfo, unsigned int FAR* puArgErr)
    {
        return E_NOTIMPL;
    }

    // ISensLogon methods:
    virtual HRESULT STDMETHODCALLTYPE Logon(BSTR bstrUserName)
    {
        LogWrite("Logon\n");
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE Logoff(BSTR bstrUserName)
    {
        LogWrite("Logoff\n");
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE StartShell(BSTR bstrUserName)
    {
        LogWrite("StartShell\n");
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE DisplayLock(BSTR bstrUserName)
    {
        LogWrite("DisplayLock\n");
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE DisplayUnlock(BSTR bstrUserName)
    {
        LogWrite("DisplayUnlock\n");
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE StartScreenSaver(BSTR bstrUserName)
    {
        LogWrite("StartScreenSaver\n");
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE StopScreenSaver(BSTR bstrUserName)
    {
        LogWrite("StopScreenSaver\n");
        return S_OK;
    }
};


static wchar_t* methods[]={L"Logon", L"Logoff", L"StartShell", L"DisplayLock", L"DisplayUnlock", L"StartScreenSaver", L"StopScreenSaver"};
static wchar_t* names[]={L"HB_System_Logon", L"HB_System_Logoff", L"HB_StartShell", L"HB_DisplayLock", L"HB_DisplayUnlock",
 L"HB_StartScreenSaver", L"HB_StopScreenSaver"};
static wchar_t* subids[]={L"{D2FE562E-139B-490F-A31C-4F0F7CD82677}", L"{C4CEE207-5021-4948-99EA-DE6D8E537DB3}",
 L"{77405340-F779-4E3C-B2D6-E9890B19333D}", L"{EFAD2171-191D-48AF-875D-7468BB3A8051}", L"{51A05BC8-BDCC-475F-BBF5-8DFCDB9C824C}", 
 L"{AE090FB3-4539-4FF1-92DC-BEA7BF817A6A}", L"{2F03C69D-A22C-419D-87C8-A2BA764D6414}", L"{E2C86015-B91F-4928-ABD6-F569064EB5F5}", 
 L"{123F30C3-762B-4FAA-869B-07A50D8789D4}"};

CSensLogonHandler::CSensLogonHandler()
{
    pISensLogon = 0;
    pIEventSystem = 0;
    pIEventSubscription = 0;

    Register();
}

CSensLogonHandler::~CSensLogonHandler()
{
    Unregister();
}

void CSensLogonHandler::Register()
{
    HRESULT res;

    res = CoCreateInstance(CLSID_CEventSystem, 0, CLSCTX_SERVER, IID_IEventSystem, (void**)&pIEventSystem);
    if (res != S_OK || !pIEventSystem)
        return;

    pISensLogon= new(std::nothrow) MyISensLogon();
    if (!pISensLogon)
        return;

    for (int i = 0; i < 7; ++i) {
        res = CoCreateInstance(CLSID_CEventSubscription, 0, CLSCTX_SERVER, IID_IEventSubscription, (LPVOID*)&pIEventSubscription);
        if (res != S_OK)
            continue;

        res = pIEventSubscription->put_EventClassID(L"{d5978630-5b9f-11d1-8dd2-00aa004abd5e}"); // SENSGUID_EVENTCLASS_LOGON from Sens.h
        if (res != S_OK)
            break;
        res = pIEventSubscription->put_SubscriberInterface((IUnknown*)pISensLogon);
        if (res != S_OK)
            break;

        res = pIEventSubscription->put_MethodName(methods[i]);
        if (res != S_OK)
            break;
        res = pIEventSubscription->put_SubscriptionName(names[i]);
        if (res != S_OK)
            break;
        res = pIEventSubscription->put_SubscriptionID(subids[i]);
        if (res != S_OK)
            break;
        res = pIEventSubscription->put_PerUser(TRUE);
        if (res != S_OK)
            break;

        res = pIEventSystem->Store(PROGID_EventSubscription, (IUnknown*)pIEventSubscription);
        if (res != S_OK)
            break;

        pIEventSubscription->Release();
        pIEventSubscription = 0;
    }
}

void CSensLogonHandler::Unregister()
{
    HRESULT res;
    int errorIndex;
    wchar_t nom[64];
    lstrcpy(nom, L"SubscriptionID=");

    for (int i = 0; i < 7; ++i) {
        lstrcat(nom + 15, subids[i]);
        res = pIEventSystem->Remove(PROGID_EventSubscription, nom, &errorIndex);
        nom[15] = 0;
    }

    pIEventSystem->Release();

    if (pISensLogon)
        delete pISensLogon;
}

Напоследок ссылка на документ, в котором говорится о том, что в Windows Vista прекращена поддержка Winlogon, и о том, какие вообще есть способы для отлова события входа пользователя в систему.

4 comments:

Louie Athanasiadis said...

How do you compile this and how do you get it to run?

Dmitry I Yastrebkov said...

I'm using Visual Studio. Here you see ISensLogon wrapper class only, but it is fully functional and you can embed it into your application

Louie Athanasiadis said...

Just rewriting a (7 year old) application that used Notifications in XP to record how long PhD students spend on computers. Any student how spend 70 hours per month or more gets their own personal workstation otherwise they have to use shared machines. Since Notification are not supported after XP, I need to write a new application to record the timestamped events of Logon/off, Workstation Un/Lock and screen saver start/stop. To figure out how long students use the work stations. The log files are auto copied to a central server and another program produces an Excel file so the research manager can use Pivot tables to determine usage.

You code will be a good starting point for this project

Dmitry I Yastrebkov said...

Good luck! But I also can suggest you to try to find ready-to-use solution, suppose it exists. Any event logger, which output you can grab