rubrapack 매뉴얼←↑→

17 MSIX 패키지와 번들

목표: 같은 원본으로 Hello 를 MSIX 패키지 - Windows 의 더 새로운 패키지 형식 - 로도 빌드하고, 이어서 세 아키텍처를 모두 담은 번들 하나로 빌드한다.

17.1 MSI 인가 MSIX 인가?#

MSI (.msi)MSIX (.msix)
설치패키지가 말하는 곳 어디든, 시키는 대로 컴퓨터를 바꾼다Windows 가 관리하는 봉인된 폴더에. 앱이 쓰는 파일과 레지스트리는 따로 보관된다
제거패키지가 만든 만큼 깨끗하게늘 완전히: 아무것도 남지 않는다
서명권장필수: Windows 는 서명 안 된 MSIX 를 설치하지 않는다(아래의 시험용은 예외)
할 수 있는 것이 튜토리얼의 모든 것파일, 바로가기, 파일 형식, 레지스트리, INI 파일, 글꼴, 서비스, 명령줄 별칭, 로그인할 때 시작, 앱 자신의 프로세스에 주는 환경 변수
할 수 없는 것-사용자 지정 동작, 컴퓨터 전체의 환경 변수, 권한, 조건, 대화창

많은 제품이 둘 다 낸다. rubrapack 은 원본 하나로 어느 쪽이든 빌드한다: 출력 파일의 확장자가 정한다.

17.2 폴더#

원본 옆 assets\ 에 로고 셋을 둔다: 정확히 150x150, 44x44, 50x50 픽셀인 PNG 그림이다. 시작 메뉴와 설정 앱이 이것을 보인다. 없으면 패키지는 단색의 밋밋한 로고를 받는다 - 셋을 모두 주거나 하나도 주지 않는다.

C:\work\hello\
    assets\
        Square150x150.png
        Square44x44.png
        StoreLogo.png
    dist\
        x64\hello.exe
        x86\hello.exe
        arm64\hello.exe
        docs\guide.txt
    hello.toml

17.3 원본#

# tutorial 17: hello.toml
format = 1

[package]
name = "Hello"
manufacturer = "Example Software"
version = "$(VERSION)"
arch = "x64"
upgrade-code = "{3F2A6C1D-8B4E-4F7A-9C2D-5E6F7A8B9C0D}"
upgrade-code-x86 = "{7D1C2B3A-4E5F-4061-9728-3A4B5C6D7E8F}"
upgrade-code-arm64 = "{0E9F8D7C-6B5A-4948-8372-6150F4E3D2C1}"

[define]
VERSION = "2.1.0"

[msix]
identity-name = "ExampleSoftware.Hello"
publisher = "CN=Example Software"
publisher-display-name = "Example Software"
min-version = "10.0.17763.0"

[msix-app.Hello]
executable = "Hello"
display-name = "Hello"
description = "Says hello."
logo-150 = "assets/Square150x150.png"
logo-44 = "assets/Square44x44.png"
store-logo = "assets/StoreLogo.png"

[msix-extension.Command]
kind = "alias"
alias = "hello.exe"

[msix-extension.AtSignIn]
kind = "startup-task"
display-name = "Hello"
enabled = false

[msix-extension.Web]
kind = "firewall"
direction = "in"
protocol = "tcp"
ports = "8080"
profile = "private"

[dir.INSTALLDIR]
path = "$(ProgramFiles)/Hello"

[file.Hello]
dir = "INSTALLDIR"
source = "dist/$(ARCH)/hello.exe"

[files.DocFiles]
dir = "INSTALLDIR"
glob = "dist/docs/**"

[shortcut.StartMenu]
dir = "Programs"
name = "Hello"
target = "file:Hello"

[assoc.HelloDoc]
extension = ".hello"
prog-id = "ExampleSoftware.HelloDocument"
description = "Hello document"
target = "file:Hello"

[registry.Greeting]
root = "HKMU"
key = "Software\\Example Software\\Hello"
name = "Greeting"
value = "Hello"

[env.HelloHome]
name = "HELLO_HOME"
value = "[INSTALLDIR]"
msi-only = true

17.4 패키지의 정체: [msix]#

