كيف تفهم عزل الجلسات في Windows — Session 0 وRDP وتشغيل عدة مستخدمين في وقت واحد
· غو كومورا · Session 0, عزل الجلسات, RDP, سطح المكتب البعيد, خدمات Windows, تعدد المستخدمين, Windows, .NET, C#, التصميم, الاستشارات التقنية
«مربع حوار تعرضه خدمة يكون غير مرئي للجميع.» «بمجرد أن يتصل شخص عبر RDP، تحوّلت شاشة الجهاز الفعلي إلى شاشة تسجيل الدخول.» «كان من المفترض أن تمنع حماية النسخة الواحدة التشغيل المزدوج، لكن تسجيل الدخول بمستخدم آخر شغّل نسخة ثانية دون أي مانع.» تُظهر مهام الاستشارة الخاصة بتطبيقات الأعمال هذه الأعراض مرارًا وتكرارًا، ويكمن خلف جميعها نفس الفجوة: فهم غير مكتمل لمفهوم «الجلسة» (session) في Windows. تتقاطع الجلسات مع تصميم الخدمات، وتشغيل سطح المكتب البعيد، وحماية النسخة الواحدة للكائنات المسمّاة على حدٍ سواء، ومع ذلك من النادر أن يُشرح هذا الموضوع بشكل منهجي في مكان واحد.
يرتّب هذا المقال، من منظور عملي، الأسباب التي دعت إلى إدخال عزل Session 0، وكيفية تصرف الجلسات عند الاتصال عبر RDP، وكيفية عزل الكائنات المسمّاة حسب الجلسة، وأخطاء التصميم الشائعة التي تظهر في بيئات أجهزة الكمبيوتر المشتركة وRDS (Remote Desktop Services).
1. الخلاصة أولًا
- تعمل الخدمات في جلسة مخصصة تُسمى Session 0، معزولة عن الجلسة التفاعلية لأي مستخدم مسجّل الدخول. لا تستطيع الخدمة عرض مربع حوار مباشرةً، وحتى لو حاولت، فلن يراه المستخدم أبدًا.1
- الحل الموصى به رسميًا للخدمة التي تحتاج إلى واجهة مستخدم هو فصل الواجهة إلى عملية منفصلة (تعيش في جلسة المستخدم) والتواصل معها عبر IPC. تشغيل برنامج بواجهة مستخدم كمستخدم تفاعلي عبر جدولة المهام خيار آخر متاح.2
- يُبنى Windows Server (RDS) حول عدة جلسات متزامنة، بينما أنظمة التشغيل العميلة مثل Windows 10/11 Pro أحادية الجلسة بشكل أساسي. باستثناء الإصدار الخاص «Enterprise multi-session» المصمَّم لـ Azure Virtual Desktop، لا تُصمَّم التثبيتات العادية لنظام التشغيل العميل لاستضافة عدة جلسات تفاعلية متزامنة.3
- الكائنات المسمّاة في النواة (Mutex، الأحداث، الإشارات، إلخ) لكل منها مساحة أسماء خاصة بكل جلسة. إذا أردت تقليص كائن إلى نسخة واحدة على مستوى الجهاز بالكامل وعبر الجلسات، يجب أن تضيف صراحةً البادئة
Global\إلى الاسم — وإلا فلن تتصرف حماية النسخة الواحدة كما هو متوقَّع. الأنابيب المسمّاة (named pipes) تقع خارج هذه الآلية: فهي تستخدم افتراضيًا مساحة أسماء منفصلة يمكن الوصول إليها على مستوى الجهاز بالكامل (وعن بُعد أيضًا إذا كانت خدمة الخادم قيد التشغيل)، لذا إذا احتجت إلى التمييز حسب الجلسة أو المستخدم، يجب تضمين شيء مثل معرّف الجلسة في اسم الأنبوب نفسه.45 - يمكن تحديد نوع الجلسة الحالية عبر متغيّر البيئة
SESSIONNAMEأوGetSystemMetrics(SM_REMOTESESSION). مع ذلك، هناك حالات — مثل استخدام RemoteFX vGPU — ينحرف فيها الكشف عن السلوك المقصود، لذا لا تعتمد عليه بشكل أعمى.67 - مسألة ما إذا كان سلوك أجهزة الكمبيوتر المشتركة/RDS يجب أن يكون جزءًا من متطلباتك هي أمر ينبغي تأكيده في بداية مرحلة التصميم تمامًا. تعارضات مسارات الملفات المؤقتة، وارتباك AppData، والوصول المتزامن للأجهزة/المفاتيح (dongles) هي الحوادث الكلاسيكية التي تنفجر في اللحظة التي يُنشر فيها تطبيق مصمَّم لجهاز واحد مستقل على RDS (انظر الفصل 7).
2. ما هو عزل Session 0 — ولماذا حدث الفصل عن جلسات المستخدمين
قبل Windows Vista / Windows Server 2008، كانت الخدمة وأول مستخدم يسجّل الدخول يشتركان في نفس Session 0. يمكن للعمليات ضمن نفس الجلسة تبادل رسائل النوافذ بغض النظر عن مستوى صلاحيات كل منها. كانت عملية بصلاحيات قياسية قادرة على إرسال رسالة مُعدَّة خصيصًا إلى نافذة تملكها خدمة تعمل بصلاحيات SYSTEM، واختطاف معالجة رسائل الخدمة، وتصعيد صلاحياتها — أُبلغ عن هذه الفئة الكاملة من تصعيد الصلاحيات، التي تستغل عيوبًا في معالجة رسائل النوافذ، مرارًا خلال العقد الأول من الألفية، وفي الواقع تم إصلاح عدة ثغرات لتصعيد الصلاحيات تستغل عيوبًا في معالجة رسائل النوافذ في نواة Windows (win32k.sys) على مر السنين.8
تغيّرت هذه الصورة ابتداءً من Windows Vista. أصبحت Session 0 مخصصة حصريًا للخدمات وللتطبيقات غير المرتبطة بجلسة مستخدم تفاعلية، ويُخصَّص الآن لأول مستخدم يسجّل الدخول Session 1، وللمستخدم التالي Session 2، وهكذا — كل واحد في جلسة مختلفة عن جلسة الخدمة. لا تدعم Session 0 التشغيل التفاعلي، ولا تستطيع الخدمات إرسال رسائل إلى التطبيقات، ولا التطبيقات إرسالها إلى الخدمات. كما لا تستطيع الخدمة عرض عناصر واجهة المستخدم مثل مربع الحوار مباشرةً. بهذا التغيير، أُغلق هيكليًا بالضبط المسار الذي كان يسمح لعملية بصلاحيات قياسية بإرسال رسائل مباشرةً إلى نافذة خدمة ما.1
بعبارة أخرى، عزل Session 0 ليس مجرد قيد تشغيلي من نوع «الخدمات هي عمليات تعمل في الخلفية، لذا ليس لديها شاشة» — إنه تصميم أمني يمنع هيكليًا تبادل رسائل النوافذ عبر حدود الصلاحيات. الطريق الوحيد المتبقي للخدمة لعرض أي شيء على الشاشة هو الدالة WTSSendMessage، وهي واجهة برمجة تطبيقات تُظهر مربع رسالة بسيطًا في جلسة أخرى، لكن لا يمكن استخدامها لمربعات حوار معقدة أو للتفاعل ثنائي الاتجاه.1
3. لا تستطيع الخدمات عرض واجهة المستخدم — القيود العملية وأنماط الالتفاف عليها
النتيجة العملية لعزل Session 0 بسيطة. إذا استدعت خدمة MessageBox.Show أو حاولت عرض نموذج (form)، فلن يظهر شيء على شاشة المستخدم المسجّل الدخول. والأسوأ من ذلك، أن هذا سبب كلاسيكي لتعليق العملية بأكملها إلى الأبد في انتظار زر موافق لن يستطيع أحد النقر عليه أبدًا. عند ترحيل كود قديم (شيء كان يعمل في يوم من الأيام كـ«خدمة تفاعلية» في زمن Windows XP) إلى خدمة، تحقّق دائمًا مما إذا كانت قد تبقّت أي استدعاءات لعرض واجهة المستخدم.
هناك حلّان موثّقان رسميًا.2
- فصل واجهة المستخدم إلى عملية منفصلة (تعيش في جلسة المستخدم التفاعلي) والتواصل عبر IPC. تقوم الخدمة بأداء العمل وتمرير النتائج أو طلبات الإدخال إلى عملية واجهة المستخدم. تعرضها عملية واجهة المستخدم للمستخدم كأيقونة في منطقة الإشعارات أو كمربع حوار، وتعيد نتيجة العملية إلى الخدمة. عند استخدام IPC مثل الأنابيب المسمّاة، يجب تصميم قوائم التحكم بالوصول (ACL) بحيث لا يُكشف أي شيء عن غير قصد عبر الشبكة. في البيئات التي يسجّل فيها عدة مستخدمين الدخول جنبًا إلى جنب، التوجيه هو التمييز بين الجلسات، مثلًا بتضمين معرّف الجلسة في اسم الأنبوب.2 لاختيار آلية IPC، راجع المقال المرافق المنشور في نفس اليوم، «جدول قرار للاتصال بين العمليات في Windows». كيفية بناء الخدمة نفسها، إلى جانب جوانب التصميم التشغيلي مثل نوع البدء وحساب التشغيل وخيارات الاسترداد، مشروحة في «كيفية بناء خدمة Windows».
- تشغيل برنامج كمستخدم تفاعلي عبر جدولة المهام. بدلًا من إبقائه مقيمًا كخدمة، يشغّل هذا التكوين برنامجًا بواجهة مستخدم تحت صلاحيات وجلسة المستخدم المسجّل الدخول نفسه. هناك نقطة يجب الانتباه إليها هنا: يجب ألا تكون «مهمة مسجَّلة تحت SYSTEM» أبدًا هي التي تعرض واجهة المستخدم. لا يملك حساب SYSTEM أي حقوق تسجيل دخول تفاعلي، ولا يستطيع المستخدم رؤية برنامج أو مهمة تعمل بصلاحيات SYSTEM أو التفاعل معها.9 نموذج المهمة المرتفعة (elevated task) مصمَّم بحيث تتولى مهمة SYSTEM — التي يسمح واصف الأمان الخاص بها لمستخدم عادي ببدء تشغيلها — فقط العمليات التي تتطلب صلاحيات المسؤول، بينما تعمل واجهة المستخدم نفسها في برنامج منفصل تحت صلاحيات وجلسة المستخدم المسجّل الدخول نفسه (أو كنفس العملية المسجَّلة كمهمة تحت حساب المستخدم)؛ هذا هو كامل أساس تقسيم الأدوار هذا.10 احتفظ بمهمة SYSTEM بصرامة كـ«وسيط بلا واجهة»، ودع الواجهة المرئية تعمل دائمًا في عملية ضمن جلسة المستخدم نفسه.
أيًا كان النهج المُختار، فإن الأمر الأساسي هو دمج مبدأ «تبقى الخدمة خلف الكواليس، وتعيش الواجهة في عملية منفصلة» في التصميم منذ اليوم الأول. ما نلاحظه بانتظام في الممارسة العملية هو أن محاولة إضافة ذلك لاحقًا تصطدم بكود متجذر بعمق بالفعل في الاعتماد على الواجهة داخل الخدمة، مما يرفع تكلفة فصلهما.
4. كيف تتصرف الجلسات عبر RDP — الفرق بين RDS ونظام تشغيل عميل
الارتباك من نوع «إنه نفس RDP على الخادم والعميل، فلماذا يختلف السلوك؟» ينبع من وجود دور Remote Desktop Services (RDS) أو غيابه.
Windows Server مع دور RDS مصمَّم حول عدة مستخدمين يسجّلون الدخول في وقت واحد إلى خادم واحد، كل منهم يعمل في جلسة مستقلة بسطح مكتبه وتطبيقاته الخاصة — تكوين مبني منذ البداية على عدة جلسات متزامنة.11 تحت RDS، تدعم اتصالات RDP الإدارية العادية (للمسؤولين) حتى جلستين دون الحاجة إلى CAL، لكن تجاوز هذا العدد، أو السماح لمستخدمين عاديين بالاتصال في وقت واحد، يتطلب نشر دور RD Session Host إلى جانب تراخيص RDS CAL (ترخيص الوصول للعميل) المناسبة.12 تأتي تراخيص CAL بنموذجين — لكل جهاز ولكل مستخدم — ويجب تأكيد تفاصيل الترخيص لكل بيئة على حدة.13
من ناحية أخرى، نظام تشغيل عميل عادي مثل Windows 10/11 Pro أو Enterprise لا يحمل دور RDS وهو، من حيث المبدأ، أحادي الجلسة. يوجد إصدار خاص من نظام العميل يُسمى «Windows Enterprise multi-session» قادر على الاحتفاظ بعدة جلسات تفاعلية في آن واحد، لكنه يُقدَّم حصريًا لـ Azure Virtual Desktop — التثبيتات العادية لنظام العميل، الموزَّعة محليًا، غير مصمَّمة لدعم عدة جلسات مستخدم متزامنة.3 عمليًا، وتماشيًا مع التجربة المألوفة «اتصلت عبر RDP بجهاز زميل، وتحوّلت الشاشة على مكتبه إلى شاشة القفل»، فمن الأسلم افتراض أنه في أنظمة العميل، لا يمكن في الأساس أن تتعايش جلسة وحدة تحكم فعلية (شخص يشغّل الجهاز محليًا) واتصال بعيد في نفس اللحظة.
فيما يلي ملخّص للاختلافات المهمة لتصميم تطبيقات الأعمال.
| الجانب | Windows Server + RDS | نظام تشغيل عميل (Windows 10/11 Pro، إلخ) |
|---|---|---|
| الجلسات المتزامنة | يمكن لعدة مستخدمين تسجيل الدخول في وقت واحد11 | أحادي الجلسة بشكل أساسي. لا تُدعم عدة جلسات تفاعلية باستثناء إصدار multi-session الحصري لـ AVD3 |
| الترخيص | يتطلب RD Session Host + تراخيص RDS CAL (بالإضافة إلى اتصالي الإدارة)12 | ميزة سطح المكتب البعيد نفسها مضمَّنة في نظام التشغيل، لكن متطلبات الترخيص والإصدارات المسموح بها تحتاج إلى تأكيد منفصل |
| الاستخدام النمطي | تشغيل تطبيقات الأعمال على أطراف مشتركة، بيئات العميل الخفيف | الصيانة عن بُعد لجهاز كمبيوتر شخصي، الوصول أثناء العمل عن بُعد |
عدم مراعاة هذا الاختلاف — بافتراض أنه «عمل على نظام التشغيل العميل في جهاز التطوير الخاص بي، لذا يجب أن يعمل بنفس الطريقة في بيئة RDS الخاصة بالخادم المشترك» — سيجعلك تتجاهل الأخطاء الخاصة بتزامن تعدد المستخدمين التي يتناولها الفصل 7. وعلى العكس، إذا كنت تتوقع فقط تشغيلًا أحادي الجلسة على نظام عميل، فلا حاجة لتحمّل تكلفة تصميم دعم أجهزة الكمبيوتر المشتركة مسبقًا. هذا قرار ينبغي اتخاذه مبكرًا أثناء تعريف المتطلبات (الفصل 8).
5. تحديد الجلسة الحالية
هناك العديد من المواقف التي تريد فيها معرفة الجلسة التي تعمل فيها عمليتك حاليًا — على سبيل المثال لإيقاف تأثير رسم مكلف أثناء معالجة في الخلفية، أو تعطيل ميزة تستهلك عرض النطاق الترددي في جلسة بعيدة. هناك عدة طرق لتحديد ذلك.
- متغيّر البيئة
SESSIONNAME: يُضبط علىConsoleلجلسة وحدة التحكم، أو على اسم يبدأ بـRDP-Tcp#لاتصال RDP. هذه هي نفس المعلومات المعروضة في العمودSESSIONNAMEالذي يسرده الأمرqwinsta.14 قابل للاستخدام لفحص سريع، لكن بما أنه ليس واجهة برمجة تطبيقات رسمية، فمن الأسلم عدم الاعتماد عليه كثيرًا. GetSystemMetrics(SM_REMOTESESSION): واجهة برمجة تطبيقات Win32 تخبرك ما إذا كنت في جلسة بعيدة. تُرجع قيمة غير صفرية إذا كانت العملية المستدعية مرتبطة بجلسة عميل Terminal Services. مع ذلك، هناك تحذير: ابتداءً من Windows 8/Server 2012، في الجلسات البعيدة التي تستخدم RemoteFX vGPU، تُبلّغ هذه الدالة خطأً بأن الجلسة البعيدة محلية. في هذه الحالة، الحل الموثّق البديل هو مقارنة قيمةGlassSessionIdفي السجل مع معرّف الجلسة الحالي.67SystemParameters.IsRemoteSessionفي .NET (WPF): تعرض نفس الفحص الذي تؤديهGetSystemMetrics(SM_REMOTESESSION)، كخاصية يمكن قراءتها من تطبيق WPF.15 من WinForms أو تطبيق وحدة تحكم، يجب استدعاؤها مباشرةً عبر P/Invoke.WTSGetActiveConsoleSessionId: واجهة برمجة تطبيقات تحصل على معرّف الجلسة المتصلة بوحدة التحكم الفعلية. مقارنتها بمعرّف جلستك الخاص تتيح تحديد «هل أعمل على وحدة التحكم الفعلية؟». لاحظ أنه عندما لا يكون أحد متصلًا بوحدة التحكم الفعلية (أي أثناء الانتقال)، تُرجع0xFFFFFFFF.16
لا تستخدم WTSGetActiveConsoleSessionId لتحديد الجلسات في بيئة RDS. وفاءً لاسمها، كل ما تُرجعه هو «الجلسة المتصلة بوحدة التحكم الفعلية» — لا يُشمل المستخدم المسجّل الدخول عبر RDP.16 إذا احتاجت خدمة إلى معرفة الجلسة التي يجب عرض واجهة المستخدم المرافقة لها فيها، فهذه الواجهة كافية عندما تتوقع فقط مستخدمًا متصلًا بوحدة التحكم، لكن في بيئة RDS حيث يمكن لعدة مستخدمي RDP تسجيل الدخول في وقت واحد، ينبغي بدلًا من ذلك تعداد الجلسات قيد التشغيل باستخدام WTSEnumerateSessions17، أو ببساطة استخدام معرّف الجلسة الذي يوفّره إشعار تغيير الجلسة (WM_WTSSESSION_CHANGE). هذا الخلط سبب كلاسيكي يصعب اكتشافه لأخطاء لا تظهر فيها واجهة الإشعار على الإطلاق — أو تظهر في الجلسة الخاطئة — تحديدًا بالنسبة للمستخدمين المتصلين عبر RDP. احتفظ بهذا التمييز حاضرًا في ذهنك بقوة عند تحديد الجلسة التي ستشغّل فيها عملية واجهة المستخدم لتكوين IPC الموصوف في الفصل 3.
6. عزل الكائنات المسمّاة حسب الجلسة
الكائنات المسمّاة في النواة — Mutex، الأحداث، الإشارات، المؤقتات القابلة للانتظار، كائنات تعيين الملفات — لكل منها مساحة أسماء مستقلة لكل جلسة. حتى لو أنشأت في جلسة ما Mutex باسم MyAppMutex، فإن محاولة فتح كائن بنفس الاسم من جلسة مختلفة تُنشئ كائنًا جديدًا ومنفصلًا تمامًا. بالنسبة للعمليات التي تعمل بشكل أساسي في Session 0، مثل الخدمات، أو لتكوينات العميل/الخادم التي تريد مشاركة كائن عبر عدة جلسات، يمكنك وضعه صراحةً في مساحة الأسماء العامة (global) بإضافة البادئة Global\ إلى الاسم. على العكس، إضافة البادئة Local\ تضعه صراحةً في مساحة أسماء جلستك الخاصة.4
هذا هو المكان الذي يهم فيه هذا الأمر حقًا في الممارسة العملية: Mutex المُستخدَم لحماية النسخة الواحدة.
- إذا كان كل ما تريده هو «منع نفس المستخدم من تشغيل نسختين من التطبيق ضمن نفس الجلسة»، فإن اسم Mutex الافتراضي (بدون بادئة) كافٍ. تعمل مساحة الأسماء لكل جلسة تلقائيًا، لذا تُعامَل جلسة مستخدم مختلف على أنها Mutex مختلف.
- إذا أردت «حتى مع تسجيل دخول عدة مستخدمين في وقت واحد في بيئة RDS، قصر التطبيق على نسخة واحدة على مستوى الجهاز بالكامل»، فلن تتمكن من تحقيق هذا الهدف إلا باستخدام
Global\صراحةً. إذا تُرك على الوضع الافتراضي، ينتهي الأمر بكل مستخدم قادرًا على تشغيل نسخته الخاصة — خطأ يظهر كـ«كان من المفترض أن تعمل حماية النسخة الواحدة، لكن عدة نسخ عملت رغم ذلك».
لاحظ أيضًا أن العملية خارج Session 0 تحتاج إلى صلاحية SeCreateGlobalPrivilege لإنشاء كائن تعيين ملف جديد أو كائن رابط رمزي (symbolic link) جديد في مساحة الأسماء العامة (فتح كائن موجود بالفعل لا يتطلب ذلك). ينطبق فحص هذه الصلاحية فقط على تعيينات الملفات والروابط الرمزية — ولا يُفرض على إنشاء Mutex أو الأحداث — لكن يجدر تذكّره لأي تصميم يستخدم ذاكرة مشتركة (ملف مُعيَّن في الذاكرة) في مساحة الأسماء العامة.4
هناك أيضًا مبدأ عام لتطبيقات العميل/الخادم: التوجيه هو عدم معادلة «اتصال من جهاز واحد» بـ«جلسة مستخدم واحدة». بالنظر إلى أن عدة جلسات (اتصالات RDP لعدة مستخدمين) يمكن إنشاؤها في وقت واحد من نفس الجهاز، يُوصى بأن يحدد الجانب الخادم الجلسات باستخدام، على سبيل المثال، ProcessIdToSessionId، وأن يُخصِّص قناة اتصال منفصلة لكل جلسة.18
7. أخطاء تصميم شائعة في أجهزة الكمبيوتر المشتركة وبيئات RDS
هناك حفنة من أنماط الفشل المتكررة التي تنفجر في اللحظة التي يُنشر فيها تطبيق مصمَّم مع مراعاة استخدام جهاز واحد فقط، وحدة تحكم فقط، على جهاز كمبيوتر مشترك أو بيئة RDS.
- تعارضات مسارات الملفات المؤقتة. بشكل افتراضي، تُنشئ RDS مجلدًا مؤقتًا منفصلًا لكل جلسة (تحت ملف تعريف المستخدم، مع تضمين معرّف الجلسة في الاسم).19 بعبارة أخرى، إذا كان تطبيقك يستخدم ببساطة متغيّر البيئة
%TEMP%، فإنه يحصل مجانًا على فائدة هذا العزل. تبدأ المشاكل عندما يُشفِّر التطبيق مسارًا ثابتًا مثلC:\Work\tempبدلًا من استخدام%TEMP%. إذا كتبت عدة جلسات لنفس حساب المستخدم، أو عدة مستخدمين، في نفس المسار الثابت في وقت واحد، فبالطبع سيتنازعون على الملف. - ارتباك AppData. يجب اتخاذ قرار مكان حفظ إعدادات المستخدم وذاكرات التخزين المؤقت (caches) والسجلات (logs) مع فهم متين لآلية ملفات تعريف المستخدم في Windows — المحلي مقابل المتجوّل (roaming)، ومتى يُستخدَم
%LOCALAPPDATA%بدلًا من%APPDATA%. على جهاز كمبيوتر مشترك، يميل التصميم المُهمَل في هذه النقطة إلى الظهور كـ«تغيّرت شاشتي بسبب إعدادات شخص آخر» أو «تمت الكتابة فوق السجل». راجع «مقدمة إلى ملفات تعريف مستخدم Windows» للتفاصيل. - افتراض الوصول المتزامن للأجهزة والمفاتيح (dongles) والمنافذ التسلسلية. يمر الوصول إلى جهاز محلي على جانب العميل أو منفذ تسلسلي من جلسة RDS عبر آلية إعادة توجيه (redirection). إذا حاولت السماح لعدة جلسات باستخدام تطبيق يعتمد على مفتاح USB فعلي أو جهاز اتصال تسلسلي في وقت واحد، تواجه مشاكل حيث قد لا يدعم الجهاز نفسه الوصول المتزامن من الأساس، أو تعتمد الرؤية على تكوين إعادة التوجيه وقد تكون موجودة أو غير موجودة. تعتمد طريقة ظهور جهاز متصل من جانب العميل من جلسة على جانب الخادم على تكوين إعادة التوجيه، لذا يجب تأكيد ذلك في مرحلة التصميم.20
- الطابعات ومحركات الأقراص المُشفَّرة بشكل ثابت. تشفير اسم طابعة افتراضية أو حرف محرك أقراص بشكل ثابت داخل تطبيق ينهار بسهولة في بيئة جهاز كمبيوتر مشترك حيث تختلف الطابعة الافتراضية المُعاد توجيهها أو محرك الأقراص المُعيَّن حسب المستخدم. الأسلم هو الحصول على اسم الطابعة وقت التشغيل، والتعامل مع محركات الأقراص، كلما أمكن، كمسارات UNC.
- قراءة خاطئة لـ HKCU / سجل المستخدم. يجدر أيضًا ملاحظة أن
HKEY_CURRENT_USERمرتبط بالمستخدم المسجّل الدخول، وليس بالجلسة. في تكوين يملك فيه نفس المستخدم عدة جلسات (مثلًا، بمشاركة نفس الحساب على RDS)، تُشارَك قيم HKCU عبر جميع جلسات ذلك المستخدم — لذا فإن تخزين حالة وقت تشغيل تريدها في الواقع معزولة حسب الجلسة في HKCU سيؤدي إلى تسرّبها عن غير قصد إلى الجلسات الأخرى. المعلومات التي تتطلب عزلًا حسب الجلسة ينبغي بدلًا من ذلك استخدام مساحات الأسماء لكل جلسة من الفصل 6، أو مسار ملف يتضمن معرّف الجلسة.
جميع هذه الأمثلة هي من نوع الأخطاء التي «لن تُلاحَظ أبدًا على جهاز كمبيوتر واحد مستقل» والتي «تظهر فقط عندما يستخدم عدة مستخدمين التطبيق فعليًا في وقت واحد» — لذا فإن المسار النمطي هو أن بيئة اختبار تتكون فقط من جهاز تطوير مستقل واحد لا تُعيد إنتاج المشكلة أبدًا، وتظهر لأول مرة فقط على جهاز كمبيوتر مشترك أو بيئة RDS إنتاجية.
8. جدول القرار — هل يجب أن يكون دعم «جهاز الكمبيوتر المشترك/RDS» جزءًا من المتطلبات؟
مسألة ما إذا كان سلوك جهاز الكمبيوتر المشترك/RDS يجب أن يكون جزءًا من متطلباتك هي أمر ينبغي تأكيده أثناء تعريف المتطلبات، قبل بدء التنفيذ. بمجرد أن تقرر تضمينه، استخدم النقاط التالية كقائمة تحقق.
| الجانب | من المقبول افتراض جهاز مستقل/وحدة تحكم فقط | يلزم دعم جهاز كمبيوتر مشترك/RDS (تعدد مستخدمين متزامن) |
|---|---|---|
| الملفات المؤقتة / الإعدادات | مسار ثابت يسبب ضررًا حقيقيًا قليلًا | فرض مسارات لكل مستخدم/لكل جلسة مثل %TEMP% / %LOCALAPPDATA% (الفصل 7) |
| الكائنات المسمّاة | من المقبول ترك الوضع الافتراضي (مساحة أسماء الجلسة) | إذا احتجت إلى «نسخة واحدة على مستوى الجهاز بالكامل»، استخدم Global\ صراحةً. إذا كانت «نسخة واحدة لكل جلسة» كافية، اترك الوضع الافتراضي كما هو. إذا أردت «نسخة واحدة لكل مستخدم، تشمل جلسات ذلك المستخدم»، اجمع بين Global\ ومعرّف الأمان SID الخاص بالمستخدم (الفصل 6؛ راجع أيضًا مقال حماية النسخة الواحدة) |
| الأجهزة / المفاتيح (dongles) | اتصال محلي مباشر وبسيط | صمِّم مع مراعاة أن الوصول يمر عبر إعادة التوجيه وأن الوصول المتزامن قد لا يكون ممكنًا |
| الواجهة والخدمات | سهل إبقاء كل شيء على سطح مكتب واحد | افصل إلى عملية منفصلة + IPC، في ضوء عزل Session 0 (الفصل 3) |
| الترخيص / الفوترة | بسيط للعد حسب عدد الأجهزة | أكِّد مفهوم عدد الجلسات المتزامنة وما إذا كانت تراخيص RDS CAL مطلوبة، أثناء تعريف المتطلبات13 |
محور القرار بسيط: اتفق مع العميل مبكرًا أثناء التطوير على ما إذا كان «هذا التطبيق يجب أن يعمل فقط بشكل مستقل على جهاز شخص واحد» أو «قد يستخدمه عدة أشخاص في وقت واحد على طرف مشترك أو بيئة 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;
// فحص سريع وتقريبي. ليس واجهة برمجة تطبيقات رسمية، لذا اقصره على
// التسجيل/التشخيص وليس على القرارات المهمة
public static string? SessionName() =>
Environment.GetEnvironmentVariable("SESSIONNAME");
}
يجب أن يميّز Mutex المُستخدَم لحماية النسخة الواحدة بين ثلاث حالات: «واحدة على مستوى الجهاز بالكامل»، و«واحدة لكل جلسة»، و«واحدة لكل مستخدم، تشمل جلسات ذلك المستخدم». لا يمكن تحقيق الحالة الثالثة (لكل مستخدم، عبر الجلسات) بالبادئة الافتراضية Global\ وحدها — يجب أن يتضمن الاسم أيضًا معرّف الأمان SID الخاص بالمستخدم. هذا مشروح بالتفصيل في «منع التشغيل المزدوج في تطبيقات Windows».
// يتطلب: using System.Security.AccessControl; و using System.Security.Principal;
// (يتطلب أيضًا حزمة NuGet التالية: System.Threading.AccessControl)
// النية: حتى مع تسجيل دخول عدة مستخدمين في وقت واحد على RDS/جهاز كمبيوتر مشترك،
// الحفاظ على نسخة واحدة بالضبط على مستوى الجهاز بالكامل.
// يطلب Mutex/MutexAcl.Create في .NET داخليًا SYNCHRONIZE و MUTEX_MODIFY_STATE،
// بالإضافة إلى DELETE/READ_CONTROL/WRITE_DAC/WRITE_OWNER، حتى لمجرد فتح
// Mutex موجود. هذا يعني أنه ما لم يُمنح «المستخدمون المصادَق عليهم»
// ما يعادل FullControl، فإن مستخدمًا شرعيًا غير المُنشئ سيحصل على
// UnauthorizedAccessException عند لحظة فحص createdNew.
// (المقايضة: هذا يترك أيضًا قائمة التحكم بالوصول ACL قابلة للتعديل لاحقًا من
// قِبل أي مستخدم مصادَق عليه. إذا كان ذلك غير مقبول، تخلَّ عن فئة Mutex
// واستدعِ CreateMutexEx مباشرةً عبر P/Invoke، طالبًا فقط حقوق الوصول
// الدنيا اللازمة)
//
// تحذير: تنطبق قائمة التحكم بالوصول ACL هذه فقط إذا كنت أنت من نجح في
// إنشاء Mutex أولًا. يواجه Mutex من نوع Global\ باسم ثابت خطر أن يُنشئه
// مستخدم آخر مسبقًا (احتلال الاسم/name squatting)، وفي هذه الحالة يعود
// createdGlobal بقيمة false، أو تحصل على 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;
}
إنشاء Mutex بالبادئة Global\ نفسه لا يتطلب أي صلاحية خاصة (كما ذُكر سابقًا، ينطبق فحص الصلاحية فقط على إنشاء كائنات تعيين ملفات وروابط رمزية جديدة).4 من الخطأ ترك Mutex في مساحة أسماء الجلسة الافتراضية إذا كانت النية «واحدة لكل مستخدم». ليس من غير المألوف أن يحتفظ نفس المستخدم في آن واحد بجلسة RDP مقطوعة الاتصال وجلسة وحدة تحكم/RDP جديدة، وفي هذه الحالة تُعامَل كل جلسة كـMutex منفصل — لذا فإن ترك الوضع الافتراضي كما هو قد يؤدي إلى أن يشغّل نفس المستخدم نسخة ثانية في النهاية. أي من «واحدة على مستوى الجهاز بالكامل» أو «واحدة لكل جلسة» أو «واحدة لكل مستخدم عبر الجلسات» هو المتطلب الصحيح يعتمد على طبيعة التطبيق، لذا أكِّد المتطلب قبل الاستقرار على مخطط تسمية.
10. الخلاصة
«الجلسة» في Windows مفهوم يستمر في الظهور عبر موضوعات تبدو للوهلة الأولى غير مترابطة — تصميم الخدمات، وتشغيل سطح المكتب البعيد، وحماية النسخة الواحدة للكائنات المسمّاة. يمكن اختصار البنية المركزية في ثلاث نقاط. تعمل الخدمة في Session 0، وهي جلسة خاصة ومعزولة، ولا تستطيع عرض واجهة المستخدم مباشرةً. تُبنى RDS حول عدة جلسات متزامنة، بينما يكون نظام تشغيل عميل عادي أحادي الجلسة بشكل أساسي. وتملك الكائنات المسمّاة مساحة أسماء لكل جلسة، مما يتطلب الحكم، بناءً على النية، على ما إذا كانت Global\ مطلوبة.
مسألة ما إذا كان سلوك جهاز الكمبيوتر المشترك/RDS يجب أن يكون جزءًا من متطلباتك هي موضوع يتسبب ملاحظته متأخرًا أثناء التنفيذ في قدر كبير من إعادة العمل. نوصي بشدة بالتأكيد، أثناء تعريف المتطلبات، على «مَن سيستخدم هذا التطبيق، وعلى أي أجهزة، وكم عدد الأشخاص في وقت واحد». إذا كنت تخطط لنشر تطبيق موجود على بيئة جهاز كمبيوتر مشترك أو RDS، أو تحتاج إلى التحقيق في خطأ حيث «تسوء الأمور عندما يستخدمه عدة مستخدمين»، فعادةً ما يكون التشخيص أسرع بالنظر إلى التكوين الفعلي — لا تتردد في التواصل معنا.
مقالات ذات صلة
- كيفية بناء خدمة Windows — دليل Worker Service
- جدول قرار للاتصال بين العمليات في Windows
- منع التشغيل المزدوج في تطبيقات Windows — Mutex المسمّى وتفعيل نسخة موجودة
- مقدمة إلى ملفات تعريف مستخدم Windows — AppData و NTUSER.DAT
- طريقة ملموسة لعزل «العمليات التي تتطلب صلاحيات المسؤول فقط» في تطبيق Windows
مجالات استشارية ذات صلة
تتعامل KomuraSoft LLC (合同会社小村ソフト) مع مراجعات تصميم تطبيقات Windows الموجَّهة نحو نشر محتمل على أجهزة كمبيوتر مشتركة أو بيئات RDS، وتصميم فصل الخدمة/IPC، والتحقيق في الأسباب الجذرية للأخطاء الخاصة بالاستخدام المتزامن لعدة مستخدمين.
- الاستشارات التقنية / مراجعة التصميم
- تطوير تطبيقات Windows
- تحديث وصيانة برمجيات Windows الحالية
- اتصل بنا
روابط مرجعية
-
Microsoft Learn, Service Changes for Windows Vista. حول خلفية إدخال عزل Session 0، وعدم قدرة الخدمات والتطبيقات الناتج عن ذلك على تبادل رسائل النوافذ، والدالة
WTSSendMessageكبديل. ↩ ↩2 ↩3 -
Microsoft Learn, Interactive Services. حول أسباب عدم قدرة الخدمة على التفاعل مباشرةً مع المستخدم، والتصميم الذي يستخدم
CreateProcessAsUserلتشغيل عملية GUI منفصلة والتنسيق عبر IPC، والتوجيه بتمييز أسماء الأنابيب حسب معرّف الجلسة. ↩ ↩2 ↩3 -
Microsoft Learn, Windows Enterprise multi-session FAQ. حول قدرة multi-session — الاحتفاظ بعدة جلسات تفاعلية في آن واحد — التي كانت ممكنة سابقًا فقط على Windows Server، والتي تُقدَّم الآن حصريًا لـ Azure Virtual Desktop. ↩ ↩2 ↩3
-
Microsoft Learn, Kernel object namespaces. حول مساحة الأسماء لكل جلسة للكائنات المسمّاة في النواة، والبادئتين
Global\/Local\، وضرورة صلاحيةSeCreateGlobalPrivilegeلإنشاء كائن تعيين ملف جديد أو رابط رمزي جديد في مساحة الأسماء العامة. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, Named Pipes. حول كون الأنابيب المسمّاة قابلة للوصول من أي عملية ضمن حدود فحوصات الأمان، ويمكن الوصول إليها عن بُعد إذا كانت خدمة الخادم قيد التشغيل (الأساس الذي يفسّر سبب كونها آلية مختلفة عن مساحات أسماء الجلسة
Global\/Local\التي تستخدمها Mutex وغيرها). ↩ -
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. حول إمكانية اتصالين بعيدين متزامنين لأغراض إدارية دون ترخيص RDS CAL، وضرورة دور RD Session Host وتراخيص RDS CAL المناسبة لما يتجاوز ذلك. ↩ ↩2
-
Microsoft Learn, License Remote Desktop Services with Client Access Licenses (CALs). حول الفرق بين نموذجي ترخيص RDS CAL لكل جهاز ولكل مستخدم، وآلية إصدار/تتبع خادم الترخيص. ↩ ↩2
-
Microsoft Learn, qwinsta. حول عرض العمود
SESSIONNAMEللاسم المخصَّص لجلسة ما (consoleلوحدة التحكم، أو اسم يبدأ بـrdp-tcp#لاتصال RDP). ↩ -
Microsoft Learn, SystemParameters.IsRemoteSession Property. حول الخاصية التي تعرض من WPF الفحص المكافئ لـ
SM_REMOTESESSION. ↩ -
Microsoft Learn, WTSGetActiveConsoleSessionId function (winbase.h). حول الحصول على معرّف الجلسة المتصلة بوحدة التحكم الفعلية، وإرجاع
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 بشكل افتراضي مجلدًا مؤقتًا منفصلًا لكل جلسة (تحت ملف تعريف المستخدم، مع معرّف الجلسة في الاسم). ↩
-
Microsoft Learn, Peripheral hardware guidelines. حول كيفية ظهور محركات الأقراص والطابعات على جانب العميل من جلسة على خادم RD Session Host حسب تكوين إعادة التوجيه، وآلية السماح لبرنامج تشغيل الجهاز بتعطيل إعادة التوجيه. ↩
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
أيقونات علبة النظام والإشعارات المنبثقة في تطبيقات Windows — مزالق NotifyIcon وكيفية اختيار AppNotification المناسب
دليل عملي لإبقاء تطبيق Windows الخاص بالأعمال مقيمًا في علبة النظام (منطقة الإشعارات) وإخطار المستخدم عبر الإشعارات المنبثقة (Toast). يتن...
إلى متى ستستمرّ تطبيقات VB6 في العمل ── وضع دعم بيئة التشغيل والمسار العمليّ للترحيل إلى .NET
إلى متى تستمرّ تطبيقات VB6 في العمل؟ يُنظّم هذا المقال التناقض بين سياسة دعم بيئة تشغيل VB6 (المدعومة حتّى على Windows 11) وحقيقة انتهاء ...
فهم نموذج OSI حقًا — تشريح طلب HTTP واحد إلى طبقاته السبع
نفهم نموذج OSI من خلال التجربة الفعلية بدلًا من الحفظ. نقوم بتجميع وتشريح إطار Ethernet الذي يحمل طلب HTTP GET واحدًا باستخدام C#، ونتحقق...
قائمة تحقّق للتعامل الآمن مع child processes في تطبيقات Windows - Job Objects ونشر الـ exit وstdio وتصميم الـ watchdog
دليل تصميم متكامل للتعامل الآمن مع child processes في تطبيقات Windows عبر Job Objects ونشر الـ exit وتصريف stdio ووضع الـ watchdog خارج ا...
ما هو .NET Generic Host - شرح DI والإعدادات والـ logging و BackgroundService
مقدمة عمليّة إلى .NET Generic Host وكيف يجمع DI والإعدادات والـ logging و BackgroundService في تطبيقات console و worker، مع متى يفيد فعلا...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
ندعم تطوير برامج ويندوز للأعمال، وتكامل الأجهزة، وأدوات التواصل.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- لماذا لا تستطيع خدمة Windows عرض مربع حوار؟
- منذ Windows Vista، تعمل الخدمات في Session 0 مخصصة، معزولة عن الجلسة التفاعلية لأي مستخدم مسجّل الدخول، لذلك تكون واجهة المستخدم التي ترفعها خدمة ما غير مرئية للجميع — ويمكن لاستدعاء MessageBox أن يُعلّق العملية إلى الأبد في انتظار الضغط على زر موافق لا يستطيع أحد النقر عليه. هذا العزل هو تصميم أمني يمنع هيكليًا تبادل رسائل النوافذ عبر حدود الصلاحيات، مما يغلق فئة كاملة من هجمات تصعيد الصلاحيات. الحل الموصى به رسميًا هو فصل واجهة المستخدم إلى عملية منفصلة تعيش في جلسة المستخدم والتواصل مع الخدمة عبر IPC مثل الأنابيب المسمّاة (named pipes)؛ وتشغيل برنامج بواجهة مستخدم كمستخدم تفاعلي عبر جدولة المهام (Task Scheduler) خيار موثّق آخر.
- هل يمكن لعدة مستخدمين الاتصال بـ Windows 10 أو 11 عبر RDP في وقت واحد؟
- لا — أنظمة التشغيل العميلة العادية مثل Windows 10/11 Pro أو Enterprise هي أنظمة أحادية الجلسة (single-session) بشكل أساسي، لذا لا يمكن في الأساس أن تتعايش جلسة وحدة التحكم الفعلية (console) مع اتصال بعيد في آن واحد؛ فالاتصال عبر RDP يحوّل الشاشة المحلية إلى شاشة القفل. تتطلب الجلسات التفاعلية المتزامنة المتعددة Windows Server مع دور Remote Desktop Services، الذي يحتاج إلى دور RD Session Host وتراخيص RDS CAL بالإضافة إلى اتصالي الإدارة. إصدار العميل الوحيد الذي يدعم تعدد الجلسات، وهو Windows Enterprise multi-session، يُقدَّم حصريًا لـ Azure Virtual Desktop.
- لماذا لا يمنع Mutex الخاص بي (للنسخة الواحدة) التشغيل من جلسات مستخدمين آخرين؟
- الكائنات المسمّاة في النواة (kernel) مثل Mutex والأحداث (events) والإشارات (semaphores) لكل منها مساحة أسماء (namespace) خاصة بكل جلسة. يقع Mutex الذي أُنشئ بدون بادئة (prefix) في مساحة أسماء الجلسة الحالية، لذا تُعامَل جلسة مستخدم آخر (أو حتى جلسة ثانية لنفس المستخدم) على أنها كائن جديد ومنفصل تمامًا، وتفشل حماية النسخة الواحدة بصمت. إذا أردت نسخة واحدة على مستوى الجهاز بالكامل وعبر جميع الجلسات، يجب أن تضيف صراحةً البادئة Global\ إلى الاسم. وإذا أردت نسخة واحدة لكل مستخدم تشمل جميع جلسات ذلك المستخدم، اجمع بين Global\ ومعرّف الأمان SID الخاص بالمستخدم مضمّنًا في الاسم.
- كيف يمكن لتطبيقي اكتشاف ما إذا كان يعمل ضمن جلسة سطح مكتب بعيد؟
- الفحص القياسي هو واجهة برمجة التطبيقات Win32 التالية: GetSystemMetrics(SM_REMOTESESSION)، التي تُرجع قيمة غير صفرية للجلسة البعيدة؛ وتوفّر WPF نفس الفحص عبر الخاصية SystemParameters.IsRemoteSession. انتبه إلى أنه في الجلسات البعيدة التي تستخدم RemoteFX vGPU، تُبلّغ هذه الدالة خطأً بأن الجلسة البعيدة محلية، والحل الموثّق البديل هو مقارنة قيمة GlassSessionId في السجل (registry) مع معرّف الجلسة الحالي. متغيّر البيئة SESSIONNAME (Console مقابل RDP-Tcp#...) مناسب للتشخيص السريع لكنه ليس واجهة برمجة تطبيقات رسمية. لا تستخدم WTSGetActiveConsoleSessionId لتحديد جلسات RDP — فهي تُرجع فقط الجلسة المتصلة بوحدة التحكم الفعلية.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.
روابط عامة