Jump to content
gioma

WTSQuerySessionInformation doesn't work in x64 application

Recommended Posts

Hi,

I have a big problem.
I have a service that monitors sessions on a PC/Server.
If I develop the application in 32 bit and it works, but if I develop the application in 64 bit it goes into error (but not always!!):
Error 87 : The Parameter is incorrect

This is my code:

 

Unit Wtsapi32:

unit UN_Wtsapi32;
interface
uses 
	Windows;
const
  WINSTATIONNAME_LENGTH = 32;
  DOMAIN_LENGTH = 17;
  USERNAME_LENGTH = 20;
  CLIENTNAME_LENGTH = 20;
  CLIENTADDRESS_LENGTH = 30;

type
  WTS_INFO_CLASS = (
    WTSInitialProgram,
    WTSApplicationName,
    WTSWorkingDirectory,
    WTSOEMId,
    WTSSessionId,
    WTSUserName,
    WTSWinStationName,
    WTSDomainName,
    WTSConnectState,
    WTSClientBuildNumber,
    WTSClientName,
    WTSClientDirectory,
    WTSClientProductId,
    WTSClientHardwareId,
    WTSClientAddress,
    WTSClientDisplay,
    WTSClientProtocolType,
    WTSIdleTime,
    WTSLogonTime,
    WTSIncomingBytes,
    WTSOutgoingBytes,
    WTSIncomingFrames,
    WTSOutgoingFrames,
    WTSClientInfo,
    WTSSessionInfo,
    WTSSessionInfoEx,
    WTSConfigInfo,
    WTSValidationInfo,
    WTSSessionAddressV4,
    WTSIsRemoteSession
  );

 WTS_CONNECTSTATE_CLASS = (
    WTSActive,
    WTSConnected,
    WTSConnectQuery,
    WTSShadow,
    WTSDisconnected,
    WTSIdle,
    WTSListen,
    WTSReset,
    WTSDown,
    WTSInit
  );
{$IFDEF UNICODE}
PWTS_INFO = ^WTS_INFO;
  WTS_INFO = record
    State:WTS_CONNECTSTATE_CLASS ;
    SessionId: DWORD;
    IncomingBytes :DWORD;
    OutgoingBytes :DWORD;
    IncomingFrames:DWORD;
    OutgoingFrames:DWORD;
    IncomingCompressedBytes:DWORD;
    OutgoingCompressedBytes:DWORD;
    WinStationName: array[0..WINSTATIONNAME_LENGTH - 1] of WCHAR;
    Domain: array[0..DOMAIN_LENGTH - 1] of WCHAR;
    UserName:array[0..USERNAME_LENGTH - 1] of WCHAR;
    ConnectTime:LARGE_INTEGER;
    DisconnectTime:LARGE_INTEGER;
    LastInputTime:LARGE_INTEGER;
    LogonTime:LARGE_INTEGER;
    CurrentTime:LARGE_INTEGER;
  end;
 {$ENDIF}
  
function WTSEnumerateSessions(
  hServer: THandle;
  Reserved: DWORD;
  Version: DWORD;
  var ppSessionInfo:PWTS_SESSION_INFO;
  var pCount: DWORD
  ): BOOL; stdcall; external 'Wtsapi32.dll' name {$IFDEF UNICODE}'WTSEnumerateSessionsW'{$ELSE}'WTSEnumerateSessionsA'{$ENDIF};

function WTSQuerySessionInformation(
  hServer: NativeUInt;
  SessionId: DWORD;
  WTSInfoClass: WTS_INFO_CLASS;
  var ppBuffer: PLPWSTR;
  var pBytesReturned: PDWORD): BOOL; stdcall; external 'Wtsapi32.dll' name {$IFDEF UNICODE}'WTSQuerySessionInformationW'{$ELSE}'WTSQuerySessionInformationA'{$ENDIF};
  

unit test:

 

//... 

function GetSessionInfo(id:DWORD):TSessionInfo;
var
  wpSessionInfo:PWTS_INFO;
  pSessionInfo:PLPWSTR;
  pBytesReturned:PDWORD;
  iderr:integer;
begin

  Result.ID:=-1;
  if id >0 then
  begin
    try
      if (WTSQuerySessionInformation (
                            WTS_CURRENT_SERVER_HANDLE,
                            id,
                            WTS_INFO_CLASS.WTSSessionInfo,
                            pSessionInfo,
                            pBytesReturned) ) then
      begin
        Result.ID := id;
        wpSessionInfo:=PWTS_INFO(pSessionInfo);
        //user name
        Result.user:= wpSessionInfo.UserName;
        //station name
        Result.StationName:= wpSessionInfo.WinStationName;
        //domain name
        Result.domain:=wpSessionInfo.Domain;
        //connection status
        Result.ConnectState:=wpSessionInfo.State;

      end
      else begin
        idErr:=GetLastError;
        GV_TxtFileLog.Log('[GetSessionInfo] id '+intTostr(id)+' ERR :  '+ intToStr(idErr)+' '+SysErrorMessage(idErr),true);
      end;

    except
       on e:exception do
        GV_TxtFileLog.Log('[GetSessionInfo] id '+intTostr(id)+' EX : '+ e.Message,true);
    end;
  end;


