كيف نفهم عزل الجلسات في Windows ── Session 0 وRDP وتشغيل عدة مستخدمين معاً

· آخر تحديث: · · Session 0, عزل الجلسات, RDP, سطح المكتب البعيد, خدمات Windows, تعدد المستخدمين, Windows, .NET, C#, التصميم, الاستشارات التقنية

سجل التعديلات (3 تحديثات، آخر تحديث 2 Sep، 2026)

سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.

أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
أضفنا علامات اتجاه غير مرئية (RLM) بعد الشرطات الواقعة بين كلمات إنجليزية، حتى يُعرض ترتيب الكلمات بشكل صحيح في التخطيط من اليمين إلى اليسار. نص المقال نفسه لم يتغيّر.
النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621634)

هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.

غو كومورا (2026). كيف نفهم عزل الجلسات في Windows ── Session 0 وRDP وتشغيل عدة مستخدمين معاً. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621634 https://comcomponent.com/ar/blog/windows-session-rdp-multiuser-guide/

DOI (أحدث نسخة)
10.5281/zenodo.21621634
DOI (هذه النسخة)
10.5281/zenodo.22241009

«مربع الحوار الذي عرضته الخدمة لا يراه أحد». «ما إن اتصلنا عبر RDP حتى تحوّلت الشاشة في الموقع إلى شاشة تسجيل الدخول». «كان يفترض أن يُمنع التشغيل المتعدد، لكن تسجيلاً بمستخدم آخر شغّل نسخة ثانية بلا عائق». في استشارات تطبيقات الأعمال، يكمن خلف هذه الأعراض نقص مشترك في فهم ما يسمّيه Windows «جلسة». الجلسة موضوع عرضي يمس تصميم الخدمات، وتشغيل سطح المكتب البعيد، ومنع التشغيل المتعدد بالكائنات المسمّاة، لكن نادراً ما يُشرح بشكل منظّم في موضع واحد.

يجمع هذا المقال، من زاوية عملية، سبب إدخال عزل Session 0، وكيف تتصرف الجلسات عند الاتصال عبر RDP، وكيف تُعزل الكائنات المسمّاة حسب الجلسة، وأخطاء التصميم الشائعة على الأجهزة المشتركة وفي بيئة RDS (Remote Desktop Services).

1. الخلاصة أولاً

الأساس ثلاث نقاط.

  • تعمل الخدمة في جلسة معزولة اسمها Session 0، ولا تستطيع عرض واجهة مباشرة.1
  • RDS مبني على عدة جلسات متزامنة، أما نظام العميل العادي فأساسه جلسة واحدة.2
  • للكائنات المسمّاة في النواة مساحة أسماء لكل جلسة، ويجب الحكم بحسب القصد على الحاجة إلى Global\.3

الخلاصة حسب المسألة، والفصول التي تفصّلها:

المسألة الخلاصة التفصيل
عرض الواجهة من الخدمة بسبب عزل Session 0، مربع الحوار لا يصل إلى المستخدم المسجّل دخوله. تتوقف المعالجة في انتظار زر لا يضغطه أحد1 الفصلان 2 و3
طريقة التجاوز تُفصل واجهة المستخدم إلى عملية أخرى في جلسة المستخدم التفاعلي، ويُتحدَّث معها عبر IPC (الاتصال بين العمليات). التشغيل كمستخدم تفاعلي عبر جدولة المهام خيار آخر4 الفصل 3
عدد الجلسات المتزامنة Windows Server + RDS عدة جلسات معاً. نظام العميل مثل Windows 10/11 Pro جلسة واحدة أساساً. الاستثناء إصدار Enterprise multi-session المخصّص لـ AVD (Azure Virtual Desktop)2 الفصل 4
عزل الكائنات المسمّاة Mutex والأحداث والإشارات وغيرها تملك مساحة أسماء لكل جلسة. إذا أردت واحداً على مستوى الجهاز كله، صرّح بـ Global\3 الفصلان 6 و9
استثناء الأنابيب المسمّاة خارج الآلية أعلاه. تستخدم افتراضياً مساحة أسماء أخرى يمكن الوصول إليها من الجهاز كله (ومن البُعد إذا كانت خدمة الخادم تعمل). للتمييز حسب الجلسة أو المستخدم، ضع معرف الجلسة مثلاً في اسم الأنبوب نفسه5 الفصلان 3 و6
تحديد جلستك يمكن الحكم بمتغير البيئة SESSIONNAME أو GetSystemMetrics(SM_REMOTESESSION)، لكن في حالات مثل استخدام RemoteFX vGPU ينحرف الحكم عن القصد، فلا تبالغ في الاعتماد عليه67 الفصل 5
هل يلزم دعم الجهاز المشترك/RDS أمر ينبغي الاتفاق عليه مع جهة الطلب في مرحلة التصميم المبكرة. تصادم مسارات الملفات المؤقتة، والخلط في AppData، والوصول المتزامن إلى جهاز أو مفتاح، حوادث نموذجية تنفجر في اللحظة التي يُحمَّل فيها تطبيق مصمَّم لجهاز منفرد على RDS الفصلان 7 و8

في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 20، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle

