WTS APIs(Windows終端服務API)獲取進程信息
 Windows XP 有一個新特性叫做“快速用戶轉換——Fast User Switching”,這個特性允許多個用戶同時在一臺機器上登陸。當一個用戶登陸后,另一個用戶啟動的進程仍然能夠運行。這個神奇的特性所倚仗的是 WTS APIs。如果你想了解更多有關 WTS 的內容,可以參考 MSJ Oct99 的一篇文章:“Windows NT和 Windows 2000 終端服務APIs介紹”,作者是 Frank Kim。
   Windows XP為每一個登陸用戶創建一個WTS會話(Session)。每個運行進程總是與這樣一個Session關聯。Windows XP的任務管理器允許你列出進程清單,不論是針對所有會話的還是僅僅針對自己的會話,任務管理器對話框的進程標簽中有一個"顯示所有用戶的進程"復選框可 以對此進行選擇。(如圖所示):



任務管理其的進程列表


   如果你想了解某個進程隸屬的 session ID,可以調用 kernel32.dll 輸出的一個 API 函數 ProcessIdToSessionId。給定一個進程的ID,他返回相應的 session ID。有趣的是這個 API 函數不是由 wtsapi32.dll 輸出的,而是出自于 kernel32.dll,前者是所有 Windows 終端服務 APIs 的輸出動態庫。實際上,即使 Windows 終端服務沒有運行起來,Windows 2000 和 Windows XP 都將 session ID 存儲在 PEB 中。
  注意 Windows NT 既不在 PEB 中存儲 session ID,也不從 kernel32.dll 中輸出 ProcessIdToSessionId 函數。當你調用 ProcessIdToSessionId,而 WTS 又沒有運行,這時其返回值總是0。
  除了允許你列出打開的會話之外,WTS 還有一個 API 用于枚舉運行的進程,其實現方式與 PSAPI 和 TOOLHELP32 的實現方式是不同的。我寫了一個類 CWTSWrapper 來打包 WTS 中與進程和會話有關的函數,以便避免與 wtsapi32.dll 進行靜態鏈接。這個類的實現細節請參考下載的源代碼,見 common 目錄的 wrappers.cpp 文件。用 CWTSWrapper 很容易構造象 ProcessXP 這樣的控制臺應用程序。下面是ProcessXP 程序的輸出,它列出了與登陸用戶對應的打開的會話以及會話項下的運行進程。ProcessXP 程序的輸出如下:
3 open sessions
ID State Window Station
0 (WTSActive) Console [Administrator]
1 (WTSDisconnected) [standard]
2 (WTSDisconnected) [Player]
30 running processes
0 0 ?
0 4 System \\NT AUTHORITY\SYSTEM
0 388 smss.exe \\NT AUTHORITY\SYSTEM
0 600 csrss.exe \\NT AUTHORITY\SYSTEM
0 632 winlogon.exe \\NT AUTHORITY\SYSTEM
0 676 services.exe \\NT AUTHORITY\SYSTEM
0 688 lsass.exe \\NT AUTHORITY\SYSTEM
0 856 svchost.exe \\NT AUTHORITY\SYSTEM
0 968 svchost.exe \\NT AUTHORITY\SYSTEM
0 1160 svchost.exe \\NT AUTHORITY\NETWORK SERVICE
0 1192 svchost.exe \\NT AUTHORITY\LOCAL SERVICE
0 1252 spoolsv.exe \\NT AUTHORITY\SYSTEM
0 1888 explorer.exe \\MACHINE\Administrator
0 2004 msmsgs.exe \\MACHINE\Administrator
0 104 svchost.exe \\NT AUTHORITY\SYSTEM
1 1496 csrss.exe \\NT AUTHORITY\SYSTEM
1 1172 winlogon.exe \\NT AUTHORITY\SYSTEM
1 1640 explorer.exe \\MACHINE\standard
1 1900 ctfmon.exe \\MACHINE\standard
1 352 notepad.exe \\MACHINE\standard
1 1896 freecell.exe \\MACHINE\standard
2 416 csrss.exe \\NT AUTHORITY\SYSTEM
2 268 winlogon.exe \\NT AUTHORITY\SYSTEM
2 1784 explorer.exe \\MACHINE\Player
0 1820 msiexec.exe \\NT AUTHORITY\SYSTEM
2 1544 ctfmon.exe \\MACHINE\Player
2 1632 msmsgs.exe \\MACHINE\Player
2 1268 wordpad.exe \\MACHINE\Player
0 1696 wuauclt.exe \\MACHINE\Administrator
0 1996 ProcessXP.exe \\MACHINE\Administrator

   從上面的輸出可以看出,名為 MACHINE 的機器上打開的會話有三各。第一個會話的 ID 為0,狀態為活動(WTSActive 因為它就是運行中的 ProcessXP 所在的會話),產生這個會話的登陸用戶為 Administrator。第二個會話的ID是1,處于斷開狀態(WTSDisconnected),產生這個會話的用戶為標準用戶,此用戶啟動了 Notepad 和 Freecell 程序,用戶Player打開了會話2,并運行WordPad,但目前狀態是斷開的。
   ProcessXP 的源代碼包含在本文可下載的壓縮包中。WTS 有一個與注冊表類似特性,那就是允許你獲取另外一臺機器的信息。這就是為什么WTS枚舉 APIs 函數的第一個參數都是一個服務器句柄。WTS_CURRENT_SERVER_HANDLE 用于當前的機器。第二個參數是保留參數,值應該為0。第三個參數希望的版本,其值應該是1。最后兩個參數用于存放返回的信息。一個用于存放會話數或進程 數。另一個是結構數組的指針,結構可以是描述會話信息的結構,也可以是描述進程信息的結構。就看你是使用哪個枚舉API,是枚舉會話還是枚舉進程。因為數 組的存儲空間是由 WTS 分配的,你必須要記住用 WTSFreeMemory 釋放這個空間。
下面是描述會話的結構:WTS_SESSION_INFO:

typedef struct _WTS_SESSION_INFO{    DWORD SessionId;    LPTSTR pWinStationName;    WTS_CONNECTSTATE_CLASS State;} WTS_SESSION_INFO, * PWTS_SESSION_INFO;

  結構中除了會話的 SessionId,還有會話名 pWinStationName,當前會話的名字是“console”,而其它的會話是無名的。當前的會話狀態為 WTSActive,其它則為 WTSDisconnected。

下面是描述進程的結構 WTS_PROCESS_ INFO:

typedef struct _WTS_PROCESS_INFO {    DWORD SessionId;    DWORD ProcessId;    LPTSTR pProcessName;    PSID pUserSid;} WTS_PROCESS_INFO, * PWTS_PROCESS_INFO;

   SessionId 與 ProcessIdToSessionId 所要找的值一樣,ProcessId 不用說了,是進程ID。最后一個成員 pUserSid 指向安全標示符,描述用戶賬號,用戶正是在這個賬號下運行進程。使用 LookupAccountSid,你可以獲得從 pUserSid 中獲得用戶名。這個信息已經可以通過 CProcess 類中的 GetProcessOwner 獲得,但它是通過進程記號(token),而不是通過 WTS。某些情況下,即便由 WTSEnumerateProcesses 控制對它的提供,要想獲得進程記號也是不可能的,這就是在 Windows XP 環境下要用 WTS API 而不用 PSAPI 或 TOOLHELP32 的緣故


VB代碼例子:

Option Explicit

Private Const WTS_CURRENT_SERVER_HANDLE = 0&

Private Type WTS_PROCESS_INFO
?? SessionID As Long
?? ProcessID As Long
?? pProcessName As Long
?? pUserSid As Long
End Type

Private Declare Function WTSEnumerateProcesses _
?? Lib "wtsapi32.dll" Alias "WTSEnumerateProcessesA" _
?? (ByVal hServer As Long, ByVal Reserved As Long, _
?? ByVal Version As Long, ByRef ppProcessInfo As Long, _
?? ByRef pCount As Long _
?? ) As Long

