Windows 세션 분리를 어떻게 이해할 것인가 ── Session 0·RDP·다중 사용자 동시 실행

· 업데이트: · · Session0, 세션 분리, RDP, 원격 데스크톱, Windows 서비스, 멀티유저, Windows, .NET, C#, 설계, 기술 상담

수정 이력(7건, 최종 수정 2026년 09월 03일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
관련 기사 링크를 한국어 permalink에 맞추는 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
글 맨 앞에 「이 글의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
외부 리뷰(1283건)에 대응하여 본문을 갱신했습니다. 개별 변경 내용은 아래 이력을 참조하십시오.
결론을 골격 3가지와 논점 표로 정리하고, 각 행에서 해당 장으로 찾아갈 수 있게 했습니다. 더불어 세션별 네임스페이스 구조도, 코드 안의 긴 주석을 본문으로 옮긴 점, 약어의 정식 명칭, 세션 목록 명령 출력 읽는 법을 추가했습니다.
본문 중 관련 글로 가는 링크 문구가 링크 대상의 현재 제목과 어긋나 있던 부분을 실제 제목에 맞췄습니다. 본문 내용은 바꾸지 않았습니다.
최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.21635376)

아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.

Go Komura (2026). 「Windows 세션 분리를 어떻게 이해할 것인가 ── Session 0·RDP·다중 사용자 동시 실행」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/windows-session-rdp-multiuser-guide/

DOI(등록된 아카이브)
10.5281/zenodo.21635376
DOI(마지막 등록 버전)
10.5281/zenodo.21635377

「서비스에서 띄운 대화 상자가 누구에게도 보이지 않는다」「RDP로 연결하자마자 현장 화면이 로그인 화면으로 바뀌었다」「중복 실행을 막았어야 하는데 다른 사용자로 로그인하니 그대로 두 개가 떴다」. 업무 앱 상담에서는 이런 증상의 이면에 공통으로 「Windows 세션」 개념에 대한 이해 부족이 있습니다. 세션은 서비스 설계에도 원격 데스크톱 운용에도, named object의 중복 실행 방지에도 관여하는 여러 영역에 걸친 주제이지만, 체계적으로 설명된 기회는 많지 않습니다.

이 글에서는 Session 0 분리가 왜 도입되었는지, RDP 연결 시 세션이 어떻게 동작하는지, named object가 세션마다 어떻게 분리되는지, 그리고 공유 PC·RDS(Remote Desktop Services) 환경에서 흔한 설계 실수를 실무 관점에서 정리합니다.

1. 먼저 결론

골격은 세 가지입니다.

  • 서비스는 Session 0라는 격리된 세션에서 동작하며, UI를 직접 표시할 수 없습니다.1
  • RDS는 여러 동시 세션을 전제로 만들어져 있지만, 일반적인 클라이언트 OS는 단일 세션이 기본입니다.2
  • named kernel object에는 세션별 네임스페이스가 있으며, 의도에 따라 Global\ 필요 여부를 판단해야 합니다.3

논점별 결론과 자세히 다루는 장은 다음과 같습니다.

논점 결론 자세히
서비스에서의 UI 표시 Session 0 분리로 인해, 대화 상자를 표시해도 로그온 중인 사용자에게 전달되지 않는다. 아무도 누를 수 없는 버튼을 기다리며 멈춘다1 2장·3장
그 우회책 UI 앱을 대화형 사용자 세션 쪽의 별도 프로세스로 분리하고, IPC(프로세스 간 통신)로 통신한다. 작업 스케줄러로 대화형 사용자로 시작하는 방법도 선택지이다4 3장
동시 세션 수 Windows Server + RDS는 여러 동시. Windows 10/11 Pro 등 클라이언트 OS는 기본적으로 단일. AVD(Azure Virtual Desktop) 전용의 「Enterprise 멀티세션」 에디션은 예외2 4장
named object의 분리 Mutex·이벤트·세마포 등은 세션별 네임스페이스를 가진다. 머신 전체에서 하나로 제한하려면 Global\를 명시한다3 6장·9장
named pipe의 예외 위 메커니즘의 대상이 아니다. 기본값으로 머신 전체에서(서버 서비스가 동작 중이면 원격에서도) 접근할 수 있는 다른 네임스페이스를 쓰므로, 세션이나 사용자로 구분하려면 파이프 이름 자체에 세션 ID 등을 넣는다5 3장·6장
자신의 세션 판정 SESSIONNAME 환경 변수나 GetSystemMetrics(SM_REMOTESESSION)로 판정할 수 있으나, RemoteFX vGPU 사용 시 등 판정이 본래 의도와 어긋나는 경우가 있어 과신은 금물이다67 5장
공유 PC/RDS 대응 여부 설계 초기 단계에서 발주 측과 합의해야 할 사항. 임시 파일 경로 충돌, AppData 혼동, 디바이스·동글에 대한 동시 액세스는 단독 PC 전제 앱이 RDS에 올라간 순간에 터지는 전형적인 사고이다 7장·8장

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 20건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

