Files
networkSetup/docs/code-design/14-exe-installer-ui.md
T
jychoi4andClaude Opus 5.5 7f5540f148 docs: design the EXE installer UI and code signing scope
- Design 14: WiX MSI + Burn with a C# BA host and WebView2 web UI,
  default screen catalog, installer-ui.json and the WebView2 fallback.
- Design 16: Smart App Control checks and what must be signed.
- Manual: beginner guide to Azure Artifact Signing.
- Link the decision from designs 09 and 10 and record it.

Refs #15

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 11:08:50 +09:00

19 KiB
Raw Blame History

EXE 설치 UI: WebView2 BA와 기본 화면

결정일: 2026-11-09. EXE 설치 파일(setup.exe)의 화면을 사용자가 자유롭게 꾸밀 수 있게 하는 구조와 기본으로 제공할 화면 목록을 정한다. 이 문서는 설계이며 BA·웹 UI 구현, Burn 연결, Windows 설치 시험은 수행하지 않았다. MSIX는 Windows가 설치 UI를 관리하므로 이 문서의 대상이 아니다.

09 설치 과정과 UI 편집의 단계·데이터 계약과 10 설치 마법사 재설계의 모드별 흐름·실패 처리 원칙은 유지한다. 아래 결정이 두 문서와 다르면 이 문서를 따른다.

결정

항목 결정
설치 패키지 WiX로 만든 MSI를 Burn 번들(setup.exe)로 감싼다. WiX 버전과 이용 조건(OSMF)은 채택 전에 확정한다.
설치 UI 사용자 정의 Burn BA를 사용한다. BA 호스트는 C#(.NET Framework 4.8)로 만들고 화면은 WebView2가 표시하는 HTML/CSS/TypeScript 웹 UI로 만든다.
제품별 차이 BA 호스트와 웹 UI는 공통으로 한 번 빌드·서명한다. 제품마다 다른 화면 설정은 installer-ui.json과 이미지·약관 자산으로만 전달한다.
기본 화면 아래 기본 화면 카탈로그를 표준으로 제공한다. 제작자는 문구·자산·표시 여부·일부 순서를 편집한다.
WebView2가 없을 때 같은 BA 안의 단순한 네이티브 기본 화면(WPF)으로 설치를 진행한다. 인터넷이 되면 WebView2 웹 부트스트래퍼를 BA 선행 패키지로 먼저 설치할 수 있다. 고정 버전 런타임 포함은 기본값이 아니다.
무인 설치 /quiet·/passive에서는 WebView2를 만들지 않고 같은 검증과 기본값으로 설치한다.
제외 Rust/Tauri로 BA를 직접 구현하는 방식(09의 "Rust/Tauri 사용자 정의 설치 UI")과 WixStdBA 테마 편집을 첫 구현으로 삼는 순서는 채택하지 않는다. WiX가 C++·.NET BA API만 공식 제공하기 때문이다.

구조

