Как устроена изоляция сеансов Windows — Session 0, RDP и одновременная работа нескольких пользователей

· Обновлено: · · Session 0, изоляция сеансов, RDP, удалённый рабочий стол, службы Windows, многопользовательский режим, Windows, .NET, C#, проектирование, технический консалтинг

История изменений (1 обновлений, последнее 30 Aug 2026)

Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.

Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
Первая публикация
Цитирование статьи(DOI (зарегистрированный архив): 10.5281/zenodo.21619972)

Приведённые ниже DOI относятся к ранее зарегистрированным архивным версиям, которые могут отличаться от текущего текста. Для ссылки на текущий текст используйте URL этой страницы.

Го Комура (2026). Как устроена изоляция сеансов Windows — Session 0, RDP и одновременная работа нескольких пользователей. KomuraSoft LLC. https://comcomponent.com/ru/blog/windows-session-rdp-multiuser-guide/

DOI (зарегистрированный архив)
10.5281/zenodo.21619972
DOI (последняя зарегистрированная версия)
10.5281/zenodo.21619973

«Диалог, который показала служба, никто не видит». «Как только подключились по RDP, локальный экран стал экраном входа». «Повторный запуск вроде запрещён, а после входа под другим пользователем спокойно поднялась вторая копия». За такими симптомами в консультациях по бизнес-приложениям почти всегда стоит одно и то же: слабо разобрано, что в Windows называется сеансом. Сеанс проходит через проектирование служб, эксплуатацию удалённого рабочего стола и защиту от повторного запуска через именованные объекты — тема сквозная, но её редко объясняют целиком.

Ниже с практической стороны собрано, зачем ввели изоляцию Session 0, как ведут себя сеансы при подключении по RDP, как именованные объекты разделяются по сеансам и какие ошибки проектирования типичны на общих ПК и в среде RDS (Remote Desktop Services).

1. Сначала выводы

Опора — три пункта.

  • Служба работает в изолированном сеансе Session 0 и не может показать UI напрямую.1
  • RDS рассчитан на несколько одновременных сеансов, а обычная клиентская ОС — в основном на один.2
  • У именованных объектов ядра есть пространство имён на каждый сеанс; нужен ли префикс Global\, зависит от замысла.3

Выводы по отдельным вопросам и главы, где они разобраны подробнее:

Вопрос Вывод Где подробно
UI из службы Из-за изоляции Session 0 диалог до вошедшего пользователя не доходит. Обработка зависает в ожидании кнопки, которую никто не нажмёт1 главы 2 и 3
Как обойти UI выносят в отдельный процесс в сеансе интерактивного пользователя и общаются через IPC (межпроцессное взаимодействие). Запуск от имени интерактивного пользователя через Планировщик заданий — ещё один вариант4 глава 3
Число одновременных сеансов Windows Server + RDS — несколько сразу. Клиентская ОС вроде Windows 10/11 Pro — в основном один. Исключение — издание Enterprise multi-session только для AVD (Azure Virtual Desktop)2 глава 4
Изоляция именованных объектов Mutex, событие, семафор и т. п. имеют пространство имён на каждый сеанс. Чтобы оставить один объект на всю машину, явно указывают Global\3 главы 6 и 9
Исключение: именованные каналы На механизм выше не распространяется. По умолчанию используется другое пространство имён, доступное с всей машины (и удалённо, если запущена служба сервера). Чтобы различать по сеансу или пользователю, в само имя канала кладут, например, ID сеанса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 служба и первый вошедший пользователь делили один и тот же Session 0. Процессы одного сеанса могут обмениваться оконными сообщениями независимо от уровня привилегий. Процесс со стандартными правами отправлял поддельное сообщение в окно службы, работающей как SYSTEM, перехватывал обработку на стороне службы и повышал привилегии — этот класс атак через дефекты обработки оконных сообщений неоднократно фиксировали в 2000-х, и уязвимости повышения привилегий, бьющие в обработку оконных сообщений в ядре Windows (win32k.sys), правили не раз.8