2. Session 0 분리란 무엇인가 ── 왜 사용자 세션과 나눴는가

Windows Vista / Windows Server 2008보다 이전에는 서비스와 처음 로그온한 사용자가 같은 세션 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 프로세스는 알림 영역 아이콘이나 대화 상자로 사용자에게 보여 주고, 조작 결과를 서비스로 돌려준다. named pipe 등의 IPC(Inter-Process Communication, 프로세스 간 통신)를 쓸 때는 네트워크 너머로 공개되지 않도록 ACL(Access Control List, 액세스 제어 목록. 그 객체에 누가 어떤 조작을 할 수 있는지를 열거한 것)을 적절히 설계해야 합니다. 여러 사용자가 함께 있는 환경에서는 파이프 이름에 세션 ID를 넣는 등으로 세션마다 구분하는 설계가 안내됩니다. 4 IPC 수단 고르는 법은 같은 날 공개한 자매 글 「프로세스 간 통신 판단표」를 참조하십시오. 서비스 자체의 만드는 법이나 시작 종류·실행 계정·복구 옵션 같은 운용 면 설계는 「Windows 서비스 만드는 법」에 정리해 두었습니다.
  • 작업 스케줄러로 대화형 사용자로 시작한다. 서비스로 상주시키는 대신, 로그온 중인 사용자의 권한·세션에서 UI를 가진 프로그램을 시작하는 구성입니다. 여기서 주의할 점은 「SYSTEM으로 등록한 작업」 자체를 UI를 띄우는 쪽으로 두면 안 된다는 것입니다. SYSTEM 계정에는 대화형 로그온 권한이 없고, SYSTEM 권한으로 실행되는 프로그램이나 작업을 사용자가 보거나 조작할 수 없습니다. 9 상승 작업 모델은 본래, 표준 사용자가 시작할 수 있는 보안 설명자를 가진 SYSTEM 작업에 관리자 권한이 필요한 처리만 맡기고, UI 자체는 로그온 중인 사용자의 권한·세션에서 동작하는 다른 프로그램(또는 같은 처리를 사용자 계정 작업으로 등록한 것)에 두는 역할 분담이 전제입니다. 10 SYSTEM 작업을 「UI 비표시 브로커」에 철저히 두고, 보이는 UI는 반드시 사용자 자신의 세션에서 동작하는 프로세스에 맡기십시오.

어느 방식이든 「서비스는 백그라운드, UI는 별도 프로세스」라는 전제를 처음부터 설계에 넣는 것이 핵심입니다. 나중에 넣으려 하면 서비스 쪽에 UI 의존 코드가 스며들어 분리 비용이 치솟는 전개를 실무에서 자주 봅니다.

4. RDP 연결 시 세션의 동작 ── RDS와 클라이언트 OS의 차이

「서버와 클라이언트에서 같은 RDP인데 동작이 다르다」는 혼동은 Remote Desktop Services(RDS) 역할의 유무에서 비롯됩니다.

Windows Server + RDS 역할은 한 대의 서버에 여러 사용자가 동시에 로그온하여, 각자 독립 세션에서 데스크톱이나 앱을 쓰는, 여러 동시 세션을 전제로 한 구성입니다. 11 RDS에서는 일반적인 RDP 관리 연결(관리자용)은 2세션까지 CAL 없이 쓸 수 있지만, 그것을 넘는 대수나 일반 사용자의 동시 이용에는 RD Session Host 역할 도입과 적절한 RDS CAL(클라이언트 액세스 라이선스)이 필요합니다. 12 CAL에는 per device / per user 두 방식이 있으며, 라이선스 모델의 상세는 환경마다 확인이 필요합니다. 13