2. ما هو عزل Session 0 ── لماذا فُصلت عن جلسات المستخدمين

قبل Windows Vista / Windows Server 2008، كانت الخدمة وأول مستخدم يسجّل الدخول يشتركان في الجلسة 0 نفسها. العمليات في الجلسة نفسها تستطيع تبادل رسائل النوافذ بغض النظر عن الصلاحيات. عملية بصلاحيات قياسية ترسل رسالة معدّة إلى نافذة خدمة تعمل بصلاحيات SYSTEM، فتختطف معالجة الخدمة وترفع صلاحياتها. تصعيد الصلاحيات الذي يستغل عيباً في معالجة رسائل النوافذ أُبلغ عنه مراراً في العقد الأول من الألفية، وفي الواقع أُصلحت مراراً ثغرات تصعيد صلاحيات تستغل عيباً في معالجة رسائل النوافذ في نواة Windows (win32k.sys).8

منذ Windows Vista تغيّرت هذه الصورة. أصبحت Session 0 مخصّصة للخدمات وللتطبيقات غير المرتبطة بجلسة مستخدم تفاعلية، ويُخصَّص لأول مستخدم يسجّل الدخول الجلسة 1، وللذي يليه الجلسة 2، وهكذا، في جلسات منفصلة عن الخدمة. Session 0 لا تدعم التشغيل التفاعلي، ولا تستطيع الخدمة إرسال رسائل إلى التطبيق، ولا التطبيق إرسالها إلى الخدمة. ولا تستطيع الخدمة عرض عناصر واجهة مثل مربع الحوار مباشرة. بهذا التغيير أُغلق هيكلياً المسار نفسه الذي كانت عملية بصلاحيات قياسية ترسل عبره رسائل مباشرة إلى نافذة الخدمة.1

أي أن عزل Session 0 ليس مجرد قيد تشغيلي من نوع «الخدمة تعمل في الخلفية فلا شاشة لها»، بل تصميم أمني يقطع هيكلياً تبادل رسائل النوافذ عبر حدود الصلاحيات. الوسيلة الوحيدة المتبقية للخدمة لعرض واجهة هي الدالة WTSSendMessage، وهي واجهة تعرض مربع رسالة بسيطاً في جلسة أخرى، لكنها لا تصلح لمربعات حوار معقّدة أو لتبادل ثنائي الاتجاه.1

3. الخدمة لا تستطيع عرض واجهة ── القيود العملية وأنماط التجاوز

النتيجة العملية لعزل Session 0 بسيطة. حتى لو استدعيت MessageBox.Show أو عرض نموذج من داخل الخدمة، لا يظهر شيء على شاشة المستخدم المسجّل دخوله. بل إن ذلك سبب كلاسيكي لحادث تتوقف فيه المعالجة كلها في انتظار زر موافق لا يستطيع أحد ضغطه. عند نقل شيفرة قديمة (مما كان يعمل «خدمة تفاعلية» في زمن Windows XP) إلى خدمة، تحقق دائماً من بقاء استدعاءات عرض الواجهة.

الحلّان الموثَّقان رسمياً اثنان.4

  • فصل تطبيق الواجهة إلى عملية أخرى (في جلسة المستخدم التفاعلي) والتواصل عبر IPC. تُجري الخدمة المعالجة وتمرّر النتائج أو طلبات الإدخال إلى عملية الواجهة. تعرض عملية الواجهة ذلك للمستخدم كأيقونة في منطقة الإشعارات أو كمربع حوار، وتعيد نتيجة العملية إلى الخدمة. عند استخدام IPC مثل الأنابيب المسمّاة (Inter-Process Communication، الاتصال بين العمليات)، يجب تصميم ACL (Access Control List، قائمة التحكم بالوصول التي تعدّد من يستطيع أي عملية على ذلك الكائن) بحيث لا يُكشَف شيء عبر الشبكة دون قصد. في بيئة يسكنها عدة مستخدمين، يُنصح بالتمييز حسب الجلسة، مثلاً بتضمين معرف الجلسة في اسم الأنبوب.4 لاختيار وسيلة IPC، راجع المقال الشقيق المنشور في اليوم نفسه «كيف تختار وسيلة الاتّصال بين عمليّات Windows». طريقة بناء الخدمة نفسها، والجوانب التشغيلية مثل نوع البدء وحساب التشغيل وخيارات الاسترداد، مجموعة في «كيفيّة إنشاء خدمات Windows وتشغيلها».
  • التشغيل كمستخدم تفاعلي عبر جدولة المهام. بدل الإبقاء كخدمة مقيمة، يُشغَّل برنامج ذو واجهة بصلاحيات وجلسة المستخدم المسجّل دخوله. ما يجب الانتباه إليه هنا: لا تجعل «مهمة مسجَّلة تحت SYSTEM» هي من يعرض الواجهة. حساب SYSTEM لا يملك حق تسجيل دخول تفاعلي، ولا يستطيع المستخدم رؤية برنامج أو مهمة تعمل بصلاحيات SYSTEM أو التفاعل معها.9 نموذج المهمة المرتفعة يفترض أصلاً تقسيم أدوار: مهمة 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 نموذجان per device / per user، وتفاصيل نموذج الترخيص تحتاج تأكيداً حسب البيئة.13

