?
Solved

Detect user login (like WM_USERCHANGED)

Posted on 2003-02-23
20
Medium Priority
?
7,334 Views
Last Modified: 2007-12-19
Hi Experts,

I'm doing a Service and this NT-service is interacting with the desktop.
I need to know when then user login to the system OR when the taskbar is created.

I know that WM_USERCHANGED wont work on Win2000 or XP - so how do i trap system login ??

I have read a lot about this issue on Google-Groups, but i didn't found anything there that really could help me out.

Peter
0
Comment
Question by:PeterLarsen
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 8
  • 5
  • 3
  • +3
20 Comments
 
LVL 3

Expert Comment

by:smot
ID: 8004440
You might want to look at the
WTSRegisterSessionNotification() API to receive session switch notification. (consult msdn)
0
 
LVL 2

Author Comment

by:PeterLarsen
ID: 8015112
But WTSRegisterSessionNotification/WM_WTSSESSION_CHANGE only works with XP right !?!
0
 
LVL 3

Expert Comment

by:smot
ID: 8019360
yes, right.
0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 2

Author Comment

by:PeterLarsen
ID: 8023843
I dont get it.
Is it really necessary to use 3 different methods to detect system login - one for Win 95/98/NT4, one for Win2000 and one for XP ???
0
 

Expert Comment

by:Fallen_Knight
ID: 8023961
http://www.pcmag.com/article2/0,4149,93230,00.asp

this guy did it somehow, the source for the program he wrote is availble to download. not sure how much this will help but its worth a shot. (its in C++ but i followed your link in the c++ section to here)
0
 
LVL 12

Assisted Solution

by:Salte
Salte earned 300 total points
ID: 8023978
Peter,

Don't you just love Microsoft?

Afraid you have to use 3 methods to detect it.

You should probably pack that in a streamlined interface.

You could try to use the other 2 methods (Win2K and WinXP methods), detect that user has changed and instead of handling it immediately you send a WM_USERCHANGED message to yourself. That way you get the message also for Win2000 and WinXP and you can use common code to handle the situation.

If you need to do different things in those 3 types of systems you probably should do the handling in each of the functions anyway. In that case you might want to make that a separate DLL and create 3 DLLs, one for Win95/98 etc and one for Win2000 and one for WinXP and at the beginning of your program test what system you are running on and load the correct DLL and call the DLL to handle the system specific stuff.

Alf
0
 
LVL 86

Accepted Solution

by:
jkr earned 300 total points
ID: 8025287
What about using Winlogon Notification Packages? You'd basically build a Dll that exports

//Event handler for the Winlogon Logon event
VOID WLEventLogon (PWLX_NOTIFICATION_INFO pInfo)
{
    //Print the name of the handler to debug output.
    //You can replace this with more useful functionality.
    OutputDebugString (TEXT("NOTIFY:  Entering WLEventLogon.\r\n"));
}

//Event handler for the Winlogon Logoff event.
VOID WLEventLogoff (PWLX_NOTIFICATION_INFO pInfo)
{
    //Print the name of the handler to debug output.
    //You can replace this with more useful functionality.
    OutputDebugString (TEXT("NOTIFY:  Entering WLEventLogff.\r\n"));
}

and register it under build that dll and register it under HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify as

DllName = mynotify.dll
Logon = "WLEventLogon"
Logoff = "WLEventLogoff"

See also http://msdn.microsoft.com/library/default.asp?url=/library/en-us/security/security/winlogon_notification_package_reference.asp ("Winlogon Notification Package Reference")
0
 
LVL 12

Expert Comment

by:Salte
ID: 8025660
Think jkr's solution might work.

You might even have those functions send a WM_USERCHANGED event to yourself - i.e. to your server's message queue, that way you can have the same interface as in Win95 etc.

If WM_USERCHANGED isn't defined when building your service just #define  (or use an enum) and use a value that isn't used by windows or you already.

Probably not WM_USER since WM_USER area is used by windows own controls. It is ok if you are sure that none of those windows ever get the WM_USERCHANGED but if they do and WM_USERCHANGE is defined equal to WM_USER or something like that they are likely to misunderstand the message.

Alf
0
 
LVL 2

Author Comment

by:PeterLarsen
ID: 8028173
Thank you for your comments.