Начиная с Windows Vista схема другая. Session 0 отведён службам и приложениям, не привязанным к интерактивному пользовательскому сеансу, а первому вошедшему пользователю дают сеанс 1, следующему — сеанс 2 и так далее, отдельно от службы. Session 0 не поддерживает интерактивную работу, и служба не может слать сообщения приложению, приложение — службе. Служба также не может напрямую показать элементы UI вроде диалогового окна. Сам путь, по которому процесс со стандартными правами слал сообщения прямо в окно службы, тем самым закрыт на уровне устройства.1

Изоляция Session 0 — не эксплуатационное ограничение вида «служба работает в фоне, поэтому экрана нет», а решение по безопасности: перекрыть обмен оконными сообщениями через границу привилегий. Единственный оставшийся у службы способ хоть как-то показать UI — функция WTSSendMessage: API выводит простое окно сообщения в другом сеансе, но для сложных диалогов и двустороннего обмена оно не годится.1

3. Служба не может показать UI — практические ограничения и способы обхода

Практическое следствие изоляции Session 0 простое. Вызов MessageBox.Show или показ формы внутри службы ничего не рисует на экране вошедшего пользователя. Хуже того, это классическая причина зависания всей обработки в ожидании кнопки OK, которую никто не нажмёт. Когда старый код (то, что во времена Windows XP работало как «интерактивная служба») переносят в службу, обязательно проверьте, не остались ли вызовы показа UI.

Официально описаны два решения.4

  • Вынести UI-приложение в отдельный процесс (в сеансе интерактивного пользователя) и общаться через IPC. Служба запускает работу и передаёт результат или запрос ввода процессу UI. Процесс UI показывает это пользователю значком в области уведомлений или диалогом и возвращает службе итог операции. Если берёте IPC вроде именованного канала (Inter-Process Communication, межпроцессное взаимодействие), ACL (Access Control List, список управления доступом: кто какие операции может выполнять над объектом) нужно спроектировать так, чтобы канал не оказался доступен по сети. В среде, где одновременно работают несколько пользователей, рекомендуют различать сеансы, например включая ID сеанса в имя канала.4 Как выбирать механизм IPC, см. в сестринской статье того же дня «Таблица выбора межпроцессного взаимодействия». Как строить саму службу и как проектировать эксплуатацию — тип запуска, учётную запись выполнения, параметры восстановления — собрано в «Как создавать службы Windows».
  • Запускать от имени интерактивного пользователя через Планировщик заданий. Вместо постоянной службы запускают программу с UI под правами и в сеансе вошедшего пользователя. Здесь важно: задачу, зарегистрированную как SYSTEM, нельзя делать стороной, которая показывает UI. У учётной записи SYSTEM нет права интерактивного входа, и пользователь не видит и не может оперировать программой или задачей, которая выполняется с правами SYSTEM.9 Elevated Task Model исходит из разделения ролей: задаче SYSTEM с дескриптором безопасности, который позволяет запускать её обычному пользователю, отдают только ту обработку, которой нужны права администратора, а сам UI держит другая программа под правами и в сеансе вошедшего пользователя (либо та же обработка, зарегистрированная как задача учётной записи пользователя).10 Задачу SYSTEM держите брокером без видимого UI; видимый интерфейс всегда должен жить в процессе, который работает в сеансе самого пользователя.

В обоих вариантах главное — с самого начала заложить в проект правило «служба — фон, UI — отдельный процесс». Если пытаться добавить это потом, в службе уже сидит код, завязанный на UI, и стоимость разделения резко растёт — типичная картина на практике.

4. Поведение сеансов при подключении по RDP — чем RDS отличается от клиентской ОС

Путаница «на сервере и на клиенте один и тот же RDP, а поведение разное» идёт от того, есть ли роль Remote Desktop Services (RDS).