키뜻
identity-nameWindows 가 부르는 패키지 이름. A-Z a-z 0-9 . - 로 된 3~50자이고, 관례는 회사.제품 이다. MSI 의 업그레이드 코드처럼 절대 바꾸지 않는다: 이름과 게시자가 같은 새 판이 옛 판을 대신한다.
publisher서명할 인증서의 주체(subject)를 Windows 가 쓰는 방식 그대로(다음 절)
publisher-display-name사람들이 보는 게시자 이름. 기본값은 [package] manufacturer
min-version설치할 수 있는 가장 오래된 Windows, 10.0.<빌드>.0 꼴. 기본값 10.0.17763.0 은 Windows 10 버전 1809 다

판 번호는 [package] version 에 네 번째 부분을 붙인 것이다: 2.1.0 은 2.1.0.0 이 된다.

17.5 애플리케이션: [msix-app.ID]#

MSIX 는 자기가 담은 애플리케이션 - 시작 메뉴의 항목 - 을 나열한다. executable 은 그것을 시작하는 [file.*] 를 가리키고, 그 파일의 폴더(여기서는 INSTALLDIR)가 패키지 자신의 폴더가 된다. display-name 과 description 의 기본값은 패키지 이름이다.

[msix-app.*] 표가 여럿이면 항목도 여럿이 된다.

여러 언어의 이름, 여러 크기의 로고#

display-name-ko = "헬로"(와 description-ko, 패키지는 [msix] 의 display-name-ko 와 publisher-display-name-ko)는 다른 언어의 이름을 준다. Windows 는 사용자의 언어에 맞는 것을 보이고, 그 밖에서는 접미사 없는 글을 보인다. 로고는 옆에 더 선명한 것을 두면 된다 - Square44x44.png 옆에 Square44x44.scale-200.png(88x88), 원하면 scale-125, -150, -400 도 - 그러면 200% 화면은 그것을 보인다. rubrapack 은 둘 다 패키지의 resources.pri, 곧 Windows 가 이름과 파일을 찾는 색인에 넣고, 패키지에 그런 것이 있을 때만 그것을 쓴다.

17.6 덤: [msix-extension.ID]#

[msix-app.*] 표가 여럿이면 app = "Hello" 로 덤이 어느 애플리케이션의 것인지 밝힌다. 기본값은 첫 번째다. MSI 빌드는 이 표들을 뺀다.

17.7 패키지가 요구하는 것: 능력과 의존 패키지#

MSIX 애플리케이션은 데스크톱 프로그램으로 도는 것 말고 쓰는 것을 Windows 에 요구하고, 필요한 프레임워크 패키지를 적는다:

[msix]
identity-name = "ExampleSoftware.Hello"
publisher = "CN=Example Software, O=Example Software, C=KR"
capabilities = ["internetClient", "allowElevation"]

[msix-dependency.VCLibs]
name = "Microsoft.VCLibs.140.00.UWPDesktop"
publisher = "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
min-version = "14.0.24217.0"

[msix-app.Helper]
executable = "HelperExe"
hidden = true                  # 시작 메뉴 항목 없음
background-color = "#1E3A5F"   # 타일 색

17.8 선택적 패키지와 수정 패키지#

패키지는 다른 패키지에 딸릴 수 있다. 선택적 패키지는 주 패키지에 콘텐츠 - 더 많은 단계, 플러그인 - 를 더하고, 그 패키지가 있는 곳에만 설치된다:

[msix]
identity-name = "ExampleSoftware.HelloExtras"
publisher = "CN=Example Software, O=Example Software, C=KR"
main-package = "ExampleSoftware.Hello"

수정 패키지는 설치된 애플리케이션의 설정 - 기업의 설정 파일과 레지스트리 값 - 을 애플리케이션 자신의 패키지를 건드리지 않고 바꾼다:

[msix]
identity-name = "ExampleSoftware.HelloSiteSettings"
publisher = "CN=Example Software, O=Example Software, C=KR"
main-package = "ExampleSoftware.Hello"
modification = true
min-version = "10.0.18362.0"

둘 다 [msix-app.*] 표가 없어도 된다. 선택적 패키지의 파일은 자기 폴더(dir INSTALLDIR)에, 수정 패키지의 파일은 가상 파일 시스템에 들어가 주 애플리케이션이 본다. 주 패키지의 게시자가 다른 사람이면 main-publisher 로 적는다.

17.9 다른 표들은 무엇이 되나#