There are several ideas here and right now i'm working on a solution where i am using WM_USERCHANGED for Win95/98/ME and for WinNT/2000/XP i'm trying Winlogon Notification Packages.
I dont know whether Winlogin notification will work with XP Fast-User-Switching or not - i really hope i does.

Best regards
Peter
0
 
LVL 3

Assisted Solution

by:smot
smot earned 200 total points
ID: 8028214
For XP, you can use this code I wrote some time ago:

{
  Typically, an application does not need to be notified when a session switch
  occurs. However, if the application needs to be aware when its desktop is
  current, it can register for session switch notifications. Applications that
  access the serial port or another shared resource on the computer should
  check for this. To register for a notification, use the following function:
}

  function WTSRegisterSessionNotification(
      hWnd: HWND ,    // Window handle
      dwFlags: DWORD  // Flags
      ): Bool; // Return value

{
  The registered HWND receives the message WM_WTSSESSION_CHANGE
  through its WindowProc function.

  In dwFlags you can specify:

    a) NOTIFY_FOR_THIS_SESSION. A window is notified only about the session
      change events that affect the session to which window belongs.

    b) NOTIFY_FOR_ALL_SESSIONS. A window is notified for all session change
      events.

  The action happening on the session can be found in wParam code, which may
  contain one of the following flags.

  WTS_CONSOLE_CONNECT:        A session was connected to the console session.
  WTS_CONSOLE_DISCONNECT:     A session was disconnected from the console session.
  WTS_REMOTE_CONNECT:         A session was connected to the remote session.
  WTS_REMOTE_DISCONNECT:      A session was disconnected from the remote session.
  WTS_SESSION_LOGON:          A user has logged on to the session.
  WTS_SESSION_LOGOFF:         A user has logged off the session.
  WTS_SESSION_LOCK:           A session has been locked.
  WTS_SESSION_UNLOCK:         A session has been unlocked.
  WTS_SESSION_REMOTE_CONTROL: A session has changed its remote controlled status.


  lParam contains the sessionId for the session affected.

  When your process no longer requires these notifications or is terminating,
  it should call the following to unregister its notification.

}
  function WTSUnRegisterSesssionNotification(
    hWnd: HWND // window handle.
    ): Boolean; // Result

{

  The HWND values passed to WTSRegisterSessionNotification are reference
  counted, so you must call WTSUnRegisterSessionNotification exactly the same
  number of times that you call WTSRegisterSessionNotification.

  Applications can use the WTS_CONSOLE_CONNECT, WTS_CONSOLE_DISCONNECT,
  WTS_REMOTE_CONNECT, WTS_REMOTE_DISCONNECT messages to track their state, as
  well as to release and acquire console specific resources.
}

unit Wtsapi;

interface

{ (c) By Thomas Stutz 10. April 02 }

uses
  Windows;

const
  // The WM_WTSSESSION_CHANGE message notifies applications of changes in session state.
  WM_WTSSESSION_CHANGE = $2B1;

  // wParam values:
  WTS_CONSOLE_CONNECT = 1;
  WTS_CONSOLE_DISCONNECT = 2;
  WTS_REMOTE_CONNECT = 3;
  WTS_REMOTE_DISCONNECT = 4;
  WTS_SESSION_LOGON = 5;
  WTS_SESSION_LOGOFF = 6;
  WTS_SESSION_LOCK = 7;
  WTS_SESSION_UNLOCK = 8;
  WTS_SESSION_REMOTE_CONTROL = 9;

  // Only session notifications involving the session attached to by the window
  // identified by the hWnd parameter value are to be received.
  NOTIFY_FOR_THIS_SESSION = 0;
  // All session notifications are to be received.
  NOTIFY_FOR_ALL_SESSIONS = 1;


function RegisterSessionNotification(Wnd: HWND; dwFlags: DWORD): Boolean;
function UnRegisterSessionNotification(Wnd: HWND): Boolean;
function GetCurrentSessionID: Integer;

implementation

function RegisterSessionNotification(Wnd: HWND; dwFlags: DWORD): Boolean;
  // The RegisterSessionNotification function registers the specified window
  // to receive session change notifications.
  // Parameters:
  // hWnd: Handle of the window to receive session change notifications.
  // dwFlags: Specifies which session notifications are to be received:
  // (NOTIFY_FOR_THIS_SESSION, NOTIFY_FOR_ALL_SESSIONS)