Windows Server + роль RDS рассчитан на то, что на один сервер одновременно входят несколько пользователей, каждый в своём сеансе со своим рабочим столом и приложениями — то есть на несколько одновременных сеансов.11 В RDS обычные административные подключения по RDP дают до двух сеансов без CAL, но больше этого числа или одновременная работа обычных пользователей требуют роли RD Session Host и подходящих лицензий RDS CAL (Client Access License, лицензия клиентского доступа).12 У CAL две модели — per device и per user; детали лицензирования нужно смотреть по среде.13

Обычная клиентская ОС вроде Windows 10/11 Pro или Enterprise роли RDS не несёт и в основном рассчитана на один сеанс. Есть особое издание клиентской ОС «Windows Enterprise multi-session», которое умеет держать несколько интерактивных сеансов сразу, но его дают только для Azure Virtual Desktop; у обычной клиентской ОС, которую распространяют on-premises, несколько одновременных пользовательских сеансов не предполагаются.2 На практике это совпадает с ощущением «подключился по RDP к ПК коллеги — локальный экран стал экраном блокировки»: на клиентской ОС консоль (физическая работа за машиной) и удалённое подключение в основном не сосуществуют.

Различия, которые нужно держать в уме при проектировании бизнес-приложения:

Аспект Windows Server + RDS Клиентская ОС (Windows 10/11 Pro и т. п.)
Одновременные сеансы Несколько пользователей могут войти одновременно11 В основном один сеанс. Несколько интерактивных сеансов не поддерживаются, кроме издания multi-session только для AVD2
Лицензии Нужны 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 Для грубой проверки годится, но это не официальный API, лучше не опираться на неё слишком сильно.
  • GetSystemMetrics(SM_REMOTESESSION): Win32 API, который сообщает, удалённый ли это сеанс. Если вызывающий процесс привязан к клиентскому сеансу Terminal Services, возвращает ненулевое значение. Ограничение: начиная с Windows 8 / Server 2012, в удалённом сеансе с RemoteFX vGPU эта функция принимает удалённый сеанс за локальный. Тогда рекомендуют сравнивать GlassSessionId в реестре с текущим ID сеанса.67
  • SystemParameters.IsRemoteSession в .NET (WPF): ту же проверку, что GetSystemMetrics(SM_REMOTESESSION), WPF-приложение получает свойством.15 Из WinForms или консольного приложения вызывают напрямую через P/Invoke.
  • WTSGetActiveConsoleSessionId: API, который возвращает ID сеанса, подключённого к физической консоли. Сравнение со своим ID сеанса отвечает на вопрос «работаю ли я на физической консоли». Если к физической консоли никто не подключён (переходное состояние), возвращается 0xFFFFFFFF.16

WTSGetActiveConsoleSessionId нельзя использовать, чтобы находить сеансы в среде RDS. Как видно из имени, возвращается только «сеанс, подключённый к физической консоли»; сеансы пользователей, вошедших по RDP, сюда не входят.16 Если службе нужно знать, в какой сеанс вывести companion UI интерактивного пользователя, и вы рассчитываете только на пользователя за консолью, этого API достаточно. В среде RDS, где одновременно входят несколько пользователей по RDP, нужно перечислять живые сеансы через WTSEnumerateSessions17 либо брать ID сеанса из уведомления об изменении сеанса (WM_WTSSESSION_CHANGE). Эта путаница — типичная причина трудноуловимых сбоев: UI уведомления не появляется у пользователя по RDP или появляется не в том сеансе. Когда в схеме IPC из главы 3 решаете, в каком сеансе запускать процесс UI, это различие нужно держать в уме.

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, созданный в одном сеансе, при открытии под тем же именем из другого сеанса создаётся заново как другой объект. Процессам, которые в основном работают в Session 0, например службам, или клиент-серверной схеме, которой нужно делить объект между несколькими сеансами, можно явно положить объект в глобальное пространство имён, поставив Global\ в начало имени. Наоборот, Local\ явно кладёт объект в пространство имён своего сеанса.3