한편 Windows 10/11 Pro·Enterprise 등 일반적인 클라이언트 OS는 RDS 역할이 없고, 기본적으로 단일 세션을 전제로 합니다. 「Windows Enterprise 멀티세션」이라는, 여러 대화형 세션을 동시에 가질 수 있는 클라이언트 OS용 특별 에디션도 존재하지만, 이는 Azure Virtual Desktop 전용으로 제공되는 것이며, 일반적인 온프레미스 배포 클라이언트 OS에서는 여러 사용자의 동시 세션은 상정되어 있지 않습니다. 2 실무에서는 「RDP로 사내 담당자의 PC에 연결했더니 현장 화면이 잠금 화면으로 바뀌었다」는 체감 그대로, 클라이언트 OS에서는 콘솔(현장의 물리 조작)과 원격 연결이 동시에 공존하는 구성은 기본적으로 성립하지 않는다고 이해하는 편이 안전합니다.

업무 앱 설계에서 잡아 둘 차이를 정리합니다.

관점 Windows Server + RDS 클라이언트 OS(Windows 10/11 Pro 등)
동시 세션 여러 사용자가 동시에 로그온 가능11 기본적으로 단일 세션. AVD(Azure Virtual Desktop) 전용 멀티세션 에디션을 제외하면 여러 대화형 세션은 미지원2
라이선스 RD Session Host + RDS CAL이 필요(관리 연결 2개 제외)12 Remote Desktop 기능 자체는 OS에 포함되지만, 라이선스 요건·허용되는 에디션은 별도 확인이 필요
상정 용도 공유 단말에서의 업무 앱 운용, 씬 클라이언트 환경 개인 PC에 대한 원격 보수·재택 근무 시 액세스