type
  TWTSRegisterSessionNotification = function(Wnd: HWND; dwFlags: DWORD): BOOL; stdcall;
var
  hWTSapi32dll: THandle;
  WTSRegisterSessionNotification: TWTSRegisterSessionNotification;
begin
  Result := False;
  hWTSAPI32DLL := LoadLibrary('Wtsapi32.dll');
  if (hWTSAPI32DLL > 0) then
  begin
    try @WTSRegisterSessionNotification :=
        GetProcAddress(hWTSAPI32DLL, 'WTSRegisterSessionNotification');
      if Assigned(WTSRegisterSessionNotification) then
      begin
        Result:= WTSRegisterSessionNotification(Wnd, dwFlags);
      end;
    finally
      if hWTSAPI32DLL > 0 then
        FreeLibrary(hWTSAPI32DLL);
    end;
  end;
end;

function UnRegisterSessionNotification(Wnd: HWND): Boolean;
  // The RegisterSessionNotification function unregisters the specified window
  // Parameters:
  // hWnd: Handle to the window
type
  TWTSUnRegisterSessionNotification = function(Wnd: HWND): BOOL; stdcall;
var
  hWTSapi32dll: THandle;
  WTSUnRegisterSessionNotification: TWTSUnRegisterSessionNotification;
begin
  Result := False;
  hWTSAPI32DLL := LoadLibrary('Wtsapi32.dll');
  if (hWTSAPI32DLL > 0) then
  begin
    try @WTSUnRegisterSessionNotification :=
        GetProcAddress(hWTSAPI32DLL, 'WTSUnRegisterSessionNotification');
      if Assigned(WTSUnRegisterSessionNotification) then
      begin
        Result:= WTSUnRegisterSessionNotification(Wnd);
      end;
    finally
      if hWTSAPI32DLL > 0 then
        FreeLibrary(hWTSAPI32DLL);
    end;
  end;
end;

function GetCurrentSessionID: Integer;
 // Getting the session id from the current process
type
  TProcessIdToSessionId = function(dwProcessId: DWORD; pSessionId: DWORD): BOOL; stdcall;
var
  ProcessIdToSessionId: TProcessIdToSessionId;
  hWTSapi32dll: THandle;
  Lib : THandle;
  pSessionId : DWord;
begin
  Result := 0;
  Lib := GetModuleHandle('kernel32');
  if Lib <> 0 then
  begin
    ProcessIdToSessionId := GetProcAddress(Lib, '1ProcessIdToSessionId');
    if Assigned(ProcessIdToSessionId) then
    begin
      ProcessIdToSessionId(GetCurrentProcessId(), DWORD(@pSessionId));
      Result:= pSessionId;
    end;
  end;
end;

end.

// Example:

unit Unit1;

interface

uses
  Windows, Messages, {...},  Wtsapi;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
  { Private declarations }
    FRegisteredSessionNotification : Boolean;
    procedure AppMessage(var Msg: TMSG; var HAndled: Boolean);
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.AppMessage(var Msg: TMSG; var Handled: Boolean);
var
  strReason: string;
begin
  Handled := False;
  // Check for WM_WTSSESSION_CHANGE message
  if Msg.Message = WM_WTSSESSION_CHANGE then
  begin
     case Msg.wParam of
       WTS_CONSOLE_CONNECT:
           strReason := 'WTS_CONSOLE_CONNECT';
       WTS_CONSOLE_DISCONNECT:
           strReason := 'WTS_CONSOLE_DISCONNECT';
       WTS_REMOTE_CONNECT:
           strReason := 'WTS_REMOTE_CONNECT';
       WTS_REMOTE_DISCONNECT:
           strReason := 'WTS_REMOTE_DISCONNECT';
       WTS_SESSION_LOGON:
           strReason := 'WTS_SESSION_LOGON';
       WTS_SESSION_LOGOFF:
           strReason := 'WTS_SESSION_LOGOFF';
       WTS_SESSION_LOCK:
           strReason := 'WTS_SESSION_LOCK';
       WTS_SESSION_UNLOCK:
           strReason := 'WTS_SESSION_UNLOCK';
       WTS_SESSION_REMOTE_CONTROL:
           begin
             strReason := 'WTS_SESSION_REMOTE_CONTROL';
             // GetSystemMetrics(SM_REMOTECONTROL);
           end;
      else
        strReason := 'WTS_Unknown';
     end;
   // Write strReason to a Memo
   Memo1.Lines.Add(strReason + ' ' + IntToStr(msg.Lparam));
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // register the window to receive session change notifications.
  FRegisteredSessionNotification := RegisterSessionNotification(Handle, NOTIFY_FOR_THIS_SESSION);
  Application.OnMessage := AppMessage;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  // unregister session change notifications.
 if FRegisteredSessionNotification then
   UnRegisterSessionNotification(Handle);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 // retrieve current session ID
 ShowMessage(Inttostr(GetCurrentSessionID));