msi-only = true 는 표를 MSI 에는 두고 MSIX 에서는 뺀다. MSIX 가 아예 담지 못하는 것 - [require.*], [action.*] - 은 내가 뜻을 밝힐 때까지 빌드를 멈춘다:

C:\work\hello> rubrapack build hello.toml -o hello.msix --unsigned-test
hello.toml:80:1: error[RP1605]: [require.Win81] cannot go into an MSIX; add msi-only = true to build the MSIX without it

rubrapack 은 아무것도 몰래 빼지 않는다.

기능, 속성, 대화창, [arp] 는 Windows Installer 에만 해당하므로 쓰이지 않는다. rubrapack lint hello.toml --target msix 는 빌드하지 않고 원본을 MSIX 기준으로 검사한다.

17.10 시험용 빌드: --unsigned-test#

C:\work\hello> rubrapack build hello.toml -o hello.msix --unsigned-test
C:\work\hello> rubrapack build hello.toml -o hello.msi

Windows 는 패키지가 시험용이라고 밝히고 설치하는 쪽도 그것을 받아들인다고 할 때만 서명 안 된 MSIX 를 설치한다. --unsigned-test 가 그 표시를 더한다(게시자가 바뀌므로 서명한 패키지와는 다른 패키지다). 프로그램이 들어 있으니 관리자 권한 PowerShell 에서 설치한다:

PS C:\work\hello> Add-AppxPackage -Path hello.msix -AllowUnsigned
PS C:\work\hello> hello.exe
PS C:\work\hello> Get-AppxPackage ExampleSoftware.Hello | Remove-AppxPackage

이런 패키지는 내 시험 컴퓨터에서만 쓴다.

17.11 서명하기#

다른 사람이 설치하게 하려면 서명한다(16장). 이때 MSIX 의 publisher 는 인증서의 주체와 정확히 같아야 한다. 다르면 서명이 멈추고 써야 할 주체를 알려 준다:

C:\work\hello> rubrapack build hello.toml -o hello.msix --key signer.pfx --pass-env SIGN_PASS
rubrapack: error[RP0011]: 'hello.msix': the package's publisher "CN=Example Software" is not the certificate's subject "O=Example Software Ltd, CN=Example Software"; set [msix] publisher to it (without --unsigned-test)

그 주체를 publisher 에 옮겨 적는다. Windows 는 주체의 부분들을 마지막 것부터 거꾸로 쓰므로, 어떤 도구가 보여 주는 순서와 다를 수 있다.

프로그램이 든 패키지는 서명하면 Windows 자신의 서명기가 하듯 AppxMetadata\CodeIntegrity.cat - 그 프로그램들의 해시를 같은 키로 서명한 카탈로그 - 도 갖는다. 서명된 코드만 돌 수 있는 곳(S 모드, 응용 프로그램 제어)에서 Windows 의 코드 무결성은 패키지 안의 서명 없는 프로그램을 이것과 대조한다. rubrapack 의 카탈로그는 Windows 자신의 해시와 signtool 로 확인했고, 그런 기기에서는 확인하지 않았다.

17.12 모든 아키텍처를 파일 하나에: 번들#

.msixbundle 로 끝나는 출력은 15장처럼 --arch 의 아키텍처마다 원본을 한 번씩 빌드해 패키지들을 파일 하나에 담는다:

C:\work\hello> rubrapack build hello.toml -o hello.msixbundle --arch x64,x86,arm64 --unsigned-test
C:\work\hello> rubrapack inspect hello.msixbundle --files
AppxMetadata\AppxBundleManifest.xml	1616	deflate
ExampleSoftware.Hello_2.1.0.0_x64.msix	11119	stored
ExampleSoftware.Hello_2.1.0.0_x86.msix	11208	stored
ExampleSoftware.Hello_2.1.0.0_arm64.msix	11119	stored

Windows 는 그중 자기 처리기에 맞는 패키지를 설치한다. --key 를 주면 번들과 그 안의 패키지 모두에 서명한다.