أما نظام العميل العادي مثل Windows 10/11 Pro أو Enterprise فلا يملك دور RDS، وهو مبني أساساً على جلسة واحدة. يوجد إصدار خاص لأنظمة العميل اسمه «Windows Enterprise multi-session» يستطيع الاحتفاظ بعدة جلسات تفاعلية في الوقت نفسه، لكنه يُقدَّم حصراً لـ Azure Virtual Desktop، وأنظمة العميل الموزَّعة عادة على الأجهزة المحلية لا تفترض جلسات متزامنة لعدة مستخدمين.2 عملياً، وبما يتوافق مع الإحساس المألوف «اتصلت عبر RDP بجهاز زميل في الشركة فتحوّلت شاشته إلى شاشة القفل»، فمن الآمن أن تفهم أنه في نظام العميل تكوين يجمع في الوقت نفسه بين وحدة التحكم (التشغيل الفيزيائي على الجهاز) والاتصال البعيد لا يقوم في الأساس.

نلخّص الفروق التي ينبغي تثبيتها عند تصميم تطبيق أعمال.

الجانب Windows Server + RDS نظام العميل (Windows 10/11 Pro وغيرها)
الجلسات المتزامنة يستطيع عدة مستخدمين تسجيل الدخول معاً11 جلسة واحدة أساساً. باستثناء إصدار multi-session المخصّص لـ AVD (Azure Virtual Desktop)، الجلسات التفاعلية المتعددة غير مدعومة2
الترخيص يلزم RD Session Host + RDS CAL (عدا اتصالَي الإدارة)12 ميزة Remote Desktop نفسها مضمَّنة في نظام التشغيل، لكن متطلبات الترخيص والإصدارات المسموح بها تحتاج تأكيداً منفصلاً
الاستخدام المتوقَّع تشغيل تطبيقات أعمال على أجهزة مشتركة، بيئة عميل خفيف صيانة عن بُعد لجهاز شخصي، والوصول أثناء العمل من المنزل

إذا تجاهلت هذا الفرق وحكمت بأن «عمل على نظام العميل في جهاز التطوير، إذن سيعمل بالمثل في بيئة RDS على خادم مشترك»، فستفوت أعطال التشغيل المتزامن لعدة مستخدمين المذكورة في الفصل 7. وبالعكس، إذا لم يكن متوقَّعاً سوى تشغيل جلسة واحدة على نظام عميل، فلا حاجة لتحمّل تكلفة تصميم دعم الجهاز المشترك من البداية. هذا الحكم ينبغي اتخاذه مبكراً في تعريف المتطلبات (الفصل 8).

5. تحديد الجلسة الحالية

كثيرة هي المواقف التي تريد فيها معرفة الجلسة التي تعمل فيها عمليتك الآن: كبح تأثير رسم ثقيل في معالجة خلفية، أو تعطيل ميزة تستهلك عرض النطاق في جلسة بعيدة، وما شابه.

قبل كتابة الشيفرة، نفّذ qwinsta على جهازك وانظر كم جلسة قائمة وبأي أرقام، فيتسارع الفهم. مثال المخرجات المنشور في الوثائق الرسمية بهذا الشكل.14

C:\>qwinsta
    SESSIONNAME     USERNAME        ID STATE    TYPE    DEVICE
    console         Administrator1  0 active    wdcon
    >rdp-tcp#1      User1           1 active    wdtshare
    rdp-tcp                         2 listen    wdtshare
                                    4 idle
                                    5 idle

قراءة الأعمدة كالتالي. SESSIONNAME الاسم المعيَّن للجلسة، USERNAME اسم المستخدم المسجّل دخوله في تلك الجلسة، ID معرف الجلسة، STATE الحالة، TYPE نوع الجلسة. DEVICE لا يُعرض لجلسات وحدة التحكم أو الاتصال الشبكي. علامة > في رأس السطر تشير إلى الجلسة التي أنت فيها الآن.14

انتبه إلى أن مثال المخرجات الرسمي هذا من Windows قديم، ومعرف جلسة console فيه 0. منذ Windows Vista حُجزت الجلسة 0 للخدمات، ففي الجهاز الفعلي تصبح وحدة التحكم الفيزيائية 1 فما فوق.1 إذا نفّذت الأمر محلياً وكان console هو 1، فعزل Session 0 سارٍ. سطر rdp-tcp بحالة listen مستمع ينتظر اتصال RDP، وليس جلسة مستخدم مسجّل دخوله.

وسائل التحديد عدة.

  • متغير البيئة SESSIONNAME: في جلسة وحدة التحكم يُضبط على Console، وفي اتصال RDP على اسم يبدأ بـ RDP-Tcp#. هذه المعلومات نفسها المعروضة في عمود SESSIONNAME عند سرد qwinsta.14 تصلح لحكم سريع، لكنها ليست واجهة رسمية، فمن الأسلم ألا تعتمد عليها بإفراط.
  • GetSystemMetrics(SM_REMOTESESSION): واجهة Win32 تحدّد هل أنت في جلسة بعيدة. تعيد قيمة غير صفرية إذا كانت العملية المستدعية مرتبطة بجلسة عميل Terminal Services. لكن هناك قيداً: منذ Windows 8/Server 2012، في جلسة بعيدة تستخدم RemoteFX vGPU، تخطئ هذه الدالة فتعدّ الجلسة البعيدة جلسة محلية. في تلك الحالة يُنصح ببديل يقارن GlassSessionId في السجل بمعرف الجلسة الحالي.67
  • SystemParameters.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. عزل الكائنات المسمّاة حسب الجلسة