end;

function SF_GetOpenedSessions(var codeErr:integer) :TSessionsInfo;
var
  Sessions, Session: PWTS_SESSION_INFO;
  NumSessions:DWORD;
  I:DWORD;
  SessionInfo:TSessionInfo;
  pSessionInfo:PLPWSTR;
  pBytesReturned:PDWORD;
  wpSessionInfo:PWTS_INFO;
  id:DWORD;
begin
  SetLength(result,0);
  codeErr:=0;
  if WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, Sessions, NumSessions) then
  begin
    try
      if NumSessions > 0 then
      begin
        Session := Sessions;
        for i := 0 to NumSessions do
        begin
          id:=Session.SessionId;
          if id > 0 then
          begin
            GV_TxtFileLog.Log('[SF_GetOpenedSessions] GetSessionInfo '+ intToStr(id) );
            SessionInfo := GetSessionInfo(id);
            if SessionInfo.ID>0 then
            begin
              SetLength(result, length(Result)+1);
              Result[length(Result)-1]:= SessionInfo;
            end;
            
          end;
          Inc(Session);
        end;

      end;
    except
      codeErr:=GetLastError;
      GV_TxtFileLog.Log('[SF_GetOpenedSessions] WTSQuerySessionInformation err : '+IntToStr(codeErr)+' '+SysErrorMessage(codeErr), true);
    end;
    WTSFreeMemory(Sessions);
  end
  else begin
    codeErr:=GetLastError;
    GV_TxtFileLog.Log('[SF_GetOpenedSessions] WTSEnumerateSessions err : '+IntToStr(codeErr)+' '+SysErrorMessage(codeErr), true);
  end;




end;

 

 

