如何理解 Windows 的工作階段隔離 ── Session 0・RDP・多使用者同時執行
· 小村 豪 · Session 0, 工作階段隔離, RDP, 遠端桌面, Windows 服務, 多使用者, Windows, .NET, C#, 設計, 技術諮詢
「從服務跳出的對話方塊誰也看不到」「用 RDP 連上去的瞬間,現場畫面就變成登入畫面」「明明應該防止重複啟動,換個使用者登入卻若無其事地啟動了兩個」。在業務應用程式的諮詢中,這類症狀背後,共通的原因往往是對「Windows 的工作階段」這個概念理解不足。工作階段是橫跨服務設計、遠端桌面維運、具名物件防止重複啟動的一個共通主題,卻很少有機會被系統性地說明。
本文將從實務角度整理:Session 0 隔離為什麼被導入、RDP 連線時工作階段會如何運作、具名物件如何依工作階段隔離,以及在共用 PC・RDS(Remote Desktop Services)環境中常見的設計失誤。
1. 先講結論
- 服務會在稱為 Session 0 的專用工作階段中執行,與登入中使用者的互動工作階段是分開的。 服務無法直接顯示對話方塊,就算顯示了,使用者也看不到。1
- 需要 UI 的服務,官方建議的解法是把 UI 應用程式拆到另一個處理程序(使用者工作階段那一側),透過 IPC 對話。 用工作排程器以互動使用者身分啟動也是一個選項。2
- Windows Server(RDS)是以多個同時工作階段為前提設計的,但 Windows 10/11 Pro 等用戶端作業系統基本上是單一工作階段。 除了 Azure Virtual Desktop 專用的「Windows Enterprise 多工作階段」版本之外,一般用戶端作業系統並不支援同時擁有多個互動工作階段。3
- 具名核心物件(Mutex、事件、號誌等)各自擁有依工作階段區分的命名空間。 如果想在整台電腦・跨工作階段限制為一個,不明確指定
Global\前綴就無法如預期防止重複啟動。具名管道不屬於這套機制,預設就是整台電腦(伺服器服務執行時甚至遠端)都能存取的另一種命名空間,因此若要依工作階段或使用者區分,就必須把工作階段 ID 之類的資訊放進管道名稱本身。45 - 目前工作階段的種類可以用
SESSIONNAME環境變數或GetSystemMetrics(SM_REMOTESESSION)判斷。 但使用 RemoteFX vGPU 等情況下判斷結果會偏離本意,不該過度信任。67 - 要不要把共用 PC・RDS 環境下的行為納入需求,應該在設計初期就先確認。 暫存檔路徑衝突、AppData 搞混、裝置・硬體鎖的同時存取,都是以單機為前提做出來的應用程式一旦搬上 RDS 就會立刻爆發的典型事故(第 7 章)。
2. Session 0 隔離是什麼 ── 為什麼要把它和使用者工作階段分開
在 Windows Vista / Windows Server 2008 之前,服務和第一個登入的使用者共用同一個 Session 0。同一個工作階段裡的處理程序,無論權限高低都能互相傳送視窗訊息。標準權限的處理程序,向以 SYSTEM 權限執行的服務視窗送出偽造訊息,藉此劫持服務端的處理來提升權限 ── 這類利用視窗訊息處理缺陷的權限提升,在 2000 年代被反覆報告,實際上 Windows 核心的視窗訊息處理(win32k.sys)缺陷所導致的權限提升漏洞也曾多次被修補。8
Windows Vista 以後,這個結構改變了。Session 0 變成服務,以及不繫結任何互動使用者工作階段的應用程式專用,第一個登入的使用者會分配到 Session 1,下一位使用者是 Session 2……以此類推,被分配到與服務不同的工作階段。Session 0 不支援互動操作,服務無法把訊息傳給應用程式,應用程式也無法傳給服務。服務也無法直接顯示對話方塊之類的 UI 元素。 這項變更,讓標準權限處理程序直接把訊息送進服務視窗的這條路徑,在結構上就被封死了。1
也就是說,Session 0 隔離並不只是「服務是幕後角色所以沒有畫面」這種營運上的限制,而是為了在結構上阻斷跨越權限邊界的視窗訊息交換而做的安全性設計。服務唯一保留下來能顯示 UI 的手段是 WTSSendMessage 函式,這是一個能在別的工作階段顯示簡易訊息方塊的 API,但無法用於複雜的對話方塊或雙向互動。1
3. 服務無法顯示 UI ── 實務上的限制與應對模式
Session 0 隔離在實務上的結果很單純。就算在服務裡呼叫 MessageBox.Show 或顯示表單,登入中使用者的畫面上也不會出現任何東西。更糟的是,還會一直等待沒有人能按下的確定按鈕,讓整個處理卡住,這是經典的當機成因。把舊程式碼(像是 Windows XP 時代以「互動式服務」方式運作的那種)搬進服務時,一定要確認是否還殘留著顯示 UI 的呼叫。
官方建議的解法有兩種。2
- 把 UI 應用程式拆到另一個處理程序(互動使用者工作階段那一側),透過 IPC 對話。 服務端觸發處理,把結果或輸入請求傳給 UI 處理程序。UI 處理程序以通知區域圖示或對話方塊呈現給使用者,並把操作結果回傳給服務。若使用具名管道之類的 IPC,需要妥善設計 ACL,避免透過網路曝露出去。在多使用者共用的環境中,官方建議在管道名稱中加入工作階段 ID 等方式,依工作階段區分。2 該選哪種 IPC 手段,請參考同日發布的姊妹文章「行程間通訊的判斷表」。服務本身的建立方式,以及啟動類型・執行帳戶・復原選項等維運面的設計,整理在「Windows 服務的建立方法」裡。
- 用工作排程器以互動使用者身分啟動。 不讓它以服務方式常駐,而是用登入中使用者的權限・工作階段來啟動具有 UI 的程式。這裡要注意的是,「用 SYSTEM 註冊的工作」本身不該負責顯示 UI。SYSTEM 帳戶沒有互動式登入權,使用者無法看到或操作以 SYSTEM 權限執行的程式或工作。9 提升的工作模型,原本的前提就是讓標準使用者能啟動、具備適當安全性描述元的 SYSTEM 工作,只負責需要管理員權限的處理本身,而 UI 本身則交給以登入中使用者權限・工作階段執行的另一支程式(或把同樣的處理註冊成使用者帳戶的工作)。10 要讓 SYSTEM 工作徹底扮演「不顯示 UI 的代理角色」,看得到的 UI 一定要交給在使用者自己工作階段中執行的處理程序負責。
不管採用哪種方式,從一開始就把「服務是幕後角色,UI 是另一個處理程序」這個前提納入設計才是關鍵。事後才想加進去,服務端往往已經滲入 UI 相依的程式碼,拆分成本就會暴增,這在實務上是常見的走向。
4. RDP 連線時工作階段的行為 ── RDS 與用戶端作業系統的差異
「明明伺服器和用戶端用的都是同一個 RDP,行為卻不一樣」,這種困惑源自有沒有 Remote Desktop Services(RDS)角色。
Windows Server + RDS 角色是讓多位使用者同時登入同一台伺服器,各自在獨立的工作階段中使用桌面或應用程式,以多個同時工作階段為前提的組態。11 在 RDS 中,一般的 RDP 管理連線(管理員用)最多可以 2 個工作階段不需要 CAL 就能使用,但超過這個數量,或一般使用者要同時使用時,就需要導入 RD Session Host 角色與適當的 RDS CAL(用戶端存取授權)。12 CAL 有 per device / per user 兩種方式,授權模型的細節要依環境個別確認。13
另一方面,Windows 10/11 Pro・Enterprise 等一般用戶端作業系統沒有 RDS 角色,基本上以單一工作階段為前提。雖然也存在「Windows Enterprise 多工作階段」這種讓用戶端作業系統能同時擁有多個互動工作階段的特殊版本,但這是以 Azure Virtual Desktop 專用的形式提供,一般的地端部署用戶端作業系統並不支援多使用者同時工作階段。3 實務上,正如「用 RDP 連上公司同事的 PC,現場畫面就變成鎖定畫面」的體感一樣,用戶端作業系統上主控台(現場的實體操作)與遠端連線同時共存的組態基本上不成立,這樣理解比較安全。
整理一下業務應用程式設計時該掌握的差異。
| 觀點 | Windows Server + RDS | 用戶端作業系統(Windows 10/11 Pro 等) |
|---|---|---|
| 同時工作階段 | 多位使用者可同時登入11 | 基本上是單一工作階段。除 AVD 專用多工作階段版本外,不支援多個互動工作階段3 |
| 授權 | 需要 RD Session Host + RDS CAL(2 個管理連線除外)12 | Remote Desktop 功能本身內建於作業系統,但授權需求・允許的版本要另外確認 |
| 預期用途 | 共用終端上的業務應用程式運作、精簡型客戶端環境 | 對個人 PC 的遠端維護・在家工作時連線 |
如果不考慮這個差異,就以為「在開發機的用戶端作業系統上能動,在共用伺服器的 RDS 環境應該也一樣」,就會忽略第 7 章要談的多使用者同時執行特有的缺陷。反過來說,如果只打算在用戶端作業系統上做單一工作階段運作,也不需要一開始就投入支援共用 PC 的設計成本。這個判斷應該在需求定義的早期階段就做好(第 8 章)。
5. 判斷目前的工作階段
想知道自己的處理程序目前在哪個工作階段執行的情境並不少見,例如想在背景處理中抑制較重的繪圖效果,或想在遠端工作階段中停用會佔用頻寬的功能。判斷手段有幾種。
SESSIONNAME環境變數:主控台工作階段會設為Console,RDP 連線則會設成以RDP-Tcp#開頭的名稱。這和qwinsta指令列出的SESSIONNAME欄是同一份資訊。14 可以用於簡易判斷,但不是官方 API,最好不要過度依賴。GetSystemMetrics(SM_REMOTESESSION):判斷是否為遠端工作階段的 Win32 API。若呼叫的處理程序連結到 Terminal Services 用戶端工作階段,會回傳非 0 值。不過從 Windows 8/Server 2012 開始,使用 RemoteFX vGPU 的遠端工作階段中,這個函式會把遠端工作階段誤判為本機工作階段,這是一項限制。這種情況下,官方建議用登錄檔中的GlassSessionId和目前工作階段 ID 做比較,作為替代手段。67- .NET(WPF)的
SystemParameters.IsRemoteSession:呼叫GetSystemMetrics(SM_REMOTESESSION)得到的同一種判斷結果,可以在 WPF 應用程式裡以屬性方式取得。15 在 WinForms 或主控台應用程式中,就要直接用 P/Invoke 呼叫。 WTSGetActiveConsoleSessionId:取得連接到實體主控台的工作階段 ID的 API。拿來和自己的工作階段 ID 比較,就能判斷「自己是不是在實體主控台上執行」。要注意的是,實體主控台沒有任何人連線的狀態(切換中)會回傳0xFFFFFFFF。16
不能把這個 WTSGetActiveConsoleSessionId 用在 RDS 環境的工作階段識別上。 正如其名,它回傳的只有「連接到實體主控台的工作階段」,透過 RDP 登入的使用者工作階段不在範圍內。16 如果服務想知道「該把互動使用者的隨附 UI 顯示在哪個工作階段」,若只考慮主控台連線的使用者,用這個 API 就夠了;但在 RDS 中有多位 RDP 使用者同時登入的環境裡,應該用 WTSEnumerateSessions 列舉正在執行的工作階段17,或直接使用工作階段變更通知(WM_WTSSESSION_CHANGE)傳來的工作階段 ID。這種混淆是「只有透過 RDP 的使用者看不到通知 UI,或者 UI 顯示在錯誤的工作階段」這類難以察覺缺陷的典型原因。在第 3 章的 IPC 組態中決定 UI 處理程序該啟動在哪個工作階段時,一定要意識到這個區別。
6. 具名物件的工作階段隔離
Mutex、事件、號誌、可等待計時器、檔案對應物件之類的具名核心物件,各自擁有依工作階段區分的獨立命名空間。就算在某個工作階段建立了名為 MyAppMutex 的 Mutex,在別的工作階段用同樣的名稱去開啟,也會被當成不同的物件重新建立。對於服務這類主要在 Session 0 執行的處理程序,或是想跨工作階段共享物件的用戶端/伺服器組態,可以在名稱前面加上 Global\,明確配置到全域命名空間。 反過來,加上 Local\ 則會明確配置到自己的工作階段命名空間。4
這在實務上發揮作用的地方,就是用於防止重複啟動的 Mutex。
- 如果只是想「同一使用者在同一工作階段內不能啟動兩個應用程式」,用預設(不加前綴)的 Mutex 名稱就夠了。因為工作階段命名空間會自動生效,在別的使用者工作階段裡會被視為不同的 Mutex。
- 如果是「RDS 環境中即使多位使用者同時登入,也要把整台電腦上的應用程式執行個體限制為一個」,就必須明確指定
Global\,否則無法達成目的。維持預設值的話,每個使用者都能各啟動一個,就會變成「明明該防止重複啟動卻啟動了多個」的缺陷。
另外,非 Session 0 的處理程序若要在全域命名空間中新建檔案對應物件或符號連結物件,需要 SeCreateGlobalPrivilege 權限(只是開啟既有物件則不需要)。這項權限檢查僅限於檔案對應與符號連結,建立 Mutex 或事件並不會被要求,但如果設計上要在全域命名空間使用共享記憶體(記憶體對應檔案),就必須記住這一點。4
此外,作為用戶端/伺服器型應用程式的通用原則,官方也建議不要把「來自同一台機器的連線」等同於「同一個使用者工作階段」。考慮到同一台機器可能同時建立多個工作階段(多位使用者的 RDP 連線),伺服器端應該用 ProcessIdToSessionId 之類的方式識別工作階段,並為每個工作階段準備獨立的通訊通道,這是官方建議的設計。18
7. 共用 PC・RDS 環境中常見的設計失誤
只考慮單機主控台使用的應用程式,一旦搬上共用 PC 或 RDS 環境,就容易爆發幾種固定模式的缺陷。
- 暫存檔路徑衝突。 RDS 預設會為每個工作階段建立各自的暫存資料夾(位於使用者設定檔之下,名稱中包含工作階段 ID)。19 也就是說,只要老實使用
%TEMP%環境變數,就能直接享受到這種隔離的好處。會出事的是應用程式沒用%TEMP%,而把像C:\Work\temp這樣的固定路徑寫死的情況。同一使用者帳戶的多個工作階段,或多位使用者同時寫入同一個固定路徑,自然會發生檔案爭奪。 - AppData 搞混。 使用者設定・快取・記錄該存在哪裡,需要先理解 Windows 使用者設定檔的機制(本機/漫遊、
%LOCALAPPDATA%與%APPDATA%的區分)才能決定。在共用 PC 上,這種設計上的不嚴謹容易以「別人的設定改變了我的畫面」「記錄被覆寫」的形式暴露出來。細節請參考「Windows 使用者設定檔入門」。 - 假設可以同時存取裝置・硬體鎖・序列埠。 要從 RDS 工作階段存取用戶端本機裝置或序列埠,必須透過重新導向機制。如果想讓使用實體 USB 硬體鎖或序列通訊裝置的應用程式,同時被多個工作階段使用,就會遇到硬體本身根本不支援同時存取,或者依重新導向設定不同,裝置有時看得到、有時看不到的問題。用戶端連接的裝置在伺服器端工作階段中看起來如何,取決於重新導向的組態,因此設計階段就必須確認。20
- 印表機・磁碟機寫死。 如果把預設印表機名稱或磁碟機代號寫死在應用程式裡,在每個使用者被重新導向的預設印表機或對應磁碟機都不同的共用 PC 環境中,很容易就會出問題。印表機名稱應該在執行時取得,磁碟機則盡量以 UNC 路徑處理才安全。
- 讀錯 HKCU・使用者登錄檔。 也要注意
HKEY_CURRENT_USER是綁定登入使用者,而不是工作階段。在同一使用者擁有多個工作階段的組態(例如 RDS 中共用同一個帳戶)裡,HKCU 的值會在所有工作階段之間共用,因此如果把「想依工作階段獨立的執行期狀態」放進 HKCU,就會在不知不覺間影響到其他工作階段。需要依工作階段隔離的資訊,應該用第 6 章介紹的工作階段命名空間,或是把工作階段 ID 納入檔案路徑來處理。
這些缺陷都有一個共通性質:「單機不會發現」、「多位使用者同時使用才會顯現」,如果測試環境只有單機的開發機,很容易演變成只在正式環境的共用 PC・RDS 環境中才第一次重現的情況。
8. 判斷表 ── 是否該把「支援共用 PC/RDS」納入需求
要不要把共用 PC・RDS 環境下的行為納入需求,應該在實作開始前的需求定義階段就先確認。一旦決定要納入,請用下列觀點當作檢查清單。
| 觀點 | 只以單機・主控台為前提就夠的情況 | 需要支援共用 PC/RDS(多使用者同時)的情況 |
|---|---|---|
| 暫存檔・設定 | 固定路徑也不太會出實際問題 | 必須採用 %TEMP% / %LOCALAPPDATA% 等 per-user/per-session 路徑(第 7 章) |
| 具名物件 | 維持預設(工作階段命名空間)即可 | 「整台電腦一個」就明確指定 Global\;「每個工作階段一個」維持預設即可;想「把同一使用者的多個工作階段合併成一個」,就在 Global\ 之外加上使用者 SID(第 6 章,亦可參考防止重複啟動的文章) |
| 裝置・硬體鎖 | 本機直連,單純 | 要以會透過重新導向、可能無法同時存取為前提來設計 |
| UI 與服務 | 容易在同一桌面上完成 | 考慮到 Session 0 隔離,用另一個處理程序+IPC 組成(第 3 章) |
| 授權・計費 | 可以單純依台數計算 | 需要在需求定義時確認同時工作階段數的概念、是否需要 RDS CAL13 |
判斷的準則很單純:「這個應用程式只要能在一位負責人的 PC 上獨立運作就好,還是有可能在共用終端或 RDS 環境中被多位負責人同時使用」,應該在開發初期就和發包方達成共識。如果把這一點模糊地帶過就往下實作,最後很可能要把上述觀點全部重做一次。
9. 實作範例 ── 工作階段判斷與命名空間的區分使用
把第 5 章、第 6 章介紹的判斷方法與 Mutex 命名的區分使用,整理成 .NET 程式碼。
using System.Runtime.InteropServices;
internal static class SessionInfo
{
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(int nIndex);
private const int SM_REMOTESESSION = 0x1000;
// 如果是 WPF 應用程式,System.Windows.SystemParameters.IsRemoteSession 會回傳相同的判斷結果
public static bool IsRemoteSession() => GetSystemMetrics(SM_REMOTESESSION) != 0;
// 簡易判斷。因為不是官方 API,重要的分支不要用它,只留給記錄・診斷資訊使用
public static string? SessionName() =>
Environment.GetEnvironmentVariable("SESSIONNAME");
}
用於防止重複啟動的 Mutex,要區分「整台電腦一個」「每個工作階段一個」「同一使用者的多個工作階段合併成一個」這 3 種情況,分別使用不同的命名空間。第 3 種(以使用者為單位・跨工作階段)光靠預設的 Global\ 無法實現,必須把使用者 SID 也放進名稱裡,這一點在「Windows 應用程式的防止重複啟動」中有詳細說明。
// 需要 using System.Security.AccessControl; 與 using System.Security.Principal;
// (也需要 NuGet 套件 System.Threading.AccessControl)
// 意圖:即使 RDS/共用 PC 有多位使用者同時登入,整台電腦上也只有一個執行個體。
// .NET 的 Mutex/MutexAcl.Create,就算是開啟既有的 Mutex,內部也會要求
// SYNCHRONIZE・MUTEX_MODIFY_STATE,再加上 DELETE/READ_CONTROL/WRITE_DAC/WRITE_OWNER。
// 因此如果不允許「已驗證的使用者」擁有相當於 FullControl 的權限,建立者以外的
// 正常使用者在 createdNew 判斷的那一刻就會發生 UnauthorizedAccessException。
// (代價是,任何已驗證的使用者事後都能重寫 ACL。如果這也無法接受,
// 就不要用 Mutex 類別,改用 P/Invoke 直接呼叫 CreateMutexEx,
// 只要求必要最小限度的存取權限)
//
// 注意:這個 ACL 只有在自己搶先建立 Mutex 時才會套用。固定名稱的
// Global\ Mutex 有被其他使用者搶先建立(名稱搶占)的風險,
// 那種情況下 createdGlobal 會是 false,或者依對方的 ACL 設定,
// 也可能變成 UnauthorizedAccessException。真的想擋住的話,
// 就要搭配難以猜測的名稱或其他互斥機制(細節請參考姊妹文章第 5 章)
var globalMutexSecurity = new MutexSecurity();
globalMutexSecurity.AddAccessRule(new MutexAccessRule(
new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, domainSid: null),
MutexRights.FullControl,
AccessControlType.Allow));
using var globalMutex = MutexAcl.Create(
initiallyOwned: false,
name: @"Global\KomuraSoft.MyApp.SingleInstance",
createdNew: out bool createdGlobal,
mutexSecurity: globalMutexSecurity);
// 意圖:每個工作階段只要有一個就好(同一使用者的多個工作階段各自獨立也沒關係)。
// 維持預設的工作階段命名空間即可
using var perSessionMutex = new Mutex(
initiallyOwned: false,
name: "KomuraSoft.MyApp.SingleInstance",
createdNew: out bool createdPerSession);
if (!createdGlobal)
{
// 包含其他工作階段,這台電腦上某處已經在執行了
return;
}
加上 Global\ 的 Mutex,建立本身並不需要特殊權限(如前所述,需要權限檢查的僅限於新建檔案對應物件與符號連結物件)。4 如果意圖是「每個使用者一個」,卻維持預設的工作階段命名空間,就是錯誤的用法。 同一使用者同時擁有已中斷連線的 RDP 工作階段與新的主控台/RDP 工作階段,這種情況並不罕見,此時因為每個工作階段會被視為不同的 Mutex,維持預設值就會讓同一使用者能啟動第二個執行個體。「整台電腦一個」「每個工作階段一個」「每個使用者一個(跨工作階段)」,哪一種才是正確的需求,取決於應用程式的性質,所以命名之前要先確認需求。
10. 總結
Windows 的「工作階段」是一個會同時出現在服務設計、遠端桌面維運、具名物件防止重複啟動這幾個看似不相干的主題中的共通概念。該掌握的骨架可以歸納成 3 點。服務會在 Session 0 這個隔離的特殊工作階段中執行,無法直接顯示 UI;RDS 是以多個同時工作階段為前提設計的,而一般用戶端作業系統則以單一工作階段為基本;以及具名物件擁有依工作階段區分的命名空間,需要依意圖判斷 Global\ 的必要性。
要不要把共用 PC・RDS 環境下的行為納入需求,是一個在實作後段才察覺就會造成大量返工的主題。強烈建議在需求定義階段就確認「這個應用程式是誰、在哪種終端、有多少人會同時使用」。如果有計畫要把既有應用程式部署到共用 PC・RDS 環境,或是需要調查「多人使用就會出問題」這類缺陷的原因,通常邊看實際組態邊抽絲剝繭會比較快,歡迎與我們洽詢。
相關文章
- Windows 服務的建立方法 ── Worker Service 指南
- 如何選擇 Windows 的行程間通訊 ── 判斷表
- Windows 應用程式的防止重複啟動 ── 具名 Mutex 與重複啟動時的視窗喚醒
- Windows 使用者設定檔入門 - AppData 與 NTUSER.DAT
- 在 Windows 應用程式中拆分出「只需要管理員權限的處理」的具體寫法
相關諮詢領域
合同會社小村軟體(KomuraSoft)提供以部署到共用 PC・RDS 環境為前提的 Windows 應用程式設計審查、服務化・IPC 拆分設計,以及多使用者同時使用時特有缺陷的原因調查。
參考連結
-
Microsoft Learn, Service Changes for Windows Vista. 關於 Session 0 隔離的導入背景、服務與應用程式之間無法再互相傳送視窗訊息,以及
WTSSendMessage這個替代手段。 ↩ ↩2 ↩3 -
Microsoft Learn, Interactive Services. 關於服務無法直接與使用者互動、用
CreateProcessAsUser啟動另一個處理程序的 GUI 應用程式並以 IPC 連動的設計,以及以工作階段 ID 區分管道名稱的建議。 ↩ ↩2 ↩3 -
Microsoft Learn, Windows Enterprise multi-session FAQ. 關於能同時擁有多個互動工作階段的多工作階段功能,過去只有 Windows Server 才能做到,以及以 Azure Virtual Desktop 專用形式提供。 ↩ ↩2 ↩3
-
Microsoft Learn, Kernel object namespaces. 關於具名核心物件依工作階段區分的命名空間、
Global\/Local\前綴,以及在全域命名空間新建檔案對應・符號連結物件時需要SeCreateGlobalPrivilege。 ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, Named Pipes. 關於具名管道在安全性檢查範圍內可從任何處理程序存取,伺服器服務執行時甚至可從遠端存取(這是與 Mutex 等
Global\/Local\工作階段命名空間不同機制的依據)。 ↩ -
Microsoft Learn, GetSystemMetrics function (winuser.h). 關於用
SM_REMOTESESSION判斷遠端工作階段的規格。 ↩ ↩2 -
Microsoft Learn, Detecting the Remote Desktop Services environment. 關於
GetSystemMetrics(SM_REMOTESESSION)的範例程式碼,以及使用 RemoteFX vGPU 時會把遠端工作階段誤判為本機工作階段的限制,還有透過GlassSessionId登錄機碼的替代判斷方式。 ↩ ↩2 -
Microsoft Security Bulletin, MS12-034 - Windows and Messages Vulnerability (CVE-2012-0180). 關於 Windows 核心模式驅動程式(win32k.sys)視窗訊息處理缺陷所導致的權限提升漏洞(視窗訊息類權限提升攻擊的一個實例)。 ↩
-
Microsoft Learn, schtasks create. 關於 SYSTEM 帳戶沒有互動式登入權,使用者無法看到或操作以 SYSTEM 權限執行的程式或工作。 ↩
-
Microsoft Learn, Elevated Task Model. 關於標準使用者的應用程式,透過以 SYSTEM 註冊、並設定成標準使用者也能啟動的安全性描述元的工作,來執行需要管理員權限的處理的組態。 ↩
-
Microsoft Learn, Remote Desktop Services overview in Windows Server. 關於 RDS 是提供多工作階段型、以工作階段為基礎的桌面的平台。 ↩ ↩2
-
Microsoft Learn, Troubleshoot Remote desktop disconnected errors. 關於管理用途下最多 2 個同時遠端連線不需要 RDS CAL,超過此數量則需要 RD Session Host 角色與適當的 RDS CAL。 ↩ ↩2
-
Microsoft Learn, License Remote Desktop Services with Client Access Licenses (CALs). 關於 RDS CAL 的 per device / per user 模型差異,以及授權伺服器的發行・追蹤機制。 ↩ ↩2
-
Microsoft Learn, qwinsta. 關於
SESSIONNAME欄位顯示的是分配給工作階段的名稱(主控台是console,RDP 連線則是以rdp-tcp#開頭的名稱)。 ↩ -
Microsoft Learn, SystemParameters.IsRemoteSession Property. 關於能在 WPF 中取得相當於
SM_REMOTESESSION判斷結果的屬性。 ↩ -
Microsoft Learn, WTSGetActiveConsoleSessionId function (winbase.h). 關於取得連接到實體主控台的工作階段 ID,以及切換中會回傳
0xFFFFFFFF。 ↩ ↩2 -
Microsoft Learn, WTSEnumerateSessions function (wtsapi32.h). 關於可以列舉 RD Session Host 伺服器上所有工作階段。 ↩
-
Microsoft Learn, Client/Server application guidelines. 關於不該把「來自同一台機器的連線」等同於「同一個使用者工作階段」,以及用
ProcessIdToSessionId識別工作階段。 ↩ -
Microsoft Learn, Policy CSP - ADMX_TerminalServer (TS_TEMP_PER_SESSION). 關於 Remote Desktop Services 預設會為每個工作階段建立各自的暫存資料夾(位於使用者設定檔之下,名稱中包含工作階段 ID)。 ↩
-
Microsoft Learn, Peripheral hardware guidelines. 關於從 RD Session Host 伺服器看到的用戶端磁碟機・印表機,會依重新導向的組態而改變,以及裝置驅動程式端停用重新導向的機制。 ↩
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
防止 Windows 應用程式重複啟動 ── 具名 Mutex 與重複執行時的視窗前置
整理業務用 Windows 應用程式的常見需求「不讓同一個應用程式啟動兩次」,如何用具名 Mutex 實作。涵蓋 Global\ 與 Local\ 命名空間差異在 RDP 環境中的陷阱、擁有執行緒限制與 AbandonedMutexException、考量 SetForeg...
Windows 的行程間通訊該怎麼選 ── 具名管道 / TCP / gRPC / 共享記憶體 / COM 判斷表
整理 Windows 應用程式之間該如何選擇溝通方式。以判斷表整理具名管道、本機 TCP、gRPC、共享記憶體、檔案協作、COM 各自的強項與陷阱,並從實務角度說明 UI+服務分離・32bit/64bit 橋接・權限邊界等典型架構,以及具名管道的實作範例。
Windows 服務的建立與維運 ── 從工作排程器的取捨到 BackgroundService 服務化
常駐處理該做成 Windows 服務,還是工作排程器就足夠?本文整理判斷表,以及用 .NET Worker Service(UseWindowsService)建立服務的方法、執行帳戶、復原選項、事件記錄、安全停止處理,從實務角度梳理上線維運所需的設計。
Windows應用程式資料儲存位置怎麼選 ── SQLite / JSON / 登錄檔 / Access 判斷表
Windows桌面應用程式的資料該存在哪裡、用什麼格式儲存?本文整理AppData/ProgramData的使用區分,以及SQLite、JSON檔案、登錄檔、Access(.accdb)各自的優勢與陷阱,並附判斷表,從實務角度說明防止資料損毀與位元數問題等注意事項。
在 WinForms/WPF 應用程式中導入 Entra ID 驗證 ── MSAL.NET 與 WAM 代理的實務架構
從實務角度整理在 WinForms/WPF 桌面應用程式中導入 Entra ID(原 Azure AD)驗證的做法。內容涵蓋公用客戶端的概念、ROPC 廢除現況、應用程式註冊、MSAL.NET 的 AcquireTokenSilent 模式、WAM 代理、權杖快取持久化,以...
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
技術諮詢 & 設計審查
協助釐清設計方向、架構邊界、生命週期責任,以及既有 Windows 資產的處理方式。
常見問題
整理諮詢這個主題時常見的問題。
- 為什麼 Windows 服務無法顯示對話方塊?
- 原因是 Windows Vista 以後導入的 Session 0 隔離。服務會在稱為 Session 0 的專用工作階段中執行,與登入中使用者的互動工作階段是分開的。這是為了從結構上阻擋標準權限的處理程序,向以 SYSTEM 權限執行的服務視窗送出偽造訊息以提升權限的攻擊,是一種安全性設計。就算在服務裡顯示對話方塊,使用者也看不到,反而會因為一直等待沒有人能按下的確定按鈕而讓處理卡住。解決方法是把 UI 應用程式拆到使用者工作階段那一側的另一個處理程序,透過 IPC 對話。
- 為什麼用 RDP 連線後,現場的畫面會切換成鎖定畫面?
- 因為 Windows 10/11 Pro 等一般的用戶端作業系統基本上假設只有單一工作階段。主控台(現場的實體操作)與遠端連線同時共存的組態基本上不成立。能讓多位使用者同時登入並各自使用獨立工作階段的,是 Windows Server + RDS(Remote Desktop Services)角色的組態,超過 2 個管理連線的使用需要 RD Session Host 角色與 RDS CAL。Azure Virtual Desktop 專用的「Windows Enterprise 多工作階段」版本是例外。
- 為什麼 RDS 環境中防止重複啟動的 Mutex 不起作用?
- 因為具名核心物件(Mutex、事件、號誌等)各自擁有依工作階段區分的獨立命名空間。沒有前綴的 Mutex 名稱,在別的使用者工作階段中會被當成不同的 Mutex,導致每個使用者都能各啟動一個。如果想在整台電腦上把執行個體限制為一個,就必須明確加上 Global\ 前綴。如果想「把同一使用者的多個工作階段合併成一個」,則要在 Global\ 之外,把使用者 SID 也組合進名稱中。
- 共用 PC・RDS 環境中,業務應用程式誤動作的典型原因是什麼?
- 常見模式有 5 種:沒用 %TEMP% 而把暫存路徑寫死導致的檔案衝突、使用者設定檔(AppData)區分不夠嚴謹、假設可以同時存取 USB 硬體鎖或序列埠、印表機名稱・磁碟機代號寫死,以及把應該依工作階段獨立的狀態放進 HKCU。這些問題在單機環境都不會被發現,只有在多位使用者同時使用時才會顯現,因此是否需要支援共用 PC・RDS,應該在需求定義的早期階段就先確認。
作者檔案
本文作者的個人檔案頁面。
Go Komura
小村軟體有限公司 代表
以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。