이 차이를 감안하지 않고 「개발 기기의 클라이언트 OS에서 동작했으니 공유 서버의 RDS 환경에서도 같이 동작할 것」이라고 판단하면, 제7장에서 드는 여러 사용자 동시 실행 특유의 이상을 놓칩니다. 반대로 클라이언트 OS상의 단일 세션 운용만 상정한다면, 공유 PC 대응의 설계 비용을 처음부터 들일 필요는 없습니다. 이 판단은 요구사항 정의 초기에 해 두어야 합니다(제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가 세션 ID, STATE가 상태, TYPE이 세션 종류입니다. DEVICE는 콘솔이나 네트워크 연결 세션에는 표시되지 않습니다. 행 머리의 >는 지금 자신이 있는 세션을 가리킵니다. 14

이 공식 출력 예는 오래된 Windows의 것으로, console의 세션 ID가 0인 점에 주의하십시오. Windows Vista 이후에는 세션 0가 서비스 전용으로 예약되므로, 실제 단말에서는 물리 콘솔은 1 이후가 됩니다. 1 자기 단말에서 실행해 console이 1이면 Session 0 분리가 동작 중인 상태입니다. rdp-tcp라는 listen 상태의 행은 RDP 연결을 대기하는 리스너이며, 로그온 중인 사용자 세션이 아닙니다.

판정 수단은 몇 가지가 있습니다.

  • SESSIONNAME 환경 변수: 콘솔 세션에서는 Console, RDP 연결에서는 RDP-Tcp#로 시작하는 이름이 설정됩니다. qwinsta 명령으로 목록에 나오는 SESSIONNAME 열과 같은 정보입니다. 14 간이 판정에 쓸 수 있지만 공식 API가 아니므로 과도하게 의존하지 않는 편이 안전합니다.
  • GetSystemMetrics(SM_REMOTESESSION): 원격 세션인지 판정하는 Win32 API입니다. 호출 프로세스가 Terminal Services 클라이언트 세션에 묶여 있으면 0이 아닌 값을 반환합니다. 다만 Windows 8/Server 2012 이후, RemoteFX vGPU를 쓴 원격 세션에서는 이 함수가 원격 세션을 로컬 세션으로 오인한다는 제약이 있습니다. 그 경우에는 레지스트리의 GlassSessionId와 현재 세션 ID를 비교하는 대체 수단이 안내됩니다. 67
  • .NET(WPF)의 SystemParameters.IsRemoteSession: GetSystemMetrics(SM_REMOTESESSION)를 호출하는 것과 같은 판정을 WPF 앱에서 속성으로 얻을 수 있습니다. 15 WinForms나 콘솔 앱에서는 P/Invoke로 직접 호출하는 형태가 됩니다.
  • WTSGetActiveConsoleSessionId: 물리 콘솔에 연결된 세션 ID를 얻는 API입니다. 자기 세션 ID와 비교하면 「자신이 물리 콘솔에서 동작하는가」를 판정할 수 있습니다. 물리 콘솔에 아무도 연결되지 않은 상태(전이 중)에서는 0xFFFFFFFF가 반환되는 점에 주의하십시오. 16

이 WTSGetActiveConsoleSessionId를 RDS 환경에서 세션을 특정하는 데 쓰면 안 됩니다. 이름 그대로 반환되는 것은 「물리 콘솔에 연결된 세션」뿐이며, RDP로 로그온 중인 사용자 세션은 대상이 아닙니다. 16 서비스가 「대화형 사용자의 companion UI를 어느 세션에 띄워야 하는가」를 알고 싶을 때, 콘솔 연결 사용자만 상정한다면 이 API로 충분하지만, RDS에서 여러 RDP 사용자가 동시에 로그온하는 환경에서는 WTSEnumerateSessions로 동작 중인 세션을 열거하거나17, 세션 변경 알림(WM_WTSSESSION_CHANGE)이 넘겨 주는 세션 ID를 그대로 써야 합니다. 이 혼동은 RDP 경유 사용자에게만 알림 UI가 나오지 않거나 잘못된 세션에 나오는, 발견하기 어려운 이상의 전형적인 원인이 됩니다. 제3장의 IPC 구성에서 UI 프로세스를 어느 세션에서 시작할지를 정할 때 이 구분을 반드시 의식하십시오.

6. named object의 세션 분리

2장부터 5장까지 세션이라는 말이 여러 번 나왔습니다. 여기서 일단 세션 배치와 네임스페이스의 관계를 한 장의 그림으로 정리합니다. 이후 이야기는 모두 이 구조 위에 올라갑니다.

세션 2 ── 다음에 로그온한 사용자. RDS 환경에서는 여기가 늘어난다세션 1 ── 처음 로그온한 사용자세션 0 ── 서비스 전용. 대화형 사용자는 들어오지 않는다사용자 B의 앱Local 네임스페이스 ── 세션 2만의 것사용자 A의 앱Local 네임스페이스 ── 세션 1만의 것Windows 서비스SYSTEM 등으로 동작하는 상주 프로세스Local 네임스페이스 ── 세션 0만의 것Global 네임스페이스 ── 머신에 하나뿐. 모든 세션에서 같은 것이 보인다

그림 1: 세션마다 독립된 네임스페이스가 있고, 그 바깥에 머신 전체에서 하나인 글로벌 네임스페이스가 있다

접두사를 붙이지 않고 named object를 만들면 그 세션의 네임스페이스(그림의 Local)에 놓입니다. Global\를 붙였을 때만 그림 맨 아래의 머신 공통 네임스페이스에 놓입니다. 「다른 사용자로 로그인했더니 중복 실행 방지 Mutex가 동작하지 않았다」는 증상은 Local에 만든 것을 Global 의도로 쓰고 있었던 이야기일 뿐입니다.

Mutex, 이벤트, 세마포, 대기 가능 타이머, 파일 매핑 객체와 같은 named kernel object에는 세션마다 독립된 네임스페이스가 있습니다. 어떤 세션에서 MyAppMutex라는 이름의 Mutex를 만들어도, 다른 세션에서 같은 이름으로 열려고 하면 그것은 다른 것으로 새로 만들어집니다. 서비스 등 주로 세션 0에서 동작하는 프로세스, 혹은 여러 세션을 가로질러 객체를 공유하고 싶은 클라이언트/서버 구성을 위해, Global\를 이름 앞에 붙여 글로벌 네임스페이스에 명시적으로 둘 수 있습니다. 반대로 Local\를 붙이면 명시적으로 자기 세션 네임스페이스에 둡니다. 3

이것이 실무에서 효과가 드러나는 지점이 중복 실행 방지를 위한 Mutex입니다.

  • 「같은 사용자가 같은 세션 안에서 앱을 두 개 실행하지 못하게 하고 싶다」만이면 기본(접두사 없는) Mutex 이름으로 충분합니다. 세션별 네임스페이스가 자동으로 동작하므로, 다른 사용자 세션에서는 다른 Mutex로 취급됩니다.
  • 「RDS 환경에서 여러 사용자가 동시에 로그온해도 머신 전체에서 앱 인스턴스를 하나로 제한하고 싶다」면 Global\를 명시하지 않으면 목적을 달성할 수 없습니다. 기본값 그대로면 사용자마다 하나씩 실행되어 「중복 실행을 막았어야 하는데 여러 개가 떴다」는 이상이 됩니다.

덧붙여 non-session-0 프로세스가 파일 매핑 객체나 심볼릭 링크 객체를 글로벌 네임스페이스에 새로 만들려면 SeCreateGlobalPrivilege 권한이 필요합니다(기존 객체를 열기만 하면 불필요). 이 권한 검사는 파일 매핑·심볼릭 링크에 한한 이야기이며 Mutex나 이벤트 생성에는 부과되지 않지만, 공유 메모리(메모리 매핑 파일)를 글로벌 네임스페이스에서 쓰는 설계에서는 기억해 둘 필요가 있습니다. 3

또한 클라이언트/서버형 앱 전반에 대한 원칙으로 「한 대의 머신에서의 연결」을 「하나의 사용자 세션」과 동일시하지 말 것이 안내됩니다. 같은 머신에서 여러 세션(여러 사용자의 RDP 연결)이 동시에 맺어질 가능성을 감안해, 서버 쪽은 ProcessIdToSessionId 등으로 세션을 식별하고 세션마다 다른 통신 채널을 마련하는 설계가 권장됩니다. 18

7. 공유 PC·RDS 환경에서 흔한 설계 실수

단독 PC의 콘솔 이용만 상정해 만든 앱이 공유 PC나 RDS 환경에 올라간 순간에 터지는 이상에는 몇 가지 전형적인 패턴이 있습니다.

  • 임시 파일 경로 충돌. RDS는 기본값으로 세션마다 개별 임시 폴더(사용자 프로필 아래, 세션 ID를 이름에 포함)를 만듭니다. 19 즉 %TEMP% 환경 변수를 그대로 쓰면 이 분리의 혜택을 그대로 받습니다. 사고가 나는 것은 %TEMP%를 쓰지 않고 C:\Work\temp 같은 고정 경로를 앱이 하드코딩한 경우입니다. 같은 사용자 계정의 여러 세션, 혹은 여러 사용자가 같은 고정 경로에 동시에 쓰면 당연히 파일을 서로 빼앗게 됩니다.
  • AppData 혼동. 사용자 설정·캐시·로그를 어디에 저장할지는 Windows 사용자 프로필 구조(로컬/로밍, %LOCALAPPDATA%와 %APPDATA%의 구분)를 이해한 뒤에 정해야 합니다. 공유 PC에서는 이 설계의 부실이 「남의 설정으로 내 화면이 바뀐다」「로그가 덮어씌워진다」는 형태로 드러나기 쉽습니다. 상세는 「Windows 사용자 프로필 입문」을 참조하십시오.
  • 디바이스·동글·시리얼 포트에 대한 동시 액세스 전제. RDS 세션에서 클라이언트 쪽 로컬 디바이스나 시리얼 포트에 접근하려면 리디렉션을 거칩니다. 물리 USB 동글이나 시리얼 통신 장치를 쓰는 앱을 여러 세션에서 동시에 쓰게 하면, 애초에 하드웨어 쪽이 동시 액세스를 지원하지 않거나, 리디렉트 설정에 따라 보이기도 안 보이기도 하는 문제에 직면합니다. 클라이언트에 붙은 디바이스가 서버 쪽 세션에서 어떻게 보이는지는 리디렉션 구성에 의존하므로, 설계 단계에서 확인이 필수입니다. 20
  • 프린터·드라이브의 하드코딩. 기본 프린터 이름이나 드라이브 문자를 앱 안에 하드코딩하면, 사용자마다 리디렉트되는 기본 프린터나 매핑된 드라이브가 다른 공유 PC 환경에서는 쉽게 무너집니다. 프린터 이름은 실행 시에 얻고, 드라이브는 가능한 한 UNC 경로로 다루는 편이 안전합니다.
  • HKCU·사용자 레지스트리의 오해. HKEY_CURRENT_USER가 세션이 아니라 로그온 사용자에 묶인다는 점에도 주의가 필요합니다. 동일 사용자가 여러 세션을 가진 구성(RDS에서 같은 계정을 공유하는 등)에서는 HKCU 값이 모든 세션에서 공유되므로, 「세션마다 독립시키고 싶은 런타임 상태」를 HKCU에 두면 의도치 않게 다른 세션에 영향을 줍니다. 세션별 분리가 필요한 정보는 제6장의 세션 네임스페이스나, 세션 ID를 넣은 파일 경로로 다뤄야 합니다.

이들은 모두 「단독 PC라면 알아차리지 못한다」「여러 사용자가 동시에 써야 비로소 드러난다」는 성격의 이상이며, 테스트 환경이 단독 PC 개발 기기뿐이면 운영의 공유 PC·RDS 환경에서 처음 재현되는 전개가 되기 쉽습니다.

8. 판단표 ── 「공유 PC/RDS 대응」을 요구사항에 넣어야 하는가

공유 PC·RDS 환경에서의 동작을 요구사항에 넣을지는 구현이 진행되기 전 요구사항 정의 단계에서 확인해 둘 사항입니다. 넣기로 했다면 다음 관점을 체크리스트로 쓰십시오.

관점 단독 PC·콘솔 전제로 충분한 경우 공유 PC/RDS(여러 사용자 동시) 대응이 필요한 경우
임시 파일·설정 고정 경로여도 실해가 나기 어렵다 %TEMP% / %LOCALAPPDATA% 등 per-user/per-session 경로를 필수로 한다(제7장)
named object 기본(세션 네임스페이스) 그대로면 된다 「머신 전체에서 하나」면 Global\를 명시. 「세션마다 하나」면 기본 그대로. 「같은 사용자의 여러 세션을 묶어 하나」로 하고 싶다면 Global\에 사용자 SID를 조합한다(제6장, 중복 실행 방지 글도 참조)
디바이스·동글 로컬 직결로 단순 리디렉트를 거친다는 점, 동시 액세스가 불가할 가능성을 전제로 설계한다
UI와 서비스 같은 데스크톱 위에서 끝나기 쉽다 Session 0 분리를 감안해 별도 프로세스+IPC로 구성한다(제3장)
라이선스·과금 대수 기준으로 단순하게 셀 수 있다 동시 세션 수 사고방식, RDS CAL 필요 여부를 요구사항 정의 시 확인한다13

판단의 축은 단순합니다. 「이 앱은 담당자 1명의 PC에서 끝나 동작하면 되는가, 아니면 공유 단말이나 RDS 환경에서 여러 담당자가 동시에 쓸 가능성이 있는가」를 개발 초기에 발주 측과 합의해 둔다. 이를 모호하게 둔 채 구현을 진행하면 위의 관점을 모두 다시 만들게 됩니다.

9. 구현 예 ── 세션 판정과 네임스페이스 구분

제5장·제6장에서 소개한 판정 방법과 Mutex 이름 짓기의 구분을 .NET 코드로 정리합니다.

using System.Runtime.InteropServices;

internal static class SessionInfo
{
    [DllImport("user32.dll")]
    private static extern int GetSystemMetrics(int nIndex);

    private const int SM_REMOTESESSION = 0x1000;

    // WPF 앱이라면 System.Windows.SystemParameters.IsRemoteSession이 같은 판정을 반환한다
    public static bool IsRemoteSession() => GetSystemMetrics(SM_REMOTESESSION) != 0;

    // 간이 판정. 공식 API가 아니므로 중요한 분기에는 쓰지 말고 로그·진단 정보용으로 둔다
    public static string? SessionName() =>
        Environment.GetEnvironmentVariable("SESSIONNAME");
}

중복 실행 방지 Mutex는 「머신 전체에서 하나」「세션마다 하나」「같은 사용자의 여러 세션을 묶어 하나」의 세 가지를 구분해 네임스페이스를 나눕니다. 세 번째(사용자 단위·세션 횡단)는 기본 Global\만으로는 구현할 수 없고, 이름에 사용자 SID를 넣어야 한다는 점은 「Windows 앱의 중복 실행 방지」에서 자세히 다룹니다.

「머신 전체에서 하나」를 Global\로 구현할 때는 ACL을 명시적으로 설정해야 합니다. 이유는 .NET의 Mutex / MutexAcl.Create가 기존 Mutex를 열기만 하는 경우에도 내부적으로 SYNCHRONIZE·MUTEX_MODIFY_STATE에 더해 DELETE·READ_CONTROL·WRITE_DAC·WRITE_OWNER까지 요구하기 때문입니다. 기본 ACL 그대로면 Mutex를 처음 만든 사용자 이외의 정규 사용자가 createdNew를 받는 시점에 UnauthorizedAccessException이 됩니다. 이를 피하려면 「인증된 사용자」에 FullControl 상당을 허용합니다.

이 설정에는 대가가 두 가지 있습니다. 하나는 인증된 누구든지 나중에 ACL을 바꿀 수 있는 상태가 된다는 것. 이것도 허용할 수 없다면 Mutex 클래스를 쓰지 않고 P/Invoke로 CreateMutexEx를 직접 호출해 필요 최소 액세스 권한만 요구하는 설계로 바꿉니다. 다른 하나는 이 ACL은 자신이 먼저 Mutex를 만든 경우에만 적용된다는 것입니다. 고정 이름의 Global\ Mutex는 다른 사용자에게 선점되어 만들어지는(이름 스쿼팅) 위험이 있고, 그 경우 createdGlobal이 false가 되거나 상대 ACL에 따라 UnauthorizedAccessException이 됩니다. 정말로 막고 싶다면 추측하기 어려운 이름이나 다른 배타 제어와 조합하십시오(상세는 「Windows 앱의 중복 실행 방지」 제5장).

// using System.Security.AccessControl; 과 using System.Security.Principal; 이 필요
// (NuGet 패키지 System.Threading.AccessControl도 필요)

// 의도: RDS/공유 PC에서 여러 사용자가 동시에 로그온해도 머신 전체에서 인스턴스는 하나
var globalMutexSecurity = new MutexSecurity();
globalMutexSecurity.AddAccessRule(new MutexAccessRule(
    new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, domainSid: null),
    MutexRights.FullControl,
    AccessControlType.Allow));

