Winlogon Notification Package

The Microsoft Developer Network describes a Winlogon Notification Package as:

A DLL that exports functions that handles Winlogon events. For example, when a user logs on to the system, Winlogon calls each notification package’s logon event handler function to provide information about the event.

As you might now, the mechanism of a Winlogon Notification Package is available in Windows 2000, Windows XP and Windows 2003 Server.

In Contrast to Windows Vista and above, it’s completely different and called SENS. I’ll come back with a separate article for Windows Vista describing how to subscribe to SENS Events.

I’m going to demonstrate here, how to create such a notification package and as a special gift, we will learn how to create a PNG image with drop shadow effect, which we will use later to draw a transparent window inside the Winlogon Desktop.

Winlogon Logo

Screenshot of the Winlogon displaying the JWSCL Logo

It’s time for our own Winlogon Notification Package. Start Delphi and create a new DLL Project.

What we need to do is to define the Message Callback and the WLXNotificationInfo, described in MSDN.

As you can see on the screenshot to the left we are going to create a notification package which shows the JWSCL Logon on the Windows Logon screen. This is done by creating a transparent window where we draw the png onto it.

Type
// this callback function does not return a value as described on mdsn.

  TFnMsgeCallback = function (bVerbose: Boolean; lpMessage: PWideChar): Cardinal; stdcall;

// this structure stores information about a Winlogon event.

TWlxNotificationInfo = record
    Size: Cardinal;
    Flags: Cardinal;
    UserName: PWideChar;
    Domain: PWideChar;
    WindowStation: PWideChar;
    Token: Cardinal;
    Desktop: Cardinal;
    StatusCallback: TFnMsgeCallback;
  end;
  PWlxNotificationInfo = ^TWlxNotificationInfo;

{  winlogon can inform about the following events:
Lock,
Lockoff,
Logon,
Shutdown,
StartScreensave,
StartShell,
StartUp,
StopScreenSaver,
Unlock

For a complete describtion of those events look at:

http://msdn.microsoft.com/en-us/library/aa380544(VS.85).aspx
}

// example of handling the Logon Message

procedure LogonHandler(Info: PWlxNotificationInfo); stdcall;
begin

end;

// the entrance function for DLL

procedure EntryPointProc(reason: integer);
begin
  case reason of
    DLL_PROCESS_ATTACH:  //1
      Begin
             // Disable DLL_THREAD_ATTACH & DLL_THREAD_DETACH
             // notification calls. This is a performance optimization
             // for multithreaded applications that do not need
             // thread-level notifications of attachment or
             // detachment.

        DisableThreadLibraryCalls(hInstance);

      end;
    DLL_THREAD_ATTACH:   //2
      begin

      end;
    DLL_PROCESS_DETACH:  //3
      begin

      end;
    DLL_THREAD_DETACH:   //0
      begin

      end;
  end;

end;

// define the exports for the DLL

exports
  LogonHandler;

begin
  DllProc := @EntryPointProc;
  DllProc(DLL_PROCESS_ATTACH);
end.

Our Prototype is ready to use. The compiled DLL has to be in the following folder: C:WindowsSystem32 (adapt it to your environment) Now we can inform the operating system that we created a Winlogon Notification Package.This step will be done in the registry under the following key:

HKey_Local_MachineSoftwareMicrosoftWindowsNTCurrentVersionWinlogonNotifyYourNotification

You can see the valid values and there descriptions in the following table:

Value name [data type]Description
Asynchronous

[REG_DWORD]
Indicates whether the package can handle events asynchronously. If this value is set to 1, Winlogon calls the package functions in a separate thread. Otherwise, it does not.
DllName

[REG_EXPAND_SZ]
Name of the DLL that implements the notification package, for example: "Notify.dll".
Impersonate

[REG_DWORD]
Indicates whether Winlogon should impersonate the security context of the logged-on user when it calls the notification package functions. If this value is set to 1, Winlogon uses impersonation. Otherwise, it does not.
Lock

[REG_SZ]
Name of the function which handles desktop lock events, for example: "WLEventLock".
Logoff

[REG_SZ]
Name of the function which handles logoff events, for example: "WLEventLogoff".
Logon

[REG_SZ]
Name of the function which handles logon events, for example: "WLEventLogon".
Shutdown

[REG_SZ]
Name of the function which handles shutdown events, for example: "WLEventShutdown".
StartScreenSaver

[REG_SZ]
Name of the function which handles screen saver start events, for example: "WLEventStartScreenSaver".
StartShell

[REG_SZ]
Name of the function which handles shell start events, for example: "WLEventStartShell".

A shell start event occurs after the user has logged on but before the desktop appears. It differs from the logon event in that the user's security context has been established, and resources such as network connections are available.
Startup