من الفصل 2 إلى الفصل 5 تكرّرت كلمة «جلسة» مراراً. نجمع هنا في شكل واحد موضع الجلسات وعلاقتها بمساحات الأسماء. كل ما يلي يقوم على هذا الهيكل.

مساحات أسماء الكائنات المسمّاة حسب الجلسةلكل جلسة مساحة أسماء Local مستقلّة، وخارجها مساحة أسماء عامّة واحدة على مستوى الجهاز.الجلسة 2 ── المستخدم التالي. في بيئة RDS تزداد الجلسات هناالجلسة 1 ── أول مستخدم يسجّل الدخولالجلسة 0 ── مخصّصة للخدمات. المستخدم التفاعلي لا يدخلتطبيق المستخدم Bمساحة أسماء Local ── خاصة بالجلسة 2 فقطتطبيق المستخدم Aمساحة أسماء Local ── خاصة بالجلسة 1 فقطخدمة Windowsعملية مقيمة تعمل تحت SYSTEM وغيرهامساحة أسماء Local ── خاصة بالجلسة 0 فقطمساحة أسماء Global ── واحدة على الجهاز. تراها كل الجلسات

الشكل 1: لكل جلسة مساحة أسماء مستقلّة، وخارجها مساحة أسماء عامّة واحدة على مستوى الجهاز.

إذا أنشأت كائناً مسمّى بلا بادئة، وُضع في مساحة أسماء تلك الجلسة (Local في الشكل). لا يُوضع في مساحة الأسماء المشتركة للجهاز في أسفل الشكل إلا عندما تضيف Global\. عرض «Mutex منع التشغيل المتعدد لم يعمل بعد تسجيل الدخول بمستخدم آخر» ليس سوى استخدام ما أُنشئ في Local وكأنه Global.

الكائنات المسمّاة في النواة مثل Mutex والأحداث والإشارات والمؤقتات القابلة للانتظار وكائنات تعيين الملفات تملك مساحة أسماء مستقلة لكل جلسة. حتى إذا أنشأت في جلسة ما Mutexاً باسم MyAppMutex، فإن محاولة فتح الاسم نفسه من جلسة أخرى تنشئ كائناً جديداً منفصلاً. للعمليات التي تعمل أساساً في الجلسة 0 مثل الخدمات، أو لتكوين عميل/خادم يريد مشاركة كائن عبر عدة جلسات، يمكن وضع الكائن صراحة في مساحة الأسماء العامّة بوضع Global\ في صدر الاسم. وبالعكس، Local\ يضعه صراحة في مساحة أسماء جلستك.3

ما يظهر أثره في الممارسة هو Mutex منع التشغيل المتعدد.

  • إذا كان المطلوب فقط «منع المستخدم نفسه من تشغيل نسختين من التطبيق داخل الجلسة نفسها»، يكفي اسم Mutex الافتراضي (بلا بادئة). مساحة الأسماء لكل جلسة تعمل تلقائياً، فتُعامل جلسة مستخدم آخر كـ Mutex مختلف.
  • إذا أردت «حتى مع تسجيل دخول عدة مستخدمين معاً في بيئة RDS، حصر نسخ التطبيق في واحدة على مستوى الجهاز كله»، فلن تبلغ الهدف دون التصريح بـ Global\. بالافتراضي يستطيع كل مستخدم تشغيل نسخة، فيصير العطل «كان يفترض منع التشغيل المتعدد فإذا بالنسخ متعددة».

لاحظ أيضاً أن العملية خارج session-0 تحتاج امتياز SeCreateGlobalPrivilege لإنشاء كائن تعيين ملف جديد أو كائن رابط رمزي جديد في مساحة الأسماء العامّة (فتح كائن قائم لا يحتاج ذلك). فحص هذا الامتياز يخص تعيين الملفات والروابط الرمزية فقط ولا يُفرَض على إنشاء Mutex أو الأحداث، لكن في تصميم يستخدم ذاكرة مشتركة (ملف معيَّن في الذاكرة) في مساحة الأسماء العامّة ينبغي تذكّره.3

كذلك، كمبدأ عام لتطبيقات العميل/الخادم، يُنصح بعدم معادلة «اتصال من جهاز واحد» بـ «جلسة مستخدم واحدة». مع احتمال أن تُفتح من الجهاز نفسه عدة جلسات معاً (اتصالات RDP لعدة مستخدمين)، يُوصى بأن يميّز جانب الخادم الجلسة بـ ProcessIdToSessionId مثلاً، وأن يجهّز قناة اتصال منفصلة لكل جلسة.18

7. أخطاء تصميم شائعة على الأجهزة المشتركة وفي بيئة RDS

