As you may know, you can enumerate processes of a specific Terminal Server or Citrix session using the NtQuerySystemInformation function.

On x86 system the code below works fine:

objectpascal Download
var
  Status : NTSTATUS;
  RetLength : DWORD;
  SessionInfo :  SYSTEM_SESSION_PROCESS_INFORMATION;
  CurrentProcess : PSystemProcesses;
begin

  SessionInfo.SessionId := SessionId;
  SessionInfo.Buffer := nil;
  SessionInfo.SizeOfBuf := 0;

  Status := NtQuerySystemInformation(SystemSessionProcessesInformation, @SessionInfo, SizeOf(SessionInfo), @RetLength);
  while (Status = STATUS_INFO_LENGTH_MISMATCH) do
  begin
    SessionInfo.SizeOfBuf := RetLength;
    SessionInfo.Buffer := GetMemory(RetLength);
    try
      Status := NtQuerySystemInformation(SystemSessionProcessesInformation, @SessionInfo, SizeOf(SessionInfo), @RetLength);
      if NT_SUCCESS(Status) then
      begin
        CurrentProcess := SessionInfo.Buffer;
        while True do
        begin
          // Your code here

          if (CurrentProcess^.NextEntryDelta = 0) then
            Break;

          Cardinal(CurrentProcess) := Cardinal(CurrentProcess) + CurrentProcess^.NextEntryDelta;
        end;

        Exit;
      end;
    finally
      FreeMemory(SessionInfo.Buffer);
    end;
  end;

  SetLastError(RtlNtStatusToDosError(Status));
  RaiseLastOSError;
end;

While this works fine on Windows XP and 2003 x86, it fails to work correctly on the x64 versions of Windows XP and 2003 (or maybe even higher).

The problem is that RetLength is always SizeOf(SYSTEM_SESSION_PROCESS_INFORMATION) and thus we are in an endless loop!

If you disassemble an a call to NtQuerySystemInformation, for example in the CreateToolhelp32Snapshot API, you will see that it just executes NtQuerySystemInformation and increases the buffer size in a loop.

Ok, let’s do it in the same way :-)

objectpascal Download
var
  Status : NTSTATUS;
  SessionInfo :  SYSTEM_SESSION_PROCESS_INFORMATION;
  CurrentProcess : PSystemProcesses;
begin
  SessionInfo.SessionId := SessionId;
  SessionInfo.Buffer := nil;
  SessionInfo.SizeOfBuf := 0;

  Status := STATUS_INFO_LENGTH_MISMATCH;
  while (Status = STATUS_INFO_LENGTH_MISMATCH) do
  begin
    SessionInfo.SizeOfBuf := SessionInfo.SizeOfBuf + MEMORY_DELTA_SIZE;
    SessionInfo.Buffer := GetMemory(SessionInfo.SizeOfBuf);
    try
      Status := NtQuerySystemInformation(SystemSessionProcessesInformation, @SessionInfo, SizeOf(SessionInfo), nil);
      if NT_SUCCESS(Status) then
      begin
        CurrentProcess := SessionInfo.Buffer;
        while True do
        begin
  	  // Your code here
          if (CurrentProcess^.NextEntryDelta = 0) then
            Exit;

          Cardinal(CurrentProcess) := Cardinal(CurrentProcess) + CurrentProcess^.NextEntryDelta;
        end;

        Exit;
      end;
    finally
      FreeMemory(SessionInfo.Buffer);
    end;

  end;

  SetLastError(RtlNtStatusToDosError(Status));
  RaiseLastOSError;
end;

But this code doesn’t work as well. The reason is that you need to place the whole buffer in a contiguous memory area.

So finally the working code is :

objectpascal Download
var
  Status : NTSTATUS;
  SessionInfo :  PSYSTEM_SESSION_PROCESS_INFORMATION;
  CurrentProcess : PSystemProcesses;
  Size : DWORD;
begin

  Size := 0;
  Status := STATUS_INFO_LENGTH_MISMATCH;

  while (Status = STATUS_INFO_LENGTH_MISMATCH) do
  begin
    Size := Size + MEMORY_DELTA_SIZE;
    SessionInfo := GetMemory(Size);
    try

      SessionInfo^.SessionId := SessionId;
      SessionInfo^.SizeOfBuf := Size - SizeOf(SessionInfo^);
      Cardinal(SessionInfo^.Buffer) := Cardinal(@SessionInfo^.Buffer) + SizeOf(SessionInfo^.Buffer);

      Status := NtQuerySystemInformation(SystemSessionProcessesInformation, SessionInfo, Size, nil);
      if NT_SUCCESS(Status) then
      begin
        CurrentProcess := SessionInfo^.Buffer;
        while True do
        begin
       	// Your core here

          if (CurrentProcess^.NextEntryDelta = 0) then
            Exit;

          Cardinal(CurrentProcess) := Cardinal(CurrentProcess) + CurrentProcess^.NextEntryDelta;
        end;

        Exit;
      end;
    finally
      FreeMemory(SessionInfo);
    end;

  end;

  SetLastError(RtlNtStatusToDosError(Status));
  RaiseLastOSError;
end;