[REG_SZ]
Name of the function which handles system startup events, for example: "WLEventStartup".
StopScreenSaver

[REG_SZ]
Name of the function which handles screen saver stop events, for example: "WLEventStopScreenSaver".
Unlock

[REG_SZ]
Name of the function which handles desktop unlock events, for example: "WLEventUnlock".

 

For completing this Project you will need to download the following:

Graphics32 Library

Graphics32 is a graphics library for Delphi and Kylix/CLX. Optimized for 32-bit pixel formats, it provides fast operations with pixels and graphic primitives. In most cases Graphics32 considerably outperforms the standard TBitmap/TCanvas methods. This isn’t needed anymore for Delphi 2009 and above.


PNG Components

Delphi VCL Components with PNG Image support. This isn’t needed anymore for Delphi 2009 and above.

 

Creating the PNG Image

What you’ll need is an already existing PNG or a new one. I’ll use with the Jedi Api WSCL Logo.Open the Logo in Photoshop but remember this can be done in almost any other Graphic Application which supports PNG and layer based drawing.

The Logo from this site is already a PNG, so you will see a transparent background when it is opened in Photoshop. If you like to add a drop shadow style to it, you won’t see it,because of the mentioned transparent background. It is also a problem on a black background, btw. So first thing to do is to add a temporary background layer into the Logo. To do so, click on the “Create New Layer Symbol” in the layer palette (indicated as a small notepad icon).

Now select the new created layer by clicking on it and drag it behind the Logo layer. With the new layer still selected, switch to the “Tools Window” and select the “Paint Bucket Tool“. Select white as your foreground color. If you have another color as foreground color, simply press d and then x. In this way it will reset the foreground and backgound colors. Now click inside your image on the transparent part and you’ll see the Logo with a white background applied.

Switch to your layers palette and double click on the Logo layer. The “Layers Styles Dialog” should be visible now. Select “Drop Shadow” and you can adjust the angle of the drop shadow and its size. While you adjust it, also watch the Logo. Photoshop shows you a preview of the changes. If you’re satisfied with your settings hit ok.

At this time, we don’t need our white background layer anymore, so select it and drag it to the trash can symbol in the layer palette. It will be deleted then. Afterwards save the Logo, select PNG as the file format and a new window will appear (PNG Options). Select none and hit ok.

There also exists a small video presentation which will help you.

The following source is kindly provided by Coder90. He wrote a nice example in a german delphi forum

The Sources

I won’t go into any details here, basically the procedure will load the PNG into a Bitmap32 Object and blends it to the forms canvas.

Type
interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, GR32;

  public
    { Public declarations }
    BMP32 : TBitmap32;
    BlendF: TBlendFunction;
    P: TPoint;
    Size: TSize;
    procedure BlendIt;
  end;

  procedure LoadPNGintoBitmap32(DstBitmap: TBitmap32;
                                Filename: String;
                                out AlphaChannelUsed: Boolean); overload;

  procedure LoadPNGintoBitmap32(DstBitmap: TBitmap32;
                                SrcStream: TStream;
                                out AlphaChannelUsed: Boolean); overload;

  procedure LoadPNGintoBitmap32(DstBitmap: TBitmap32;
                                szSection : PChar;
                                szName    : String;
                                out AlphaChannelUsed: Boolean); overload;

uses PNGImage;

procedure LoadPNGintoBitmap32(DstBitmap: TBitmap32;
                              SrcStream: TStream;
                              out AlphaChannelUsed: Boolean);
var
  PNGObject: TPNGObject;
  TransparentColor: TColor32;
  PixelPtr: PColor32;
  AlphaPtr: PByte;
  X, Y: Integer;
begin
  PNGObject := nil;
  try
    PNGObject := TPngObject.Create;
    PNGObject.LoadFromStream(SrcStream);

    DstBitmap.Assign(PNGObject);
    DstBitmap.ResetAlpha;

    case PNGObject.TransparencyMode of
      ptmPartial:
        begin
          if (PNGObject.Header.ColorType = COLOR_GRAYSCALEALPHA) or
             (PNGObject.Header.ColorType = COLOR_RGBALPHA) then
          begin
            PixelPtr := PColor32(@DstBitmap.Bits[0]);
            for Y := 0 to DstBitmap.Height - 1 do
            begin
              AlphaPtr := PByte(PNGObject.AlphaScanline[Y]);
              for X := 0 to DstBitmap.Width - 1 do
              begin
                PixelPtr^ := (PixelPtr^ and $00FFFFFF) or (TColor32(AlphaPtr^) shl 24);
                Inc(PixelPtr);
                Inc(AlphaPtr);
              end;
            end;

            AlphaChannelUsed := True;
          end;
        end;
      ptmBit:
        begin
          TransparentColor := Color32(PNGObject.TransparentColor);
          PixelPtr := PColor32(@DstBitmap.Bits[0]);
          for X := 0 to (DstBitmap.Height - 1) * (DstBitmap.Width - 1) do
          begin
            if PixelPtr^ = TransparentColor then
              PixelPtr^ := PixelPtr^ and $00FFFFFF;
            Inc(PixelPtr);
          end;

          AlphaChannelUsed := True;
        end;
      ptmNone:
        AlphaChannelUsed := False;
    end;
  finally
    if Assigned(PNGObject) then PNGObject.Free;
  end;