end;

0
 
LVL 2

Author Comment

by:PeterLarsen
ID: 8028409
Thanks smot,

I dont know yet, whether i have to use WTSRegisterSessionNotification/WM_WTSSESSION_CHANGE or not.

From what i hear, i must - because i have to recreate handles to the taskbar (and desktop) when fast-switching - and fast-switching dont act like a normal login/logout.

Peter
0
 
LVL 2

Author Comment

by:PeterLarsen
ID: 8044271
Hi all,

If i'm using Winlogon Notification Packages, the package must notify my running NT-service on login/logout.
How can it do that ??

Something like this : Sendmessage(aHandle, MessageIdentifier, xxxx, xxxx); ??
Regards
Peter
0
 
LVL 86

Expert Comment

by:jkr
ID: 8044319
>>Something like this : Sendmessage(aHandle, MessageIdentifier, xxxx, xxxx); ??

'SendMessage()' will only work for GUI apps - and since most services lack a GUI... :o)

I'd use either a simple event or a service control request using 'ControlService()' with a user-defined control code.
0
 

Expert Comment

by:dave_p_r_b
ID: 8179758
Hi,
I may have misunderstood, but have you looked into GINA? Its a huge topic, but search on msdn. Be prepared for a few remote network registry adjustments though!

D.
0
 
LVL 12

Expert Comment

by:Salte
ID: 8180135
SendMessage()' will only work for GUI apps - and since most services lack a GUI... :o)

This is actually wrong.

SendMessage() works for any process that has a message queue attached to it.

A process gets a message queue if it ever does a PeekMessage() or GetMessage call. First time this happens windows will create a message queue for the process.

If a server does GetMessage() you can do SendMessage() to send messages to it.

Of course, one problem is that SendMessage() takes a windows handle as argument and that means you must have a 'window'. This is actually quite silly and is the reason why full screen DirectX games have to create (an invisible) window at startup for the sole purpose so that they can receive windows messages.

A server can of course do the same thing. But most services do not and as such you can't use SendMessage() to send messages to a random server. But if you write your own server you can easily do so. Just have the server create an invisible window and do GetMessage() or PeekMessage() on that window at some early stage and there you got your message queue.

However, the regular way to interact with a service is to use ControlService() which is essentially exactly the same as SendMessage() wouldn't surprise me if it under the hood used exactly the same mechanism as SendMesage() and uses exactly the same message queue mechanism as SendMessage does. There is one important difference though, it won't use a window handle to identify the message queue but will instead use a handle returned by OpenService() to identify the message queue. Apart from that there's no reason why they should have a completely different type of message queue for servers compared to windows GUI programs.

In fact I believe it is a bad idea that they did it this way, since the difference between a service and a GUI program is - on the whole - very small, they're both event driven or request/response driven (some event or request comes and they need to respond to it), so I believe it is bad OS design to have two different worlds for something that is - on the whole - very much the same but then nobody ever claimed that Windows was good OS design? ;-)

Alf
0
 
LVL 2

Author Comment

by:PeterLarsen
ID: 8185924
Are we talking about MSGINA.DLL - and what can it do ??

salte :
Thanks for your comment.
Actually, i do have a question related to what you are writing about. Not that i dont know how to solve it, but i dont understand why it works :-). I will get back very soon.
0
 
LVL 2

Author Comment

by:PeterLarsen
ID: 8216608
Hi Salte,
You are talking about handles and the need of a window in order to obtain a handle.

In this NT-Service i have, i use the message "TaskBarCreated" - so i know when the taskbar is ready.
But it's not possible to receive messages within the Service - just like you said.