원본에 다른 언어의 이름(display-name-ko 따위)이 있으면 번들은 언어마다 그 언어의 글만 담은 리소스 패키지 ExampleSoftware.Hello_2.1.0.0_language-ko.msix 도 담고, 아키텍처 패키지에는 원본 자신의 언어만 남는다. Windows 는 사용자가 설정한 언어의 리소스 패키지만 설치하므로 보지도 않을 글을 내려받는 사람이 없고, 나중에 Windows 에 더한 언어는 다음 업데이트 때 받아 온다. [msix] 에 language-packs = false 를 두면 모든 언어를 아키텍처 패키지에 그대로 둔다.

17.13 웹 사이트에서 업데이트: .appinstaller#

Windows 는 MSIX 를 올려 둔 웹 사이트(또는 공유 폴더)에서 스스로 최신으로 유지할 수 있다. 패키지를 둘 곳을 적으면 rubrapack 이 그 옆에 App Installer 파일을 쓴다:

[msix]
...
appinstaller-uri = "https://example.com/hello/hello.appinstaller"
package-uri = "https://example.com/hello/hello.msixbundle"
update-hours = 24                 # Windows 가 새 판을 찾는 간격(0: 시작할 때마다)
C:\work\hello> rubrapack build hello.toml -o hello.msixbundle --arch x64,x86,arm64 --key ...

은 hello.msixbundle 과 hello.appinstaller 를 쓴다. 둘을 그 주소에 둔다. 사람들은 .appinstaller 로 설치하고 (열거나 Add-AppxPackage -AppInstallerFile), 그 뒤로 Windows 는 앱이 시작될 때 그 주소를 확인한다. 다음 판을 새 .appinstaller 와 함께 같은 주소에 올리면 스스로 업데이트된다. update-prompt = true 는 사용자에게 먼저 묻고, update-blocks = true 는 업데이트가 끝날 때까지 앱을 기다리게 하며, update-background = true 는 여덟 시간마다 뒤에서도 확인한다. App Installer 는 서명된 패키지만 받는다(16장).

17.14 압축#

파일은 deflate 로 압축되고, 그림처럼 이미 압축된 파일은 그대로 저장된다. --msix-compress store 는 모두 그대로 저장한다. 패키지는 커지지만(여기서는 11119 에서 30072 바이트로) 여는 것은 빨라진다. MSIX 에는 날짜도 시각도 들어 있지 않다: 같은 원본은 어느 컴퓨터에서든 같은 바이트를 낸다.

17.15 안에서 무슨 일이 일어났나#

MSIX 는 ZIP 압축 파일이다. 파일 목록을 본다:

C:\work\hello> rubrapack inspect hello.msix --files
hello.exe	17920	deflate
guide.txt	6	deflate
Assets\Square150x150.png	301	stored
Assets\Square44x44.png	111	stored
Assets\StoreLogo.png	117	stored
Registry.dat	8192	deflate
AppxManifest.xml	2960	deflate

Registry.dat 가 가상 레지스트리 - 레지스트리 하이브 파일 - 다. AppxManifest.xml 은 패키지를 설명하고, --manifest 가 그것을 출력한다:

C:\work\hello> rubrapack inspect hello.msix --manifest
...
  <Identity Name="ExampleSoftware.Hello" Publisher="CN=Example Software, OID.2.25.311729368913984317654407730594956997722=1" Version="2.1.0.0" ProcessorArchitecture="x64" />
...
    <Application Id="Hello" Executable="hello.exe" EntryPoint="Windows.FullTrustApplication">
...
          <uap3:FileTypeAssociation Name="examplesoftware.hellodocument" Parameters="&quot;%1&quot;">
...
          <desktop:StartupTask TaskId="AtSignIn" Enabled="false" DisplayName="Hello" />
...
            <desktop:ExecutionAlias Alias="hello.exe" />
...
    <rescap:Capability Name="runFullTrust" />

게시자의 OID.2.25...=1 부분이 --unsigned-test 가 더한 시험 표시다. runFullTrust 는 프로그램이 스토어 앱의 제한된 샌드박스가 아니라 여느 데스크톱 프로그램처럼 돈다는 뜻이다. --files 가 보이지 않지만 모든 패키지에 두 항목이 더 있다: 모든 파일의 64 KB 블록마다 해시를 적은 AppxBlockMap.xml(서명이 덮는 것이 이것이다)과, 파일마다 형식을 적은 [Content_Types].xml. 서명한 패키지에는 서명인 AppxSignature.p7x 도 있다. 제4부가 ZIP 구조와 블록 맵을 바이트 단위로 보여 준다.