Title: Use Undocumented Terminal Server API's
Question: How to use Undocumented Terminal Server API's?
Winsta.dll provides functions to Extract Idle Time and Login time for Terminal Sessions as well as API's to connect to another session or Shadow it.
Answer:
{******************************************************************}
{ This Unit provides Delphi translations of some functions from }
{ WinSta.dll. The functions are not documented by Microsoft and }
{ were tested only with Windows 2003 standard. Functions were not }
{ tested with Windows 2000, but are expected to work. }
{ }
{ Author: Remko Weijnen (r dot weijnen at gmail dot com) }
{ Version: 0.3 }
{ Date: 03-01-2007 }
{ }
{ The contents of this file are subject to }
{ the Mozilla Public License Version 1.1 (the "License"); you may }
{ not use this file except in compliance with the License. You may }
{ obtain a copy of the License at }
{ http://www.mozilla.org/MPL/MPL-1.1.html }
{ }
{ Software distributed under the License is distributed on an }
{ "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or }
{ implied. See the License for the specific language governing }
{ rights and limitations under the License. }
{******************************************************************}
// $Id: JwaWinSta.pas,v 1.2 2007/01/07 18:53:06 assarbad Exp $
unit JwaWinSta;
interface
Uses SysUtils, Windows, DateUtils, JwaNative;
type
 HANDLE = THandle;
 PVOID = Pointer;
 PWINSTATIONNAMEW = PWideChar;
 _WINSTATIONQUERYINFORMATIONW = record
 Reserved1: array[0..71] of byte;
// Res1: array[0..3] of byte;
// WinStationName: array[0..12] of WideChar;
// Res2: array[0..13] of byte;
// ClientName: array[0..12] of WideChar;
// Res3: array[0..1] of byte;
// Reserved1: array[0..35] of WideChar;
 SessionId: Longint;
 Reserved2: array[0..3] of byte;
 ConnectTime: FILETIME;
 DisconnectTime: FILETIME;
 LastInputTime: FILETIME;
 LoginTime: FILETIME;
 Reserved3: array[0..1095] of byte;
// Reserved3: array[0..548] of byte;
 CurrentTime: FILETIME;
 end;
function _WinStationNotifyNewSession(hServer: HANDLE; SessionID: ULONG): Boolean; stdcall;
function WinStationNameFromLogonIdW(hServer: HANDLE; SessionID: ULONG; pWinStationName: PWINSTATIONNAMEW): boolean; stdcall;
{***********************************************************}
{ WinStationShadow: Shadow another user's session }
{ hServer : Handle to Terminal Server }
{ Use WTSOpenServer to obtain or pass }
{ SERVERNAME_CURRENT }
{ }
{ pServerName: ServerName (or IP), can be nil or empty }
{ string for local server }
{ }
{ SessionID : The session you want to shadow }
{ }
{ Hotkey : The hotkey to end the remote control. }
{ must be a Virtual-Key Code. }
{ Supply VK_MULTIPLY for the default (*) }
{ }
{ HKModifier: Key to press in combination with Hotkey, }
{ also a Virtual-Key code. Supply MOD_CONTROL }
{ for the default (CTRL) }
{ }
{ v0.2: }
{ Changed param2 Unknown: ULONG to pServerName: PWideChar }
{***********************************************************}
function WinStationShadow(hServer: Handle; pServerName: PWideChar; SessionID: ULONG; HotKey: ULong; HKModifier: ULong): Boolean; stdcall;
{***********************************************************}
{ WinStationShadowStop: Not needed, is called for you when }
{ pressing the hotkey supplied with WinStationShadow }
{ v0.2: }
{ Added param 3, Unknown: Integer }
{ (note: possibly pServerName: PWideChar; }
{ Not tested!
{***********************************************************}
function WinStationShadowStop(hServer: Handle; SessionID: ULONG; Unknown: Integer): Boolean; stdcall;
{***********************************************************}
{ WinStationConnect: Connect to a session }
{ Target session will be disconnected. When connecting to }
{ another users session, you have to provide password }
{ Password must not be nil, empty string '' is allowed to }
{ connect to owned session }
{ hServer: Handle to Terminal Server }
{ Use WTSOpenServer to obtain or pass }
{ SERVERNAME_CURRENT }
{ }
{ SessionID: The session you want to connect to }
{ }
{ TargetSessionID: The Session which is connected to the }
{ SessionID (LOGINID_CURRENT) }
{ }
{ pPassword: Password for the disconnected session, it's }
{ the Windows password for the user that owns }
{ the session. Supply PWideChar('') for no }
{ password, nil is invalid. }
{ bWait: Boolean, wait until the connect has completed }
{ }
{ v0.3: }
{ Changed to stdcall, changed Unknown to boolean bWait }
{ changed name ActiveSessionID to TargetSessionID }
{ Function tested and working on Windows 2003 }
{***********************************************************}
function WinStationConnectW(hServer: Handle; SessionID: ULong; TargetSessionID: ULong; pPassword: PWideChar; bWait:Boolean): Boolean; stdcall;
{***********************************************************}
{ WinStationQueryInformation: Query Terminal Sessions Info }
{ When using WTSAPI function, this function is called }
{ supply WinStationInformationClass 8 to retrieve Idle Time }
{ and logon time, see helper function GetWTSIdleTime }
{ }
{ hServer: Handle to Terminal Server }
{ Use WTSOpenServer to obtain handle or pass }
{ SERVERNAME_CURRENT }
{ }
{ SessionID: The session you want query }
{***********************************************************}
function WinStationQueryInformationW(hServer: HANDLE; SessionId: ULONG;
 WinStationInformationClass: Cardinal; pWinStationInformation: PVOID;
 WinStationInformationLength: ULONG; var pReturnLength: ULONG):
 Boolean; stdcall;
function GetWTSLogonIdleTime(hServer: Handle; SessionID: ULong; var sLogonTime: String; var sIdleTime: String): Boolean;
function FileTime2DateTime(FileTime: FileTime): TDateTime;
const
 SERVERNAME_CURRENT = HANDLE(0);
 LOGONID_CURRENT = ULONG(-1);
implementation
function _WinStationNotifyNewSession; external 'winsta.dll' name '_WinStationNotifyNewSession';
function WinStationNameFromLogonIdW; external 'winsta.dll' name 'WinStationNameFromLogonIdW';
function WinStationShadow; external 'winsta.dll' Name 'WinStationShadow';
function WinStationShadowStop; external 'winsta.dll' Name 'WinStationShadowStop';
function WinStationConnectW; external 'winsta.dll' Name 'WinStationConnectW';
function WinStationQueryInformationW; external 'winsta.dll' Name 'WinStationQueryInformationW';
function FileTime2DateTime(FileTime: FileTime): TDateTime;
var
 LocalFileTime: TFileTime;
 SystemTime: TSystemTime;
begin
 FileTimeToLocalFileTime(FileTime, LocalFileTime) ;
 FileTimeToSystemTime(LocalFileTime, SystemTime) ;
 Result := SystemTimeToDateTime(SystemTime) ;
end;
function GetWTSLogonIdleTime(hServer: HANDLE; SessionID: ULong; var sLogonTime: String; var sIdleTime: String): Boolean;
var uReturnLength: ULONG;
 info: _WINSTATIONQUERYINFORMATIONW;
 CurrentTime: TDateTime;
 LastInputTime: TDateTime;
 IdleTime: TDateTime;
 LogonTime: TDateTime;
 Days, Hours, Minutes: Word;
 fs: TFormatSettings;
begin
 GetLocaleFormatSettings(LOCALE_SYSTEM_DEFAULT, fs);
 uReturnLength := 0;
 try
 Result := WinStationQueryInformationW(hServer, SessionID, 8, @info,
 sizeof(info), uReturnLength);
 if Result then
 begin
 LogonTime := FileTime2DateTime(Info.LoginTime);
 if YearOf(LogonTime) = 1601 then
 begin
 sLogonTime := '';
 end
 else
 begin
 sLogonTime := DateTimeToStr(LogonTime, fs);
 end;
 { from Usenet post by Chuck Chopp
 http://groups.google.com/group/microsoft.public.win32.programmer.kernel/browse_thread/thread/c6dd86e7df6d26e4/3cf53e12a3246e25?lnk=st&q=WinStationQueryInformationa+group:microsoft.public.*&rnum=1&hl=en#3cf53e12a3246e25
 2) The system console session cannot go into an idle/disconnected state.
 As such, the LastInputTime value will always math CurrentTime for the
 console session.
 3) The LastInputTime value will be zero if the session has gone
 disconnected. In that case, use the DisconnectTime value in place of
 LastInputTime when calculating the current idle time for a disconnected session.
 4) All of these time values are GMT time values.
 5) The disconnect time value will be zero if the sesson has never been
 disconnected.}
 CurrentTime := FileTime2DateTime(Info.CurrentTime);
 LastInputTime := FileTime2DateTime(Info.LastInputTime);
 // Disconnected session = idle since DisconnectTime
 if YearOf(LastInputTime) = 1601 then begin
 LastInputTime := FileTime2DateTime(Info.DisconnectTime);
 end;
// IdleTime := LastInputTime - CurrentTime;
 IdleTime := CurrentTime - LastInputTime;
 Days := Trunc(IdleTime);
 Hours := HourOf(IdleTime);
 Minutes := MinuteOf(IdleTime);
 if Days 0 then
 begin
 sIdleTime := Format('%d + %d:%1.2d', [Days, Hours, Minutes]);
 end
 else if Hours 0 then
 begin
 sIdleTime := Format('%d:%1.2d', [Hours, Minutes]);
 end
 else if Minutes 0 then
 begin
 sIdleTime := IntToStr(Minutes);
 end
 else
 begin
 sIdleTime := '-';
 end;
 end;
 except
 on E: Exception do
 begin
 Result := False;
 end;
 end;
end;
end.