end;

procedure LoadPNGintoBitmap32(DstBitmap: TBitmap32;
                              Filename: String;
                              out AlphaChannelUsed: Boolean);
var
  FileStream: TFileStream;
begin
  FileStream := TFileStream.Create(Filename, fmOpenRead);
  try
    LoadPNGintoBitmap32(DstBitmap, FileStream, AlphaChannelUsed);
  finally
    FileStream.Free;
  end;
end;

procedure LoadPNGintoBitmap32(DstBitmap: TBitmap32;
                              szSection : PChar;
                              szName    : String;
                              out AlphaChannelUsed: Boolean);
var
  Stream: TResourceStream;
begin
  Stream := TResourceStream.Create(hInstance, szName, PChar(szSection));
  try
    LoadPNGintoBitmap32(DstBitmap, Stream, AlphaChannelUsed);
    finally
     Stream.Free;
   end;
end;

{ Works only Win2000_Up }

procedure TForm1.BlendIt;
  var
    Alpha: Boolean;
begin
  BMP32 := TBitmap32.Create;
 {Load PNG from File
  LoadPNGintoBitmap32(BMP32, ExtractFilePath(ParamStr(0)) + ‘WSCL.png’, Alpha);
 }
  // Load PNG From Resource
  LoadPNGintoBitmap32(BMP32, PChar(‘PNG’), ‘WSCL’, Alpha);

  setWindowLong(Handle, GWL_EXSTYLE,
  getWindowLong(Handle, GWL_EXSTYLE) or WS_EX_LAYERED {or WS_EX_TRANSPARENT});
  // WS_EX_TRANSPARENT makes the Window for MouseClicks transparent…

  BlendF.BlendOp := AC_SRC_OVER;
  BlendF.BlendFlags := 0;
  BlendF.SourceConstantAlpha := 255;
  BlendF.AlphaFormat := AC_SRC_ALPHA;
  P := Point(0, 0);
  Size.cx := BMP32.Width;
  Size.cy := BMP32.Height;

  UpdateLayeredWindow(Handle, 0, nil, @Size, BMP32.Handle, @P, 0, @BlendF, ULW_ALPHA);

  // Set Window on Top
  SetWindowPos(Handle, HWND_TOPMOST, Left, Top, Width, Height,
  SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);

  // Set Parent to Desktop
  SetWindowLong(Handle, GWL_HWNDPARENT, 0);

  // Hide Window from Taskbar
  SetWindowLong(Handle, GWL_EXSTYLE,
  GetWindowLong(Handle, GWL_EXSTYLE) or
  WS_EX_TOOLWINDOW and not WS_EX_APPWINDOW);
end;

Call it in the FormOnCreate handler and don’t forget to free the Bitmap32 Object, a good place e.g. would be on the FormOnDestroy Handler.

Additional informations for you

I have to tell you that the first version of this package was slightly different than the one I’ll post here. Well I used an external executeable which was called from my DLL when the LogonEvent was called. But I encountered a big problem with that technique. One of the biggest problems were that my external application was executed more then once. Additionally I wasn’t able to close it. Neither Application.Terminate nor Application.Mainform.Close worked here. I did some experiments with a mutex which worked in some cases, but then, Christian Wimmer gave me many good ideas and suggestions. So the improvements were: The transparent Window is now directly created inside of the Winlogon Process by creating a new thread object. Christian implemented an TApplication.OnIdle handler inside the thread object which will safely close the transparent window when it isn’t needed anymore.

Prerequisites:

The following Delphi packages are necessary. They are not included and must therefore downloaded and installed (if necessary) separately. Please, also recognize the license of these packages. The winlogon examples does only have a Copyright and no Warranty license.

Download:

3rd Party Components and Library’s you need to download:

Graphics32 Library (download size: 2.01 MB Format: ZIP)

Warning:

If you want to use such a Winlogon Notification DLL in a productive system you have to make sure that the wrong person can’t manipulate the DLL file. This can be done by setting the file’s access control list to allow only write access to SYSTEM and Administrators. There is no need for other users to have even read access, since the DLL is only used by winlogon. Furthermore you should save this file in a save place like the System32 folder.