using var globalMutex = MutexAcl.Create(
    initiallyOwned: false,
    name: @"Global\KomuraSoft.MyApp.SingleInstance",
    createdNew: out bool createdGlobal,
    mutexSecurity: globalMutexSecurity);

// 의도: 세션마다 하나. 기본 세션 네임스페이스 그대로면 된다
using var perSessionMutex = new Mutex(
    initiallyOwned: false,
    name: "KomuraSoft.MyApp.SingleInstance",
    createdNew: out bool createdPerSession);

if (!createdGlobal)
{
    // 다른 세션을 포함해 머신 어딘가에서 이미 실행 중이다
    return;
}

Global\를 붙인 Mutex 생성 자체에는 특별한 권한이 필요 없습니다(앞에서 말했듯이 권한 검사가 부과되는 것은 파일 매핑 객체와 심볼릭 링크 객체의 신규 생성에 한합니다). 3 「사용자마다 하나」를 의도하고 기본 세션 네임스페이스 그대로 쓰는 것은 잘못입니다. 같은 사용자가 끊긴 RDP 세션과 새 콘솔/RDP 세션을 둘 다 가진 상황은 드물지 않고, 그 경우 세션마다 다른 Mutex로 취급되므로 기본 그대로면 같은 사용자가 두 번째 인스턴스를 실행할 수 있습니다. 「머신 전체에서 하나」「세션마다 하나」「사용자마다 하나(세션 횡단)」 중 어느 것이 요구사항으로 맞는지는 앱 성격에 따라 정해지므로, 이름을 짓기 전에 먼저 요구사항을 확인하십시오.