عندما يُحمَّل تطبيق صُمم لافتراض استخدام وحدة التحكم على جهاز منفرد فقط على جهاز مشترك أو بيئة RDS، تنفجر بضعة أنماط أعطال متكررة.

  • تصادم مسارات الملفات المؤقتة. ينشئ RDS افتراضياً مجلداً مؤقتاً منفصلاً لكل جلسة (تحت ملف تعريف المستخدم، ومعرف الجلسة في الاسم).19 أي أن استخدام متغير البيئة %TEMP% كما هو يكفي لنيل فائدة هذا العزل. الحادث يقع عندما لا يستخدم التطبيق %TEMP% ويحدّد مساراً ثابتاً مثل C:\Work\temp. إذا كتبت عدة جلسات للحساب نفسه، أو عدة مستخدمين، في المسار الثابت نفسه في الوقت عينه، فالتنازع على الملف حتمي.
  • الخلط في AppData. مكان حفظ إعدادات المستخدم والذاكرة المؤقتة والسجلات يجب تقريره بعد فهم آلية ملف تعريف المستخدم في Windows (المحلي/التجوّل، والتمييز بين %LOCALAPPDATA% و%APPDATA%). على جهاز مشترك يظهر ضعف هذا التصميم بسهولة في صورة «شاشة تتغيّر بإعدادات شخص آخر» أو «السجل يُكتب فوقه». للتفاصيل راجع «ما هو ملفّ تعريف المستخدم في Windows».
  • افتراض الوصول المتزامن إلى الأجهزة والمفاتيح والمنافذ التسلسلية. الوصول من جلسة RDS إلى جهاز محلي أو منفذ تسلسلي في جانب العميل يمر عبر آلية إعادة التوجيه. إذا حاولت إتاحة تطبيق يستخدم مفتاح USB فيزيائياً أو جهاز اتصال تسلسلي لعدة جلسات في الوقت نفسه، تواجه أن العتاد أصلاً قد لا يدعم الوصول المتزامن، أو أن الجهاز يظهر أو يختفي حسب إعداد إعادة التوجيه. كيف يبدو جهاز مرفق في جانب العميل من جلسة في جانب الخادم يعتمد على تكوين إعادة التوجيه، فتأكيده في مرحلة التصميم لازم.20
  • تثبيت الطابعة ومحرك الأقراص. ترميز اسم الطابعة الافتراضية أو حرف محرك الأقراص داخل التطبيق ينهار بسهولة في بيئة جهاز مشترك تختلف فيها الطابعة الافتراضية المعاد توجيهها أو محرك الأقراص المعيَّن حسب المستخدم. الأسلم الحصول على اسم الطابعة وقت التشغيل، والتعامل مع محركات الأقراص بمسارات UNC قدر الإمكان.
  • سوء قراءة HKCU وسجل المستخدم. انتبه أيضاً إلى أن HKEY_CURRENT_USER مرتبط بالمستخدم المسجّل دخوله لا بالجلسة. في تكوين يملك فيه المستخدم نفسه عدة جلسات (مشاركة الحساب نفسه على RDS مثلاً)، تُشارك قيم HKCU عبر كل الجلسات، فوضع «حالة تشغيل يُراد عزلها حسب الجلسة» في HKCU يؤثر دون قصد في الجلسات الأخرى. المعلومات التي تحتاج عزلاً حسب الجلسة ينبغي التعامل معها بمساحة أسماء الجلسة في الفصل 6، أو بمسار ملف يضم معرف الجلسة.

هذه كلها أعطال من نوع «لا تُلاحظ على جهاز منفرد» و«لا تُكتشف إلا عندما يستخدم التطبيق عدة أشخاص في الوقت نفسه»، فغالباً ما يكون المسار أن بيئة الاختبار جهاز تطوير منفرد فقط، ولا تُعاد المشكلة إلا في جهاز مشترك أو بيئة RDS إنتاجية.

8. جدول قرار ── هل نُدرج دعم «الجهاز المشترك/RDS» في المتطلبات؟

إدراج العمل على جهاز مشترك أو في بيئة RDS ضمن المتطلبات أمر ينبغي تأكيده في مرحلة تعريف المتطلبات، قبل تقدّم التنفيذ. إذا قررت الإدراج، استخدم الجوانب التالية قائمة تحقق.

الجانب يكفي افتراض جهاز منفرد ووحدة تحكم يلزم دعم جهاز مشترك/RDS (عدة مستخدمين معاً)
الملفات المؤقتة والإعدادات المسار الثابت نادراً ما يُحدث ضرراً فعلياً اجعل مسارات per-user/per-session مثل %TEMP% / %LOCALAPPDATA% إلزامية (الفصل 7)
الكائنات المسمّاة الافتراضي (مساحة أسماء الجلسة) يكفي إذا أردت «واحداً على مستوى الجهاز كله» صرّح بـ Global\. إذا كفى «واحد لكل جلسة» فاترك الافتراضي. إذا أردت «جمع جلسات المستخدم نفسه في واحد» فاجمع Global\ مع SID المستخدم (الفصل 6، وراجع أيضاً مقال منع التشغيل المتعدد)
الأجهزة والمفاتيح توصيل محلي مباشر وبسيط صمّم على أساس المرور بإعادة التوجيه واحتمال تعذّر الوصول المتزامن
الواجهة والخدمة يسهل إكمال الأمر على سطح مكتب واحد مع عزل Session 0، كوّن عملية منفصلة + IPC (الفصل 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;

    // 簡易判定。公式APIではないため、重要な分岐には使わずログ・診断情報向けに留める
    public static string? SessionName() =>
        Environment.GetEnvironmentVariable("SESSIONNAME");
}

