Using WTSQuerySessionInformation to get client computer name when running in a remote session

If you’re application is running in a remote session (Terminal Services or RDP) and you need to get the name of the remote client you can use the attached class to call WTSQuerySessionInformation to get the information.
A couple of caveats, this code dynamically loads WTS32API.DLL which contains WTSQuerySessionInformation. This API wasn’t introduced until Windows Vista and Windows Server 2008.
The class uses @julesVerne LoadLib class to dynamically load the DLL. If you do not have access to that class then you will need to re-work the LoadLibrary calls. Alternatively, if you know your application will always run on newer operating systems and you can replace the dynamic loading with traditional statically linked API calls.
Seconds, this class uses my String Class. You can find that on ClarionMag or replace calls with StringTheory.
UP_System_TS.inc (2.3 KB) UP_System_TS.clw (4.2 KB)

With permission from Larry, you can download LoadLib from here.
https://clarionfreeimage.com/examples/LoadLib.zip

3 Likes

If you’re basing that on the MSDN page often it is updated when support is dropped for an OS.

IIRC I used it in Win 5. A quick Google search for “WTSQuerySessionInformation Windows 2000” turned up pages confirming so unless you need NT 4 you’re fine in 2k, Server 2000, 2003+, XP

Then you can probably generate a LIB for WTS32API.dll and go with static linking.
I wrote this code originally a long time ago.

I know I wrote it for Windows 2000 that had the first WTS server. I agree its safe to static link. But then you need to ship a LIB with your Class so from source control POV easier to LoadLib. Be nice to have a way specify the LIB in code like Pragma('MakeLib UpWTS.LIB; WTSAPI32.DLL; WTSQuerySessionInformationA; WTSFreeMemory'). Also add Delay Loading :slight_smile:

I looked at my code for this and it’s a bit different. As you know WTS returns a Memory Address and Length of the Client Name. Your code used a NEW CString and MemCpy() to make a Clarion CString to read that address.

An alternative is to Reference assign the &CString &= (Address) &':'& Size. The Size is not required but it makes a safer reference that will not overrun the Size.

UP_System_TS.GetClientName       PROCEDURE() !,STRING
!orig! ClientName    UP_StringClass
ClientName    STRING(32)    !Max Computer Name is 15
BufferPtr     long
BufferLength  ULONG
BufferRef     &CSTRING
  code
  if fpWTSQuerySessionInformation
    if upapi_WTSQuerySessionInformation(upapi_WTS_CURRENT_SERVER_HANDLE,upapi_WTS_CURRENT_SESSION,upapi_WTSClientName,BufferPtr,BufferLength) |
    and BufferPtr <> 0   |            !Check for bad pointer returned
    and BufferLength > 1 THEN         !Have note from 2010 saw Length of 1 returned that was bad so check > 1 ??
!orig!       BufferRef &= new(cstring(BufferLength+1))
!orig!       memcpy(address(BufferRef), BufferPtr, BufferLength)
!orig!       ClientName.Assign(BufferRef)
!orig!       dispose(BufferRef)
       BufferRef &= (BufferPtr) &':'& BufferLength   !9/4 edit remove " +1 "  !<-- &CString &= Address
       ClientName = BufferRef
       upapi_WTSFreeMemory(BufferPtr)
    end
  end
  return CLIP(ClientName)  !orig! ClientName.Get()

I can’t find &= (Address) documented but I’m sure I’ve seen it shown at DevCon by DAB and also by Alexey in comp.lang.

Hi Carl - Not sure you can assume that you can go beyond bufferlength with that “+1”.

1 Like

The returned Client Name is defined as a null terminated string. Typically Windows functions that return (or *out) a byte count (string length) do Not include the trailing null; otherwise, an empty string would return 1. I see nothing in the remarks.

I don’t have a WTS server to test with, maybe @Rick_UpperPark can test.


9/4 Edit:
After I replied I was troubled by the parameter being called “Bytes Returned” and not “String Length”. Also the description on MSDN seems clear it’s size not length. Jeff is right the count included the Null byte.


MSDN

ppBuffer

A pointer to a variable that receives a pointer to the requested information. The format and contents of the data depend on the information class specified in the WTSInfoClass parameter.

pBytesReturned

A pointer to a variable that receives the size, in bytes, of the data returned in ppBuffer .

That doesn’t always mean that there actually IS space allocated beyond that buffer for an extra null. There’s no guarantee what exists beyond the length that you are provided by an API.

I do like the &= technique and use it a lot, but you have to tread carefully with buffers that aren’t your own.

Maybe &STRING could be safer?

Very interesting discussion.
I agree with Jeff, you should not make any assumptions about the memory contents past the buffer length returned by any API.
Additionally, the call to WTSQuerySessionInformation does not just return the address of null terminated strings in BufferRef. It can be the offset of structures like WTSClient, WTSInfo, WTSConfigInfo, etc.

Turns out there is a bug in my original code. The BufferLength returned by the call includes the trailing NULL character. My workstation name is DIMHOLT which is 7 characters. BufferLength is returning 8 when I call to get the client name. No harm in the bug, other than allocating an extra byte of memory for a few milliseconds.

I thought it might be interesting to show how I examined the memory returned by the API call to answer the question of the buffer length and NULL character. Here’s an explanation of the techniques.

1 Like

My code ran on 1000’s of systems and worked. I have a note from 2010 that I had one site where the Buffer Size returned was 1. I did not note the String value but I would guess it was CHR(0).

Since Clarion does not allow a CSTRING(1) it would be best for your code to only process the Client Name IF Bytes Returned > 1.