To get the message, i interrupt (override) a function. And this is what i dont understand :
       OldWinProc := TFNWndProc(SetWindowLong(Forms.Application.Handle, GWL_WNDPROC, Longint(@NewWndProc)));

How can this be a valid handle : Forms.Application.Handle ??
I dont have any forms yet and "Forms" is only in the uses clause.

Regards
Peter
0
 
LVL 12

Expert Comment

by:Salte
ID: 8216705
well, for one thing this looks like Delphi code and not C++. In any case, let me see if I can decode it :-)

In C++ the equivalen code would be something like:

OldWinProc = TFNWndProc(SetWindowLong(Forms::Application->Handle, GWL_WNDPROC, static_cast<long>(NewWndProc)));

Well, the crucial point here is the 'Handle' and what value it return. Check your startup code where the application object is initialized. There's one such object per application so you should have only one such object and there should be a static pointer pointing to it.

This object is created during VCL startup. I am not sure if it sets a handle value there or what, if you - as you say - never create a window then I would believe the Handle returned from this should be some form of a NULL handle to no window.

Not sure how that works to get messages sent to such a handle though.

It is also possible that it is the application startup code that creates a window if you don't make one yourself. Handle is most likely a property and so there is a read function attached to it. If that function checks for a NULL handle and creates a window if it is NULL and then return the handle of the created window then the service will create a (presumably invisible) window that you can use to receive messages.

Alf
0
 
LVL 2

Author Comment

by:PeterLarsen
ID: 8220338
Hi salte,
Thanks for your comment - it didn't answer my question, but sometimes it helps to hear what others have to say about it - and this is what i found :

Delphi create a instance of TAppilation (Application) on program start. This is done automatically.
In the constructor of TApplication a window is created : "FHandle := CreateWindow(WindowClass.lpszClassName, PChar(FTitle), WS_POPUP or WS_CAPTION or WS_CLIPSIBLINGS or WS_SYSMENU or WS_MINIMIZEBOX, GetSystemMetrics(SM_CXSCREEN) div 2, GetSystemMetrics(SM_CYSCREEN) div 2, 0, 0, 0, 0, HInstance, nil);"
but only if the program isn't a DLL or a Console.

So you was right about this :-)  :
>>It is also possible that it is the application startup code that creates a window if you don't make one yourself. Handle is most likely a property and so there is a read function attached to it. If that function checks for a NULL handle and creates a window if it is NULL and then return the handle of the created window then the service will create a (presumably invisible) window that you can use to receive messages.

TApplication is located in unit Forms. Not the entire unit is created, only a instance of TApplication (and probably other stuff).
Thats why i could type Forms.Application.Handle. Application.handle is exactly the same.

>>Not sure how that works to get messages sent to such a handle though.
All messages are sent to the hidden window through a function called WndProc "procedure TApplication.WndProc(var Message: TMessage);"
By calling SetWindowLong the address to WndProc (in the hidden window) is changed so it point to my function (called NewWndProc) in the Service.

I guess you are an experienced programmer in C++ and since this is Delphi, you'll probably dont understand much about TApplication and similar. How does it feel not to understand anything and still be able to help ?? :-)

Best regards
Peter
0
 
LVL 12

Expert Comment

by:Salte
ID: 8223819
Well, I do know about TApplication - it is used in C++ builder also and I must confess I have some but not detailed knowledge of object pascal or delphi. A couple of years ago I had a version of C++ builder installed on my computer with the source of the VCL library included and that source was all in pascal and was rather interesting reading :-)

Must admit that it is many years ago now though and I it is not my strongest field.

Alf
0

Featured Post

Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Hello everybody This Article will show you how to validate number with TEdit control, What's the TEdit control? TEdit is a standard Windows edit control on a form, it allows to user to write, read and copy/paste single line of text. Usua…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Michael from AdRem Software outlines event notifications and Automatic Corrective Actions in network monitoring. Automatic Corrective Actions are scripts, which can automatically run upon discovery of a certain undesirable condition in your network.…
In this brief tutorial Pawel from AdRem Software explains how you can quickly find out which services are running on your network, or what are the IP addresses of servers responsible for each service. Software used is freeware NetCrunch Tools (https…
Suggested Courses

752 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question