На практике это бьёт в Mutex для защиты от повторного запуска.

  • Если нужно только «не дать одному пользователю запустить две копии приложения в одном сеансе», хватает имени Mutex по умолчанию (без префикса). Пространство имён сеанса срабатывает само, и в сеансе другого пользователя это другой Mutex.
  • Если нужно «даже когда в RDS одновременно входят несколько пользователей, оставить один экземпляр приложения на всю машину», без явного Global\ цели не достичь. По умолчанию у каждого пользователя поднимается своя копия — сбой вида «повторный запуск вроде запрещён, а копий несколько».

Отдельно: процессу не из session 0, чтобы заново создать объект проекции файла или объект символьной ссылки в глобальном пространстве имён, нужна привилегия SeCreateGlobalPrivilege (чтобы только открыть существующий объект, она не нужна). Эта проверка касается только проекции файла и символьной ссылки и на создание Mutex или события не распространяется, но в схеме с разделяемой памятью (файл, отображённый в память) в глобальном пространстве имён её стоит помнить.3

Ещё общий принцип для клиент-серверных приложений: не отождествлять «подключение с одной машины» с «одним пользовательским сеансом». С одной машины могут одновременно идти несколько сеансов (RDP нескольких пользователей), поэтому серверной стороне рекомендуют опознавать сеанс через ProcessIdToSessionId и готовить отдельный канал связи на каждый сеанс.18

7. Типичные ошибки проектирования на общих ПК и в среде RDS

У приложения, которое писали только под консоль одного ПК, на общем ПК или в RDS сразу всплывает несколько повторяющихся схем сбоев.

  • Столкновение путей временных файлов. RDS по умолчанию создаёт отдельную временную папку на каждый сеанс (в профиле пользователя, с ID сеанса в имени).19 Если просто брать переменную окружения %TEMP%, эта изоляция достаётся даром. Сбой начинается, когда приложение вместо %TEMP% жёстко прописывает путь вроде C:\Work\temp. Несколько сеансов одной учётной записи или несколько пользователей, одновременно пишущие в один фиксированный путь, неизбежно дерутся за файл.
  • Путаница с AppData. Куда класть настройки, кэш и журналы, решают, понимая устройство профилей Windows (локальный / roaming, когда брать %LOCALAPPDATA%, а когда %APPDATA%). На общем ПК слабость этой схемы проявляется как «экран изменился из-за чужих настроек» или «журнал перезаписали». Подробнее — в «Введение в профили пользователей Windows».
  • Расчёт на одновременный доступ к устройству, донглу или последовательному порту. Доступ из сеанса RDS к локальному устройству или последовательному порту на стороне клиента идёт через перенаправление. Если приложение с физическим USB-донгла или устройством последовательной связи пытаются одновременно использовать из нескольких сеансов, упираются в то, что железо само по себе не умеет одновременный доступ, либо в то, что устройство то видно, то нет — в зависимости от настройки перенаправления. Как устройство, подключённое на клиенте, видно из сеанса на сервере, зависит от конфигурации перенаправления, это нужно проверить ещё на проектировании.20
  • Жёстко заданные принтер и диск. Имя принтера по умолчанию или буква диска, зашитые в приложение, на общем ПК, где у каждого пользователя свой перенаправленный принтер по умолчанию и свой подключенный диск, быстро ломаются. Имя принтера берут во время выполнения, а к дискам по возможности ходят по UNC-пути.
  • Неверное чтение HKCU и пользовательского реестра. HKEY_CURRENT_USER привязан не к сеансу, а к вошедшему пользователю. Если у одного пользователя несколько сеансов (одна учётная запись на нескольких сеансах RDS и т. п.), значения HKCU общие для всех этих сеансов, и состояние времени выполнения, которое хотели изолировать по сеансам, непреднамеренно влияет на другие сеансы. Информацию, которую нужно разделять по сеансам, кладут в пространство имён сеанса из главы 6 или в путь файла с ID сеанса.