Mutex منع التشغيل المتعدد يميّز ثلاث حالات ويفصل مساحات الأسماء: «واحد على مستوى الجهاز كله»، و«واحد لكل جلسة»، و«جمع جلسات المستخدم نفسه في واحد». الحالة الثالثة (حسب المستخدم وعبر الجلسات) لا تتحقق بالـ Global\ الافتراضي وحده، ويلزم تضمين SID المستخدم في الاسم؛ هذا مفصّل في «منع التشغيل المتعدّد لتطبيقات Windows».

عند تنفيذ «واحد على مستوى الجهاز كله» بـ Global\، يجب ضبط ACL صراحة. السبب أن Mutex / MutexAcl.Create في .NET تطلب داخلياً، حتى عند مجرد فتح Mutex قائم، SYNCHRONIZE وMUTEX_MODIFY_STATE إضافة إلى DELETE وREAD_CONTROL وWRITE_DAC وWRITE_OWNER. بـ ACL الافتراضي، يحصل مستخدم شرعي غير من أنشأ Mutex أولاً على UnauthorizedAccessException عند استلام createdNew. لتفادي ذلك تُمنَح «المستخدمون المصادَق عليهم» ما يعادل FullControl.

لهذا الضبط ثمنان. الأول أن أي مصادَق عليه يستطيع لاحقاً إعادة كتابة ACL. إذا كان ذلك غير مقبول أيضاً، اترك صنف Mutex واستدعِ CreateMutexEx مباشرة عبر P/Invoke واطلب الحد الأدنى اللازم من حقوق الوصول. الثاني أن هذا ACL لا يُطبَّق إلا إذا نجحت أنت في إنشاء Mutex أولاً. Mutex من نوع Global\ باسم ثابت معرَّض لأن يسبقه مستخدم آخر في الإنشاء (احتلال الاسم)، وفي تلك الحالة يصير createdGlobal إلى false، أو تحصل على UnauthorizedAccessException بحسب ACL الطرف الآخر. إذا أردت الإقصاء حقاً، اجمع اسماً يصعب تخمينه مع آلية إقصاء أخرى (التفاصيل في الفصل 5 من «منع التشغيل المتعدّد لتطبيقات Windows»).

// using System.Security.AccessControl; と using System.Security.Principal; が必要
// (NuGetパッケージ System.Threading.AccessControl も必要)

// 意図: RDS/共有PCで複数ユーザーが同時ログオンしていても、マシン全体でインスタンスは1つ
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);

// 意図: セッションごとに1つ。既定のセッション名前空間のままでよい
using var perSessionMutex = new Mutex(
    initiallyOwned: false,
    name: "KomuraSoft.MyApp.SingleInstance",
    createdNew: out bool createdPerSession);

if (!createdGlobal)
{
    // 他のセッションを含め、マシン上のどこかで既に起動している
    return;
}

إنشاء Mutex ببادئة Global\ نفسه لا يحتاج امتيازاً خاصاً (كما سبق، فحص الامتياز يُفرَض فقط على الإنشاء الجديد لكائنات تعيين الملفات والروابط الرمزية).3 استخدام مساحة أسماء الجلسة الافتراضية بقصد «واحد لكل مستخدم» خطأ. ليس نادراً أن يملك المستخدم نفسه جلسة RDP مقطوعة وجلسة وحدة تحكم/RDP جديدة معاً، فتُعامل كل جلسة كـ Mutex منفصل، وبالافتراضي يستطيع المستخدم نفسه تشغيل النسخة الثانية. أي من «واحد على مستوى الجهاز كله» أو «واحد لكل جلسة» أو «واحد لكل مستخدم (عبر الجلسات)» هو المتطلب الصحيح يعتمد على طبيعة التطبيق، فأكد المتطلب قبل التسمية.

10. الخلاصة

«الجلسة» في Windows مفهوم يظهر مشتركاً في موضوعات تبدو متفرقة: تصميم الخدمات، وتشغيل سطح المكتب البعيد، ومنع التشغيل المتعدد بالكائنات المسمّاة. الأساس الذي ينبغي تثبيته يُختصر في ثلاث نقاط. تعمل الخدمة في جلسة خاصة معزولة اسمها Session 0، ولا تستطيع عرض واجهة مباشرة. RDS مبني على عدة جلسات متزامنة، بينما نظام العميل العادي أساسه جلسة واحدة. وللكائنات المسمّاة مساحة أسماء لكل جلسة، ويجب الحكم بحسب القصد على الحاجة إلى Global\.