10. 정리

Windows의 「세션」은 서비스 설계·원격 데스크톱 운용·named object의 중복 실행 방지라는, 겉보기에는 따로 떨어진 주제에 공통으로 등장하는 개념입니다. 잡아 둘 골격은 세 가지로 모입니다. 서비스는 Session 0라는 격리된 특별 세션에서 동작하며 UI를 직접 띄울 수 없다는 것. RDS는 여러 동시 세션을 전제로 만들어져 있는 반면, 일반적인 클라이언트 OS는 단일 세션이 기본이라는 것. 그리고 named object에는 세션별 네임스페이스가 있어, 의도에 따라 Global\ 필요 여부를 판단해야 한다는 것입니다.

공유 PC·RDS 환경에서의 동작을 요구사항에 넣을지는 구현 후반에 알아차리면 재작업이 커지는 주제입니다. 요구사항 정의 단계에서 「이 앱은 누가, 어느 단말에서, 몇 명이 동시에 쓰는가」를 확인해 두기를 강하게 권합니다. 기존 앱을 공유 PC·RDS 환경에 올리려는 계획이 있거나, 「여러 사용자가 쓰면 상태가 이상하다」는 이상의 원인 조사가 필요하면, 실제 구성을 보면서 원인을 가르는 편이 빠른 경우가 많으니 상담해 주십시오.