Всё это сбои, которые «на одном ПК не заметишь» и которые «всплывают, только когда приложением одновременно пользуются несколько человек». Если тестовая среда — только отдельная машина разработки, типичный сценарий: впервые воспроизводится уже на общем ПК или в RDS в рабочей среде.

8. Таблица решений — включать ли поддержку общего ПК и RDS в требования

Входит ли поведение на общем ПК и в RDS в требования, нужно выяснить на определении требований, до того как уйдёт реализация. Если решили включать, используйте как чеклист следующие пункты.

Аспект Достаточно одного ПК и консоли Нужна поддержка общего ПК / RDS (несколько пользователей одновременно)
Временные файлы и настройки Фиксированный путь редко даёт реальный вред Обязательны пути per-user / per-session вроде %TEMP% / %LOCALAPPDATA% (глава 7)
Именованные объекты Можно оставить по умолчанию (пространство имён сеанса) «Один на всю машину» — явно указать Global\. «Один на сеанс» — оставить по умолчанию. «Несколько сеансов одного пользователя свести к одному» — сочетать Global\ с SID пользователя (глава 6, см. также статью о защите от повторного запуска)
Устройства и донглы Простое прямое локальное подключение Проектировать из расчёта, что доступ идёт через перенаправление и одновременный доступ может быть невозможен
UI и служба Легко удержать всё на одном рабочем столе С учётом изоляции Session 0 собирать как отдельный процесс + IPC (глава 3)
Лицензии и оплата Можно просто считать по числу машин На определении требований уточнить, как считается число одновременных сеансов и нужны ли RDS CAL13

Ось решения простая. Ещё на ранней стадии разработки согласовать с заказчиком: «этому приложению достаточно работать на ПК одного человека» или «его могут одновременно использовать несколько человек на общем терминале или в 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. Чтобы этого избежать, «прошедшим проверку» (Authenticated Users) дают права уровня FullControl.

У этой настройки два побочных эффекта. Первый: любой прошедший проверку сможет потом переписать ACL. Если и это неприемлемо, класс Mutex не используют и через P/Invoke вызывают CreateMutexEx напрямую, запрашивая только минимально нужные права. Второй: этот ACL применяется, только если Mutex первым создали вы. У Global\ Mutex с фиксированным именем есть риск, что другой пользователь создаст его раньше (захват имени); тогда createdGlobal будет false либо, в зависимости от чужого ACL, придёт UnauthorizedAccessException. Если остальных нужно отсечь по-настоящему, сочетайте трудноугадываемое имя или другой механизм исключения (подробнее — глава 5 в «Защита Windows-приложения от повторного запуска»).

// нужны using System.Security.AccessControl; и using System.Security.Principal;
// (также NuGet-пакет System.Threading.AccessControl)

// Замысел: даже когда в RDS / на общем ПК одновременно входят несколько пользователей,
// на всю машину остаётся один экземпляр
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\ особых привилегий не требует (как сказано выше, проверка привилегии касается только создания новых объектов проекции файла и символьной ссылки).3 Оставлять пространство имён сеанса по умолчанию, задумав «один на пользователя», — ошибка. Нередко у одного пользователя одновременно есть отключённый сеанс RDP и новый консольный или RDP-сеанс; тогда каждый сеанс даёт свой Mutex, и по умолчанию тот же пользователь может запустить вторую копию. Какой вариант верен как требование — «один на всю машину», «один на сеанс» или «один на пользователя (через сеансы)» — зависит от характера приложения, поэтому до выбора имени сначала уточните требование.

10. Заключение