إدراج العمل على جهاز مشترك أو في بيئة RDS ضمن المتطلبات موضوع كبير التكلفة إذا اكتُشف في أواخر التنفيذ. نوصي بقوة بأن تؤكد في مرحلة تعريف المتطلبات «من يستخدم هذا التطبيق، وعلى أي جهاز، وكم شخصاً في الوقت نفسه». إذا كان لديك خطة لنشر تطبيق قائم على جهاز مشترك أو بيئة RDS، أو تحتاج إلى التحقيق في عطل من نوع «يتصرف بغرابة عندما يستخدمه عدة مستخدمين»، فغالباً ما يكون أسرع عزل السبب بالنظر إلى التكوين الفعلي؛ لا تتردد في التواصل معنا.

مقالات ذات صلة

مجالات استشارية ذات صلة

تتعامل شركة كومورا سوفت ذ.م.م. مع مراجعة تصميم تطبيقات Windows تمهيداً للنشر على أجهزة مشتركة أو بيئة RDS، وتصميم التحويل إلى خدمة وفصل IPC، والتحقيق في أسباب الأعطال الخاصة بالاستخدام المتزامن لعدة مستخدمين.

روابط مرجعية

  1. Microsoft Learn, Service Changes for Windows Vista. حول خلفية إدخال عزل Session 0، وتعذّر إرسال رسائل النوافذ واستقبالها بين الخدمة والتطبيق، والبديل عبر WTSSendMessage. ↩ ↩2 ↩3 ↩4 ↩5

  2. Microsoft Learn, Windows Enterprise multi-session FAQ. حول أن ميزة multi-session التي تسمح بعدة جلسات تفاعلية معاً كانت ممكنة تقليدياً على Windows Server فقط، وأنها تُقدَّم حصراً لـ Azure Virtual Desktop. ↩ ↩2 ↩3 ↩4

  3. Microsoft Learn, Kernel object namespaces. حول مساحة الأسماء لكل جلسة للكائنات المسمّاة في النواة، وبادئتَي Global\ / Local\، والحاجة إلى SeCreateGlobalPrivilege عند الإنشاء الجديد لكائن تعيين ملف أو رابط رمزي في مساحة الأسماء العامّة. ↩ ↩2 ↩3 ↩4 ↩5

  4. Microsoft Learn, Interactive Services. حول تعذّر حوار الخدمة مع المستخدم مباشرة، وتصميم تشغيل تطبيق GUI في عملية أخرى بـ CreateProcessAsUser والتنسيق عبر IPC، والإرشاد إلى تمييز اسم الأنبوب بمعرف الجلسة. ↩ ↩2 ↩3

  5. Microsoft Learn, Named Pipes. حول أن الأنبوب المسمّى يمكن الوصول إليه من أي عملية ضمن نطاق فحوصات الأمان، وأنه يمكن بلوغه من البُعد إذا كانت خدمة الخادم تعمل (أساس أنه آلية مختلفة عن مساحات أسماء الجلسة Global\/Local\ الخاصة بـ Mutex وغيرها). ↩

  6. Microsoft Learn, GetSystemMetrics function (winuser.h). حول مواصفات تحديد الجلسة البعيدة عبر SM_REMOTESESSION. ↩ ↩2

  7. Microsoft Learn, Detecting the Remote Desktop Services environment. حول شيفرة عيّنة لـ GetSystemMetrics(SM_REMOTESESSION)، وقيد الحكم الخاطئ على الجلسة البعيدة كجلسة محلية عند استخدام RemoteFX vGPU، والحكم البديل بمفتاح السجل GlassSessionId. ↩ ↩2

  8. Microsoft Security Bulletin, MS12-034 - Windows and Messages Vulnerability (CVE-2012-0180). حول ثغرة تصعيد صلاحيات تستغل عيباً في معالجة رسائل النوافذ في برنامج تشغيل وضع النواة في Windows (win32k.sys) (مثال على فئة الهجوم عبر رسائل النوافذ). ↩

  9. Microsoft Learn, schtasks create. حول أن حساب SYSTEM لا يملك حق تسجيل دخول تفاعلي، وأن المستخدم لا يستطيع رؤية برنامج أو مهمة تعمل بصلاحيات SYSTEM أو التفاعل معها. ↩

  10. Microsoft Learn, Elevated Task Model. حول تكوين ينفّذ فيه تطبيق مستخدم قياسي معالجة تحتاج صلاحيات مدير عبر مهمة مسجَّلة كـ SYSTEM ومهيَّأ واصف أمانها بحيث يستطيع المستخدم القياسي تشغيلها. ↩

  11. Microsoft Learn, Remote Desktop Services overview in Windows Server. حول أن RDS أساس يوفّر أسطح مكتب قائمة على الجلسات من النوع متعدد الجلسات. ↩ ↩2

  12. Microsoft Learn, Troubleshoot Remote desktop disconnected errors. حول أن غرض الإدارة يسمح حتى اتصالين بعيدين متزامنين بلا RDS CAL، وأن ما يتجاوز ذلك يحتاج دور RD Session Host وRDS CAL المناسب. ↩ ↩2

  13. Microsoft Learn, License Remote Desktop Services with Client Access Licenses (CALs). حول الفرق بين نموذجي RDS CAL لكل جهاز ولكل مستخدم، وآلية الإصدار والتتبّع عبر خادم الترخيص. ↩ ↩2

  14. Microsoft Learn, qwinsta. حول أن عمود SESSIONNAME يبيّن الاسم المعيَّن للجلسة (console لوحدة التحكم، أو اسم يبدأ بـ rdp-tcp# لاتصال RDP)، ومثال المخرجات المقتبس في المتن ومعاني الأعمدة (SESSIONNAME وUSERNAME وSTATE وTYPE وDEVICE)، وظهور > في رأس سطر الجلسة الحالية. ↩ ↩2 ↩3

  15. Microsoft Learn, SystemParameters.IsRemoteSession Property. حول الخاصية التي تتيح من WPF الحصول على حكم يعادل SM_REMOTESESSION. ↩

  16. Microsoft Learn, WTSGetActiveConsoleSessionId function (winbase.h). حول الحصول على معرف الجلسة المتصلة بوحدة التحكم الفيزيائية، وإعادة 0xFFFFFFFF أثناء الانتقال. ↩ ↩2

  17. Microsoft Learn, WTSEnumerateSessions function (wtsapi32.h). حول إمكانية تعداد كل الجلسات على خادم RD Session Host. ↩

  18. Microsoft Learn, Client/Server application guidelines. حول وجوب عدم معادلة «اتصال من جهاز واحد» بـ «جلسة مستخدم واحدة»، وتحديد الجلسة عبر ProcessIdToSessionId. ↩

  19. Microsoft Learn, Policy CSP - ADMX_TerminalServer (TS_TEMP_PER_SESSION). حول أن Remote Desktop Services ينشئ افتراضياً مجلداً مؤقتاً منفصلاً لكل جلسة (تحت ملف تعريف المستخدم، ومعرف الجلسة في الاسم). ↩

  20. Microsoft Learn, Peripheral hardware guidelines. حول أن محركات الأقراص والطابعات في جانب العميل تتغيّر رؤيتها من خادم RD Session Host حسب تكوين إعادة التوجيه، وحول آلية تعطيل إعادة التوجيه من جانب برنامج تشغيل الجهاز. ↩

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

الأسئلة الشائعة

أسئلة شائعة حول موضوع هذه المقالة.

لماذا لا تستطيع خدمة Windows عرض مربع حوار؟
السبب هو عزل Session 0 الذي ظهر منذ Windows Vista. تعمل الخدمة في جلسة مخصّصة اسمها Session 0، وهي مفصولة عن الجلسة التفاعلية للمستخدم المسجّل دخوله. هذا تصميم أمني يقطع هيكلياً مسار تصعيد صلاحيات يرسل فيه عملية بصلاحيات قياسية رسالة معدّة إلى نافذة خدمة تعمل بصلاحيات SYSTEM. مربع الحوار الذي تعرضه الخدمة لا يراه المستخدم، فتتوقف المعالجة في انتظار زر موافق لا يستطيع أحد ضغطه. الحل هو فصل تطبيق الواجهة إلى عملية أخرى في جلسة المستخدم والتواصل معها عبر IPC.
لماذا تتحوّل الشاشة المحلية إلى شاشة القفل عند الاتصال عبر RDP؟
لأن أنظمة العميل العادية مثل Windows 10/11 Pro مبنية أساساً على جلسة واحدة. تكوين يجمع في الوقت نفسه بين وحدة التحكم (التشغيل الفيزيائي على الجهاز) والاتصال البعيد لا يقوم في الأساس. يستطيع عدة مستخدمين تسجيل الدخول معاً والحصول على جلسات مستقلة في تكوين Windows Server مع دور RDS (Remote Desktop Services). لتجاوز اتصالَي الإدارة يلزم دور RD Session Host وتراخيص RDS CAL. الاستثناء هو إصدار Enterprise multi-session المخصّص لـ Azure Virtual Desktop.
لماذا لا يعمل Mutex منع التشغيل المتعدد في بيئة RDS؟
لأن الكائنات المسمّاة في النواة (Mutex والأحداث والإشارات وغيرها) تملك مساحة أسماء مستقلة لكل جلسة. اسم Mutex بلا بادئة يُعامل في جلسة مستخدم آخر كـ Mutex مختلف، فيستطيع كل مستخدم تشغيل نسخة. إذا أردت نسخة واحدة على مستوى الجهاز كله، يجب التصريح ببادئة Global\. وإذا أردت «جمع جلسات المستخدم نفسه في نسخة واحدة»، أضف SID المستخدم إلى الاسم مع Global\.
ما الأسباب النموذجية لتعطل تطبيق أعمال على جهاز مشترك أو في بيئة RDS؟
هناك خمسة أنماط متكررة: تصادم الملفات المؤقتة بسبب مسار ثابت بدل %TEMP%، وضعف التمييز في ملف تعريف المستخدم (AppData)، وافتراض الوصول المتزامن إلى مفتاح USB أو منفذ تسلسلي، وترميز اسم الطابعة وحرف محرك الأقراص ترميزاً ثابتاً، ووضع حالة يُراد عزلها حسب الجلسة في HKCU. لا يظهر أي منها على جهاز منفرد، ولا يُكتشف إلا عندما يستخدم التطبيق عدة أشخاص في الوقت نفسه. لذلك ينبغي تأكيد الحاجة إلى دعم الجهاز المشترك وRDS مبكراً في تعريف المتطلبات.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.

العودة إلى المدونة