관련 글

관련 상담 영역

合同会社小村ソフト에서는 공유 PC·RDS 환경으로의 도입을 염두에 둔 Windows 앱 설계 리뷰, 서비스화·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. named kernel object의 세션별 네임스페이스, Global\ / Local\ 접두사, 파일 매핑·심볼릭 링크 객체를 글로벌 네임스페이스에 새로 만들 때 SeCreateGlobalPrivilege가 필요해진다는 점에 대해. ↩ ↩2 ↩3 ↩4 ↩5

  4. Microsoft Learn, Interactive Services. 서비스가 사용자와 직접 대화할 수 없다는 점, CreateProcessAsUser로 별도 프로세스의 GUI 앱을 시작하고 IPC로 연동하는 설계, 파이프 이름을 세션 ID로 구분하는 안내에 대해. ↩ ↩2 ↩3

  5. Microsoft Learn, Named Pipes. named pipe가 보안 검사 범위 안에서 어느 프로세스에서도 접근 가능하며, 서버 서비스가 동작 중이면 원격에서도 도달 가능하다는 점에 대해(Mutex 등의 Global\/Local\ 세션 네임스페이스와는 다른 메커니즘이라는 점의 근거). ↩

  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. 관리 용도에서는 최대 2개의 동시 원격 연결이 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 연결이면 rdp-tcp#로 시작하는 이름)을 가리킨다는 점, 본문에 인용한 출력 예와 각 열(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 서비스에서 대화 상자를 표시할 수 없는 이유는 무엇입니까?