When I call the "SF_GetOpenedSessions" function I get error 87 when this function then calls the "GetSessionInfo" function.
While if I use "GetSessionInfo" passing it the SID of the program it works perfectly... does anyone have an idea why? (I'm going crazy about it!)

Compiling in 32 Bit, however, I have no problem.

Share this post


Link to post
3 hours ago, gioma said:

While if I use "GetSessionInfo" passing it the SID of the program it works perfectly... does anyone have an idea why? (I'm going crazy about it!)

None about this, but I want suggest to use the WinApi.Wtsapi32 (It is the wrapper distributed with Delphi 12). It's a little bit different from yours.

 

  • Like 1

Share this post


Link to post

Your declaration of WTSQuerySessionInformation() is wrong.  It should be like this instead:

function WTSQuerySessionInformation(
  hServer: THandle;
  SessionId: DWORD;
  WTSInfoClass: WTS_INFO_CLASS;
  var ppBuffer: LPWSTR;
  var pBytesReturned: DWORD): BOOL; stdcall; external 'Wtsapi32.dll' name {$IFDEF UNICODE}'WTSQuerySessionInformationW'{$ELSE}'WTSQuerySessionInformationA'{$ENDIF};
 

And technically, both WTSEnumerateSessions() and WTSQuerySessionInformation() should be using 'out' parameters instead of 'var' parameters.  But that is not important here.

 

You are also leaking the memory that WTSQuerySessionInformation() returns.

 

Try this instead:

function GetSessionInfo(id: DWORD; var codeErr: Integer): TSessionInfo;
var
  wpSessionInfo: PWTS_INFO;
  SessionInfo: LPWSTR;
  BytesReturned: DWORD;
  idErr: Integer;
begin
  Result.ID := -1;
  try
    if WTSQuerySessionInformation (
                            WTS_CURRENT_SERVER_HANDLE,
                            id,
                            WTS_INFO_CLASS.WTSSessionInfo,
                            SessionInfo,
                            BytesReturned) then
    begin
      try
        Result.ID := id;
        wpSessionInfo := PWTS_INFO(SessionInfo);
        //user name
        Result.user := wpSessionInfo.UserName;
        //station name
        Result.StationName := wpSessionInfo.WinStationName;
        //domain name
        Result.domain := wpSessionInfo.Domain;
        //connection status
        Result.ConnectState := wpSessionInfo.State;
      finally
        WTSFreeMemory(SessionInfo);
      end;
    end
    else begin
      idErr := GetLastError;
      GV_TxtFileLog.Log('[GetSessionInfo] id ' + IntToStr(id) + ' ERR :  ' + intToStr(idErr) + ' ' + SysErrorMessage(idErr), True);
    end;
  except
    on e: Exception do
      GV_TxtFileLog.Log('[GetSessionInfo] id ' + IntToStr(id) + ' EX : ' + e.Message, True);
  end;
end;

function SF_GetOpenedSessions(var codeErr: Integer): TSessionsInfo;
var
  Sessions, Session: PWTS_SESSION_INFO;
  NumSessions, I: DWORD;
  SessionInfo: TSessionInfo;
  SessionCount: Integer;
begin
  Result := nil;
  codeErr := 0;
  try
    if WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, Sessions, NumSessions) then
    begin
      try
        if NumSessions > 0 then
        begin
          SessionCount := 0;
          SetLength(Result, NumSessions);
          try
            Session := Sessions;
            for i := 0 to NumSessions-1 do
            begin
              if Session.SessionId <> 0 then
              begin
                GV_TxtFileLog.Log('[SF_GetOpenedSessions] GetSessionInfo '+ intToStr(Session.SessionId));
                SessionInfo := GetSessionInfo(Session.SessionId);
                if SessionInfo.ID <> -1 then
                begin
                  Result[SessionCount] := SessionInfo;
                  Inc(SessionCount);
                end;
              end;
              Inc(Session);
            end;
          finally
            SetLength(Result, SessionCount);
          end;
        end;
      finally
        WTSFreeMemory(Sessions);
      end;
    end
    else begin
      codeErr := GetLastError;
      GV_TxtFileLog.Log('[SF_GetOpenedSessions] WTSEnumerateSessions err : ' + IntToStr(codeErr) + ' ' + SysErrorMessage(codeErr), True);
    end;
  except
    on e: Exception do
      GV_TxtFileLog.Log('[SF_GetOpenedSessions] EX : ' + e.Message, True);
  end;
end;

 

Edited by Remy Lebeau

Share this post


Link to post
10 hours ago, Remy Lebeau said:

Your declaration of WTSQuerySessionInformation() is wrong.  It should be like this instead:


function WTSQuerySessionInformation(
  hServer: THandle;
  SessionId: DWORD;
  WTSInfoClass: WTS_INFO_CLASS;
  var ppBuffer: LPWSTR;
  var pBytesReturned: DWORD): BOOL; stdcall; external 'Wtsapi32.dll' name {$IFDEF UNICODE}'WTSQuerySessionInformationW'{$ELSE}'WTSQuerySessionInformationA'{$ENDIF};
 

And technically, both WTSEnumerateSessions() and WTSQuerySessionInformation() should be using 'out' parameters instead of 'var' parameters.  But that is not important here.

Hi Remy, I tried but I always get error code 87.
I also tried using this version:

 

function WTSQuerySessionInformation(
  hServer: THandle;
  SessionId: DWORD;
  WTSInfoClass: WTS_INFO_CLASS;
  var ppBuffer: Pointer;
  var pBytesReturned: Pointer): BOOL; stdcall; external 'Wtsapi32.dll' name {$IFDEF UNICODE}'WTSQuerySessionInformationW'{$ELSE}'WTSQuerySessionInformationA'{$ENDIF};

obviously it doesn't work! 😭

 

The strangest thing is that sometimes it works (with the first version), but then suddenly it stops working and there is no way to make it work anymore.
What I'm wondering is because compiling in 32 bit always works, while compiling in 64 bit doesn't!

 

Is the structure not large enough for the result? But Microsoft's documentation indicates to use those data types.

There is no documentation online on the use of this API for 64-bit applications! 😭😭

 

 

Share this post


Link to post

This appears to be an issue related to "overwriting" of memory areas.
Another tip: try disabling the "support high-entropy 64 bit address ..." item in the project options (Delphi compiler / Linking).

 

P.S.: Do not attempt to redefine the methods, they are already defined in the indicated unit that comes with Delphi.

Share this post


Link to post
11 minutes ago, DelphiUdIT said:

P.S.: Do not attempt to redefine the methods, they are already defined in the indicated unit that comes with Delphi.

Do you mean the "WTSQuerySessionInformation()" function? What is the unit in Delphi where it is already defined?

11 minutes ago, DelphiUdIT said:

Another tip: try disabling the "support high-entropy 64 bit address ..." item in the project options (Delphi compiler / Linking

I tried, unfortunately nothing changes.

Share this post


Link to post
4 hours ago, gioma said:

Do you mean the "WTSQuerySessionInformation()" function? What is the unit in Delphi where it is already defined?

 

  • Thanks 1

Share this post


Link to post
6 minutes ago, gioma said:

 

you have solved my problem!
I used the unit WinApi.Wtsapi32 and the error disappeared!
Honestly, I couldn't find this unit so I thought it didn't exist!
Thanks thanks thanks.

Share this post


Link to post

Now I also understand why I couldn't find it, winapi.wtsapi32 isn't there on delphi 11!
I started the project with Delphi 10, then I switched to Delphi 11 and now I've switched to Delphi 12!

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×