Private Declare Sub WTSFreeMemory Lib "wtsapi32.dll" _
?? (ByVal pMemory As Long)

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
?? (Destination As Any, Source As Any, ByVal Length As Long)

Private Sub Command1_Click()
?? GetWTSProcesses
End Sub

Private Function GetStringFromLP(ByVal StrPtr As Long) As String
?? Dim b As Byte
?? Dim tempStr As String
?? Dim bufferStr As String
?? Dim Done As Boolean

?? Done = False
?? Do
????? ' Get the byte/character that StrPtr is pointing to.
????? CopyMemory b, ByVal StrPtr, 1
????? If b = 0 Then? ' If you've found a null character, then you're done.
???????? Done = True
????? Else
???????? tempStr = Chr$(b)? ' Get the character for the byte's value
???????? bufferStr = bufferStr & tempStr 'Add it to the string
???????????????
???????? StrPtr = StrPtr + 1? ' Increment the pointer to next byte/char
????? End If
?? Loop Until Done
?? GetStringFromLP = bufferStr
End Function

Private Sub Form_Load()
?? ListView1.View = lvwReport
?? Command1.Caption = "Refresh"

'Add the Column Headers for your ListView Control
?? ListView1.ColumnHeaders.Add 1, "SessionID", "Session ID"
?? ListView1.ColumnHeaders.Add 2, "ProcessID", "Process ID"
?? ListView1.ColumnHeaders.Add 3, "ProcessName", "Process Name"
?? ListView1.ColumnHeaders.Add 4, "UserID", "User ID"

?? GetWTSProcesses
End Sub

Private Sub ListView1_ColumnClick(ByVal ColumnHeader As _
????????????????????????????????? MSComctlLib.ColumnHeader)
' When a ColumnHeader object is clicked, the ListView control is
' sorted by the subitems of that column.
' Set the SortKey to the Index of the ColumnHeader - 1
?? ListView1.SortKey = ColumnHeader.Index - 1
' Set Sorted to True to sort the list.
?? ListView1.Sorted = True
End Sub

Private Sub GetWTSProcesses()
?? Dim RetVal As Long
?? Dim Count As Long
?? Dim i As Integer
?? Dim lpBuffer As Long
?? Dim p As Long
?? Dim udtProcessInfo As WTS_PROCESS_INFO
?? Dim itmAdd As ListItem

?? ListView1.ListItems.Clear
?? RetVal = WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, _
????????????????????????????????? 0&, _
????????????????????????????????? 1, _
????????????????????????????????? lpBuffer, _
????????????????????????????????? Count)
?? If RetVal Then ' WTSEnumerateProcesses was successful
????? p = lpBuffer
????? For i = 1 To Count
' Count is the number of Structures in the buffer
' WTSEnumerateProcesses returns a pointer, so copy it to a
' WTS_PROCESS_INO UDT so you can access its members
????????
???????? CopyMemory udtProcessInfo, ByVal p, LenB(udtProcessInfo)
???????
' Add items to the ListView control
???????? Set itmAdd = ListView1.ListItems.Add(i, , _
???????????????????? CStr(udtProcessInfo.SessionID))
???????? itmAdd.SubItems(1) = CStr(udtProcessInfo.ProcessID)
' Since pProcessName contains a pointer, call GetStringFromLP to get the
' variable length string it points to
???????? itmAdd.SubItems(2) = GetStringFromLP(udtProcessInfo.pProcessName)
???????? itmAdd.SubItems(3) = CStr(udtProcessInfo.pUserSid)

' Increment to next WTS_PROCESS_INO structure in the buffer
???????? p = p + LenB(udtProcessInfo)
????? Next i

????? Set itmAdd = Nothing
????? WTSFreeMemory lpBuffer?? 'Free your memory buffer
?? Else
????? ' Error occurred calling WTSEnumerateProcesses
????? ' Check Err.LastDllError for error code
????? MsgBox "Error occurred calling WTSEnumerateProcesses.? " & _
????? "Check the Platform SDK error codes in the MSDN Documentation" _
????? & " for more information.", vbCritical, "Error " & Err.LastDllError
?? End If
End Sub
???????