Windows Vista 이후의 Session 0 분리가 이유입니다. 서비스는 Session 0라는 전용 세션에서 동작하며, 로그온 중인 사용자의 대화형 세션과는 분리되어 있습니다. 이는 표준 권한 프로세스가 SYSTEM 권한 서비스의 창으로 조작된 메시지를 보내는 권한 상승 공격을 구조적으로 차단하기 위한 보안 설계입니다. 서비스 안에서 대화 상자를 표시해도 사용자에게는 보이지 않고, 아무도 누를 수 없는 OK 버튼을 기다리며 처리가 멈추는 사고의 원인이 됩니다. 해결책은 UI 앱을 사용자 세션 쪽의 별도 프로세스로 분리하고 IPC로 통신하는 구성입니다.
RDP로 연결하면 현장 화면이 잠금 화면으로 바뀌는 이유는 무엇입니까?
Windows 10/11 Pro 등 일반적인 클라이언트 OS는 기본적으로 단일 세션을 전제로 하기 때문입니다. 콘솔(현장의 물리 조작)과 원격 연결이 동시에 공존하는 구성은 기본적으로 성립하지 않습니다. 여러 사용자가 동시에 로그온하여 독립 세션을 쓸 수 있는 것은 Windows Server + RDS(Remote Desktop Services) 역할 구성이며, 관리 연결 2개를 넘는 이용에는 RD Session Host 역할과 RDS CAL이 필요합니다. Azure Virtual Desktop 전용의 「Enterprise 멀티세션」 에디션은 예외입니다.
RDS 환경에서 중복 실행 방지 Mutex가 동작하지 않는 이유는 무엇입니까?
named kernel object(Mutex, 이벤트, 세마포 등)에는 세션마다 독립된 네임스페이스가 있기 때문입니다. 접두사 없는 Mutex 이름에서는 다른 사용자의 세션에서 다른 Mutex로 취급되어, 사용자마다 하나씩 실행할 수 있습니다. 머신 전체에서 인스턴스를 하나로 제한하려면 Global\ 접두사를 명시해야 합니다. 「같은 사용자의 여러 세션을 묶어 하나」로 하고 싶다면 Global\에 더해 이름에 사용자 SID를 조합합니다.
공유 PC·RDS 환경에서 업무 앱이 오동작하는 전형적인 원인은 무엇입니까?
전형적인 패턴은 5가지입니다. %TEMP%를 쓰지 않고 고정 경로를 하드코딩한 것에 따른 임시 파일 충돌, 사용자 프로필(AppData) 구분의 부실, USB 동글이나 시리얼 포트에 대한 동시 액세스 전제, 프린터 이름·드라이브 문자의 하드코딩, 그리고 세션마다 독립시켜야 할 상태를 HKCU에 두는 설계입니다. 모두 단독 PC에서는 알아차리기 어렵고, 여러 사용자가 동시에 써야 비로소 드러나는 성격이므로, 공유 PC·RDS 대응 여부는 요구사항 정의 초기에 확인해야 합니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기