Как устроена изоляция сеансов 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 сеанса.67SystemParameters.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 слово «сеанс» звучало много раз. Здесь одной схемой собраны размещение сеансов и пространства имён. Всё дальнейшее стоит на этой структуре.
flowchart TB
accTitle: Пространства имён именованных объектов по сеансам
accDescr: У каждого сеанса своё пространство имён Local, а снаружи — одно глобальное пространство имён на всю машину.
subgraph S0["Сеанс 0 — только службы. Интерактивный пользователь сюда не попадает"]
SVC["служба Windows<br/>постоянно работающий процесс под SYSTEM и т. п."]
L0["пространство имён Local — только сеанса 0"]
SVC --- L0
end
subgraph S1["Сеанс 1 — первый вошедший пользователь"]
APP1["приложение пользователя A"]
L1["пространство имён Local — только сеанса 1"]
APP1 --- L1
end
subgraph S2["Сеанс 2 — следующий вошедший пользователь. В среде RDS таких сеансов становится больше"]
APP2["приложение пользователя B"]
L2["пространство имён Local — только сеанса 2"]
APP2 --- L2
end
G["пространство имён Global — одно на машину. Его видят все сеансы"]
SVC --> G
APP1 --> G
APP2 --> G
Рис. 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 либо нужно найти причину сбоя вида «при нескольких пользователях ведёт себя странно», чаще быстрее разобрать это на реальной конфигурации — обращайтесь.
Связанные статьи
- Как создавать и эксплуатировать службы Windows — от выбора между Планировщиком заданий до службы на BackgroundService
- Как выбирать межпроцессное взаимодействие в Windows — именованный канал / TCP / gRPC / разделяемая память / COM, таблица решений
- Защита Windows-приложения от повторного запуска — именованный Mutex и активация уже запущенного экземпляра
- Введение в профили пользователей Windows — AppData и NTUSER.DAT
- Как на практике отделить в Windows-приложении «только ту обработку, которой нужны права администратора»
Смежные направления консультаций
KomuraSoft (合同会社小村ソフト) занимается ревью проектирования Windows-приложений с расчётом на развёртывание на общих ПК и в среде RDS, проектированием выноса в службу и разделения через IPC, а также поиском причин сбоев, характерных для одновременной работы нескольких пользователей.
- Технический консалтинг и ревью проектирования
- Разработка приложений для Windows
- Модернизация и сопровождение существующего ПО для Windows
- Контакты
Справочные ссылки
-
Microsoft Learn, Service Changes for Windows Vista. О том, зачем ввели изоляцию Session 0, о том, что служба и приложение больше не могут обмениваться оконными сообщениями, и об альтернативе через
WTSSendMessage. ↩ ↩2 ↩3 ↩4 ↩5 -
Microsoft Learn, Windows Enterprise multi-session FAQ. О том, что несколько интерактивных сеансов одновременно раньше были возможны только на Windows Server и теперь предлагаются исключительно для Azure Virtual Desktop. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Kernel object namespaces. О пространстве имён именованных объектов ядра на каждый сеанс, о префиксах
Global\/Local\и о том, что для создания нового объекта проекции файла или символьной ссылки в глобальном пространстве имён нужна привилегияSeCreateGlobalPrivilege. ↩ ↩2 ↩3 ↩4 ↩5 -
Microsoft Learn, Interactive Services. О том, что служба не может напрямую взаимодействовать с пользователем, о схеме запуска отдельного GUI-приложения через
CreateProcessAsUserи связи по IPC, и о рекомендации различать имена каналов по ID сеанса. ↩ ↩2 ↩3 -
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 per device / per user и о выдаче и учёте лицензий сервером лицензирования. ↩ ↩2
-
Microsoft Learn, qwinsta. О том, что столбец
SESSIONNAMEпоказывает имя, назначенное сеансу (consoleдля консоли, имя сrdp-tcp#для подключения по RDP), о примере вывода в тексте статьи и смысле столбцов (SESSIONNAME,USERNAME,STATE,TYPE,DEVICE), и о том, что у текущего сеанса в начале строки стоит>. ↩ ↩2 ↩3 -
Microsoft Learn, SystemParameters.IsRemoteSession Property. О свойстве WPF, которое даёт проверку, эквивалентную
SM_REMOTESESSION. ↩ -
Microsoft Learn, WTSGetActiveConsoleSessionId function (winbase.h). О получении ID сеанса, подключённого к физической консоли, и о возврате
0xFFFFFFFFв переходном состоянии. ↩ ↩2 -
Microsoft Learn, WTSEnumerateSessions function (wtsapi32.h). О том, что можно перечислить все сеансы на сервере RD Session Host. ↩
-
Microsoft Learn, Client/Server application guidelines. О том, что «подключение с одной машины» нельзя отождествлять с «одним пользовательским сеансом», и об идентификации сеанса через
ProcessIdToSessionId. ↩ -
Microsoft Learn, Policy CSP - ADMX_TerminalServer (TS_TEMP_PER_SESSION). О том, что Remote Desktop Services по умолчанию создаёт отдельную временную папку на каждый сеанс (в профиле пользователя, с ID сеанса в имени). ↩
-
Microsoft Learn, Peripheral hardware guidelines. О том, что диски и принтеры на стороне клиента с сервера RD Session Host видны по-разному в зависимости от конфигурации перенаправления, и о механизме, которым драйвер устройства отключает перенаправление. ↩
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Почему ломаются аргументы ── правила аргументов командной строки Windows
В Windows массива аргументов нет: в CreateProcess уходит одна строка, делит её принимающая сторона. Правила деления CommandLineToArgvW, C...
Практические рекомендации по многопоточности: .NET — что решить до добавления потоков
Проверенные приёмы проектирования на .NET/C#, чтобы код не «иногда падал или зависал»: не создавать потоки вручную и опираться на Task, с...
WMI/CIM из C# и PowerShell — практическое руководство по сведениям об оборудовании, мониторингу процессов и удалённым запросам
WMI/CIM — стандартный способ получить серийный номер ПК, следить за свободным местом на диске и ловить запуск процессов. Разбираем команд...
Глубины ввода-вывода Windows (часть 4) — диспетчер кэша: когда WriteFile оказывается на диске
Четвёртая часть серии со схемами диспетчера кэша Windows. Разбираем кэш как проекцию файла, упреждающее чтение и отложенную запись, когда...
Глубины ввода-вывода Windows (часть 3) — порты завершения ввода-вывода (IOCP) и пул потоков .NET: подвал под async/await
Третья часть цикла статей со схемами о портах завершения ввода-вывода (IOCP). Разбираются очередь вместе с управлением числом потоков, зн...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Бизнес-приложения, интеграция оборудования и средства связи — от требований до разработки.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Почему служба 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, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.