setup.exe (Burn 번들, 서명)
 ├─ Burn 엔진 ........ 탐지 → 계획 → 적용, UAC 권한 상승, 롤백, 프로그램 추가/제거 등록
 ├─ BA 호스트 (C#, .NET Framework 4.8)
 │    ├─ WebView2 ── 웹 UI 표시
 │    ├─ 기본 네이티브 화면 ── WebView2를 쓸 수 없을 때
 │    └─ 브리지 ── 웹 UI ↔ Burn 메시지 중계, 입력 재검증
 ├─ 웹 UI (Vite 빌드 결과: HTML/CSS/JS)
 ├─ installer-ui.json + 로고·이미지·약관
 ├─ 선행 프로그램 패키지
 └─ 제품 MSI

WiX v5부터 Burn은 BA를 별도 실행 파일로 띄운다. BA 호스트는 WiX가 제공하는 .NET BA 라이브러리로 Burn과 통신한다. 실제 파일·레지스트리 변경은 Burn과 MSI가 수행하며 BA나 웹 UI가 msiexec를 따로 실행하지 않는다.

Installer Studio도 WebView2(Tauri)를 쓰므로 같은 웹 UI에 가짜 브리지를 연결해 제작 화면에서 미리보기를 제공한다. 미리보기는 시스템을 변경하지 않으며, 최종 판정은 실제 번들의 VM 시험으로 한다.

실행 흐름

  1. Burn이 BA 호스트를 실행한다. 대화형 모드면 호스트가 창을 만들고 WebView2 사용 가능 여부를 확인한다.
  2. 웹 UI는 번들 안의 파일만 가상 호스트(https://installer.local/)로 읽는다. 외부 주소 이동, 새 창, 다운로드는 차단한다.
  3. Burn 탐지 결과(설치 여부, 설치된 버전, 기능 상태, 선행 프로그램 필요 여부)를 웹 UI에 보내고, 웹 UI는 신규 설치·업그레이드·유지 관리 중 맞는 흐름을 연다.
  4. 사용자가 review에서 시작을 누르면 웹 UI가 apply 메시지를 보낸다. 호스트는 경로 규칙, 허용 기능, 약관 동의를 다시 검사한 뒤 Burn 변수를 설정하고 계획·적용을 요청한다.
  5. Burn의 진행·오류·재부팅·취소 이벤트를 웹 UI에 전달한다. 적용이 시작된 뒤에는 선택값과 계획을 바꾸지 않는다.
  6. 완료 화면의 "앱 실행"은 원래 사용자 권한으로 실행한다.

브리지 메시지

웹 UI는 아래 형식의 메시지만 보낼 수 있다. 호스트는 알 수 없는 type, 형식이 틀린 값, 현재 상태에서 허용되지 않는 요청을 거부한다.

방향 type 내용
호스트 → UI init installer-ui.json, 선택 언어, 실행 모드(대화형)
호스트 → UI detected 설치 상태(absent/older/same/newer), 설치된 버전·경로, 기능 상태, 필요한 선행 프로그램, 차단 사유
호스트 → UI validation 입력 검증 결과(필드별 오류 코드와 문구)
호스트 → UI progress 전체 진행률, 현재 패키지·작업 이름
호스트 → UI filesInUse 사용 중인 프로그램 목록
호스트 → UI result succeeded/failed/cancelled, 재부팅 필요 여부, 오류 코드, 로그 경로
UI → 호스트 validate 설치 폴더, 기능 선택 등 입력 검증 요청
UI → 호스트 browseFolder 폴더 선택 대화상자 요청. 결과 경로도 호스트가 검증
UI → 호스트 apply 동작(install/modify/repair/uninstall), 설치 폴더, 기능, 추가 작업, 약관 동의
UI → 호스트 cancel 취소 요청. 처리 결과는 result로 확인
UI → 호스트 retryFilesInUse / closeApplications 사용 중인 프로그램 다시 확인. 강제 종료는 기본 동작이 아니다.
UI → 호스트 launchApp, openLink, restart, close 완료 후 작업. openLink는 installer-ui.json에 등록된 주소만 허용

기본 화면 카탈로그

모든 화면은 공통 틀(상단 로고·제품명·단계 표시, 본문, 하단 이전/다음·실행/취소 버튼)을 쓴다. 언어는 한국어·영어를 기본으로 하고 시스템 언어로 자동 선택한다.

신규 설치 흐름

순서 화면 ID 내용 숨김
1 welcome 제품명·버전·로고, 안내 문구. 이전 버전이 있으면 업그레이드 안내 가능
2 license 약관 전문과 동의. 동의해야 다음으로 진행 약관이 없을 때만
3 installFolder 기본 설치 폴더, 찾아보기, 필요·사용 가능 공간 가능. 숨기면 기본 경로
4 features 기능 트리와 용량. 필수 기능은 해제 불가 가능. 숨기면 기본 선택
5 tasks 바탕화면 바로가기, 파일 연결 등 선택 작업 가능. 숨기면 기본값
6 review 경로, 기능, 선행 프로그램, 필요한 권한 요약과 시작 버튼 불가
7 progress 현재 작업, 전체 진행률, 취소 불가

유지 관리 흐름

이미 설치된 PC에서 setup.exe를 실행하거나 프로그램 추가/제거에서 변경을 선택할 때 사용한다.

화면 ID 내용 다음 화면
maintenance 수정, 복구, 제거 선택 수정: features → review, 복구·제거: review 또는 uninstallConfirm
uninstallConfirm 제거할 항목과 남는 항목(사용자 도면·설정·백업) 안내, 확인 progress

공통 상태 화면

조건에 따라 표시하며 숨길 수 없다.

화면 ID 조건 내용
blocked 시작 전 검사 실패 더 높은 버전 설치됨, 지원하지 않는 OS, 디스크 부족 등 원인과 조치
filesInUse 설치할 파일을 쓰는 프로그램이 있음 저장 후 종료 안내, 다시 확인
finish 성공 결과 요약, 앱 실행 선택, 링크
rebootRequired 성공했지만 재부팅 필요 이유, 지금/나중에 선택. 기본은 나중에
failure 오류 실패 단계, 기존 제품 상태, 로그 위치, 다음 조치
cancelled 사용자 취소 취소 시점과 엔진이 확인한 실제 상태

편집 규칙

  • 화면을 숨겨도 그 단계의 작업은 기본값으로 실행한다. 숨김은 표시 여부만 바꾼다.
  • license는 약관이 지정되어 있으면 숨길 수 없다. 숨김을 동의로 처리하지 않는다. 무인 설치의 동의 전달은 별도 인자로 정한다.
  • 순서 변경은 welcome~tasks 사이의 선택 화면에만 허용한다. review 이후와 상태 화면은 고정한다.
  • 표시하는 입력은 실제 설치 정의와 연결되어야 한다. 예를 들어 tasks의 바로가기 항목은 MSI 바로가기 정의가 있어야 하며, 연결이 없으면 빌드 오류로 처리한다.
대상 편집 가능 항목
모든 화면 제목, 설명, 다국어 문구, 로고, 강조 색, 밝기 테마
welcome 대표 이미지, 안내 문구
license 약관 파일(RTF/TXT/HTML), 동의 문구
installFolder 기본 경로(프로젝트 formats.exe.installRoot), 경로 변경 허용 여부
features 기능 이름·설명·기본 선택·필수 여부
tasks 항목 표시, 기본 선택 상태
finish 앱 실행 표시와 기본 선택, 링크 목록

installer-ui.json

빌드 시 프로젝트 설정에서 생성해 번들에 넣는 파일이다. 제작기에서는 버전 메타파일의 installerUi로 편집하며(제작기 설치화면 편집), 이 절은 번들에 넣는 생성 결과의 형식을 정한다. 자산 경로는 프로젝트 폴더 기준의 안전한 상대 경로만 허용한다.

{
  "schemaVersion": 1,
  "template": { "id": "standard-wizard", "version": "1.0.0" },
  "theme": {
    "logo": "assets/logo.png",
    "welcomeImage": "assets/welcome.png",
    "accentColor": "#7257C5",
    "colorScheme": "dark"
  },
  "locales": { "default": "ko-KR", "available": ["ko-KR", "en-US"] },
  "strings": {
    "ko-KR": { "welcome.title": "EG-BIM 설치를 시작합니다" },
    "en-US": { "welcome.title": "Welcome to EG-BIM Setup" }
  },
  "pages": [
    { "id": "welcome", "visible": true },
    { "id": "license", "visible": true, "file": { "ko-KR": "assets/license.ko.rtf", "en-US": "assets/license.en.rtf" } },
    { "id": "installFolder", "visible": true, "allowChange": true },
    { "id": "features", "visible": false },
    { "id": "tasks", "visible": true },
    { "id": "review" },
    { "id": "progress" }
  ],
  "finish": {
    "launchApp": { "visible": true, "defaultChecked": true },
    "links": [{ "label": "release.notes", "url": "https://example.com/egbim/release-notes" }]
  }
}
  • 알 수 없는 필드, 카탈로그에 없는 화면 ID, 숨길 수 없는 화면의 visible: false, 허용 범위 밖의 순서는 빌드 전에 거부한다.
  • 상태 화면은 pages에 적지 않는다. 문구만 strings로 바꾼다.
  • url은 https만 허용하고 openLink 요청은 이 목록과 비교한다.
  • 빌드 스냅샷에는 installer-ui.json과 모든 자산의 SHA-256을 기록한다. 서명한 setup.exe의 화면을 외부 파일로 바꾸지 않는다.

제작기 설치화면 편집 (2026-11-09)

설치 구성 › 설치화면 탭에서 버전마다 설치 단계·화면 문구·모양·스크립트를 편집한다. 값은 버전 메타파일(.isver)의 installerUi에 저장하고, 버전을 추가하면 함께 복사하며, 잠긴 버전은 읽기 전용이다. 이 필드가 없는 기존 버전 파일은 아래 표준 마법사로 연다. 빌드 시 이 값에서 installer-ui.json과 MSI 사용자 지정 동작을 만든다(EXE 빌드와 함께 후속 구현).

항목 규칙
표준 마법사 welcome, installFolder, review, progress, finish 다음에 maintenance, uninstallConfirm, failure, cancelled. 이 9개는 지울 수 없다.
단계 추가 license(한 번만, 약관 파일 필수), info(안내 화면: 제목·설명), input(입력 화면). 추가한 단계는 review 바로 앞에 들어가며 삭제할 수 있다.
순서 welcome·license·installFolder·info·input만 review 앞에서 순서를 바꾼다. review·progress·finish와 유지 관리·결과 화면은 고정이다(InvalidPageOrder).
숨김 welcome·installFolder·info·input만 숨긴다. 숨겨도 그 단계는 기본값으로 진행한다(PageCannotBeHidden).
문구 제목·설명은 서식 문자열이며 [ProductName] 같은 속성문자를 쓴다. 정의되지 않은 속성은 UnknownProperty.
입력 화면 항목마다 저장할 사용자 속성(프로젝트 설정 › 속성문자), 이름, 형식(text·checkbox·choice), 기본값, 선택 목록, 필수 여부를 둔다. 같은 속성을 두 항목이 쓰지 않는다(DuplicateInputProperty). 입력값은 레지스트리·바로가기·스크립트 인수에서 [속성]으로 쓴다.
모양 밝기(dark/light), 강조 색(#RRGGBB), 로고(프로젝트 폴더 기준 PNG·JPG·SVG). 약관 파일은 RTF·TXT·HTML. 파일이 없으면 MissingAsset.
설치 폴더 fixedFolder를 켜면 기본 설치 폴더를 바꿀 수 없다.

스크립트

항목 규칙
종류 powershell(.ps1), cmd(.cmd·.bat), exe(.exe). 확장자가 다르면 ScriptKindMismatch. 화면에 스크립트 본문을 입력하지 않는다.
파일 프로젝트 소스(alias) 폴더 안의 / 경로. 설치 파일과 별도로 번들에 포함하므로 설치되지 않는 파일도 쓸 수 있다. 없으면 MissingScriptFile. 빌드 스냅샷에 SHA-256을 기록한다.
실행 시점 beforeFiles(설치: 파일 복사 전), afterFiles(설치: 파일 복사 후), beforeUninstall(제거: 파일 삭제 전)은 MSI 지연 사용자 지정 동작으로 설치 엔진 권한(관리자)으로 실행한다. afterFinish(완료 후)는 BA가 완료 화면 뒤에 로그인 사용자 권한으로 실행한다.
인수 한 줄에 하나씩 배열로 전달하고 명령 문자열로 합치지 않는다. PowerShell은 powershell.exe -NoProfile -ExecutionPolicy Bypass -File <파일> <인수...>, CMD는 cmd.exe /d /c <파일> <인수...> 형태로 실행하며 인수는 따옴표 규칙에 맞게 하나씩 이스케이프한다. 각 인수는 서식 문자열이다.
실패 처리 fail은 0이 아닌 종료 코드·제한 시간 초과에서 설치를 중단하고 설치 엔진의 변경을 되돌린다. 스크립트가 직접 바꾼 내용은 되돌리지 않는다. ignore는 로그에 남기고 계속한다. afterFinish는 ignore만 허용한다(ScriptFailureAfterFinish).
제한 시간 1~3600초(기본 600). 시간이 지나면 프로세스 트리를 종료한다.
로그 스크립트 이름, 시점, 종료 코드, 소요 시간을 설치 로그에 남긴다. 출력에 비밀 값을 쓰지 않도록 안내한다.

패치 버전도 같은 스크립트를 가질 수 있으며 패치 적용 시 실행된다. 스크립트 실행은 Windows VM 설치·제거·패치 시험 전에는 검증된 것으로 표시하지 않는다.

WebView2 가용성

Windows 11과 대부분의 Windows 10에는 WebView2 Evergreen 런타임이 있지만 LTSC, Server, 업데이트가 막힌 오프라인 PC에는 없을 수 있다.

상황 동작
WebView2 있음 웹 UI 표시
없음 + 인터넷 사용 가능 번들 설정이 허용하면 WebView2 웹 부트스트래퍼를 BA 선행 패키지로 설치한 뒤 웹 UI 표시. 실패하면 기본 네이티브 화면
없음 + 오프라인 기본 네이티브 화면으로 같은 흐름 진행
무인 설치 WebView2를 사용하지 않음

기본 네이티브 화면은 같은 카탈로그의 필수 화면(welcome, license, installFolder, review, progress, 상태 화면)과 같은 검증을 제공하며 디자인 편집은 문구·로고로 제한한다. WebView2 사용자 데이터 폴더는 실행마다 임시 폴더에 만들고 종료 시 삭제한다.

보안

  • 웹 UI는 번들 안 정적 파일만 사용한다. 원격 스크립트, 외부 CDN, eval을 쓰지 않고 CSP는 default-src 'self'로 둔다.
  • 릴리스 빌드는 개발자 도구, 상황 메뉴, 확대·축소 단축키를 끈다.
  • 웹 UI는 파일·레지스트리·프로세스에 직접 접근하지 않는다. 모든 요청은 정해진 메시지로 호스트를 거치고 호스트가 다시 검증한다.
  • BA와 웹 UI는 일반 사용자 권한으로 실행한다. 권한 상승은 Burn이 관리하며 UAC 화면은 꾸미지 않는다.
  • 로그와 메시지에 토큰·비밀번호를 넣지 않는다.

1단계 범위와 검증 실험

첫 구현은 신규 설치·업그레이드·제거와 실패·취소를 확인할 수 있는 화면 10개로 한다.

welcome, license, installFolder, review, progress, finish, failure, cancelled, maintenance, uninstallConfirm

features, tasks, filesInUse, rebootRequired, blocked는 MSI 기능·바로가기·파일 연결을 프로젝트 모델에 넣는 다음 단계에서 추가한다.

본 구현 전에 백로그 BL-08의 검증 실험을 VM에서 수행한다. MSI 1개와 선행 프로그램 1개를 담은 Burn 번들에 C# 호스트와 웹 UI를 붙이고 아래를 확인한다.

  • 신규 설치, 같은 UpgradeCode의 상위 버전 업그레이드, 제거, 복구
  • 설치 폴더 변경과 잘못된 경로 거부
  • 진행률 표시, 적용 중 취소와 롤백 결과 표시
  • 실패 화면과 로그 위치
  • /quiet 무인 설치에서 WebView2 미사용
  • WebView2가 없는 PC에서 기본 네이티브 화면으로 설치 완료
  • 고해상도(DPI), 키보드 이동, 스크린 리더 읽기

실험 결과는 src/tests/verification-YYYY-MM-DD-<slug>.json에 실행·미실행 항목을 모두 기록한다. 실험 전에는 이 구조를 검증된 것으로 표시하지 않는다.

미결정 사항

  • WiX 버전(v6 또는 v7)과 OSMF 이용 조건 수용 여부
  • 지원 대상 PC 범위(Windows 10 LTSC·Server·오프라인 PC 포함 여부)와 WebView2 웹 부트스트래퍼 사용 허용 여부
  • 약관 파일 형식과 무인 설치의 동의 전달 방식
  • 기본 지원 언어 목록
  • 기본 네이티브 화면을 WPF로 할지, 같은 호스트의 최소 Win32 화면으로 할지

근거