«Сеанс» Windows — понятие, которое всплывает в темах, на вид не связанных: проектирование служб, эксплуатация удалённого рабочего стола, защита от повторного запуска через именованные объекты. Опора сводится к трём пунктам. Служба работает в особом изолированном сеансе Session 0 и не может показать UI напрямую. RDS рассчитан на несколько одновременных сеансов, а обычная клиентская ОС — в основном на один. И у именованных объектов есть пространство имён на каждый сеанс, поэтому нужен ли Global\, решают исходя из замысла.

Включать ли поведение на общем ПК и в RDS в требования — тема, которую поздно замечать во второй половине реализации: переделок будет много. На определении требований стоит явно выяснить, кто, на каких машинах и сколько человек одновременно будут пользоваться приложением. Если планируете вынести существующее приложение на общий ПК или в RDS либо нужно найти причину сбоя вида «при нескольких пользователях ведёт себя странно», чаще быстрее разобрать это на реальной конфигурации — обращайтесь.

Связанные статьи

Смежные направления консультаций

KomuraSoft (合同会社小村ソフト) занимается ревью проектирования 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. О том, что несколько интерактивных сеансов одновременно раньше были возможны только на 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, и о рекомендации различать имена каналов по ID сеанса. ↩ ↩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 per device / per user и о выдаче и учёте лицензий сервером лицензирования. ↩ ↩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). О получении ID сеанса, подключённого к физической консоли, и о возврате 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 по умолчанию создаёт отдельную временную папку на каждый сеанс (в профиле пользователя, с ID сеанса в имени). ↩

  20. Microsoft Learn, Peripheral hardware guidelines. О том, что диски и принтеры на стороне клиента с сервера RD Session Host видны по-разному в зависимости от конфигурации перенаправления, и о механизме, которым драйвер устройства отключает перенаправление. ↩

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Почему служба Windows не может показать диалоговое окно?
Из-за изоляции Session 0, появившейся в Windows Vista. Служба работает в выделенном Session 0 и отделена от интерактивного сеанса вошедшего пользователя. Так перекрывают атаки с повышением привилегий, когда процесс со стандартными правами отправляет поддельные сообщения в окно службы, работающей как SYSTEM. Диалог, показанный из службы, пользователь не видит, и обработка зависает в ожидании кнопки OK, которую никто не может нажать. Решение — вынести UI в отдельный процесс в сеансе пользователя и общаться с ним через IPC.
Почему при подключении по RDP локальный экран переключается на экран блокировки?
Потому что обычная клиентская ОС вроде Windows 10/11 Pro рассчитана на один сеанс. Консоль (физическая работа за машиной) и удалённое подключение в основном не сосуществуют. Несколько пользователей могут войти одновременно и получить независимые сеансы в связке Windows Server + роль RDS (Remote Desktop Services); сверх двух административных подключений нужны роль RD Session Host и лицензии RDS CAL. Исключение — издание Enterprise multi-session только для Azure Virtual Desktop.
Почему в среде RDS не срабатывает Mutex, который должен не давать запустить вторую копию?
У именованных объектов ядра (Mutex, событие, семафор и т. д.) на каждый сеанс своё пространство имён. Имя Mutex без префикса в сеансе другого пользователя даёт другой Mutex, и у каждого пользователя поднимается своя копия. Чтобы оставить один экземпляр на всю машину, нужно явно указать префикс Global\. Если нужно «свести несколько сеансов одного пользователя к одному экземпляру», к Global\ добавляют SID пользователя в имя.
Из-за чего бизнес-приложение обычно ломается на общем ПК и в среде RDS?
Пять типичных схем: столкновение временных файлов из-за жёстко заданного пути вместо %TEMP%; неаккуратная работа с профилем пользователя (AppData); расчёт на одновременный доступ к USB-донглу или последовательному порту; зашитые имя принтера и буква диска; состояние, которое должно быть своим у каждого сеанса, кладут в HKCU. На отдельном ПК это не заметно и всплывает, только когда приложением одновременно пользуются несколько человек, поэтому, нужна ли поддержка общего ПК и RDS, стоит решить ещё на определении требований.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог