Phase 2: MSIX 패키지 + IExplorerCommand 셸 익스텐션
C++ 셸 익스텐션 (src/EverythingToJpeg.Shell/) - WRL RuntimeClass 패턴으로 IExplorerCommand 두 핸들러 구현 - Quick(빠른 변환) / Dialog(설정 창) verb를 다른 CLSID로 분리 - Invoke()에서 EverythingToJpeg.exe로 verb + 파일 경로 전달 - VS 2026 빌드 검증, /utf-8 한글 라벨 지원 MSIX 패키징 (packaging/) - Package.appxmanifest: com:SurrogateServer + desktop4:FileExplorerContextMenus - 26개 확장자 × 2 verb 자동 노출 - BuildMsix.ps1: dotnet publish + msbuild + makeappx 일관 파이프라인 - CreateDevCert.ps1 / Install-EverythingToJpeg.ps1: 자체 서명 인증서 워크플로 - GenerateAssets.ps1: placeholder 로고 자동 생성 CI/CD - .github/workflows/release.yml: 태그 푸시 시 미서명 MSIX 자동 빌드 + 첨부 검증 - 50MB MSIX 산출 확인 (packaging/dist/EverythingToJpeg-x64.msix) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8fa4a613d7
commit
f2c8610ff6
19 changed files with 973 additions and 41 deletions
138
packaging/BuildMsix.ps1
Normal file
138
packaging/BuildMsix.ps1
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
#Requires -Version 5.1
|
||||
# MSIX 빌드 파이프라인.
|
||||
# 1) .NET App publish (framework-dependent)
|
||||
# 2) C++ Shell DLL 빌드
|
||||
# 3) 패키지 Layout 디렉토리 구성
|
||||
# 4) makeappx pack
|
||||
# 5) (선택) signtool sign
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Configuration = 'Release',
|
||||
[string]$Platform = 'x64',
|
||||
[switch]$Sign,
|
||||
[string]$PfxPath,
|
||||
[securestring]$PfxPassword,
|
||||
[string]$CertThumbprint,
|
||||
[switch]$SelfContained
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$packagingDir = $PSScriptRoot
|
||||
$layoutDir = Join-Path $packagingDir 'Layout'
|
||||
$distDir = Join-Path $packagingDir 'dist'
|
||||
$manifestPath = Join-Path $packagingDir 'Package.appxmanifest'
|
||||
$assetsSrc = Join-Path $packagingDir 'Assets'
|
||||
|
||||
$appProj = Join-Path $repoRoot 'src\EverythingToJpeg.App\EverythingToJpeg.App.csproj'
|
||||
$shellProj = Join-Path $repoRoot 'src\EverythingToJpeg.Shell\EverythingToJpeg.Shell.vcxproj'
|
||||
|
||||
function Find-WindowsSdkTool {
|
||||
param([string]$ToolName)
|
||||
$sdkRoots = @(
|
||||
"${env:ProgramFiles(x86)}\Windows Kits\10\bin",
|
||||
"$env:ProgramFiles\Windows Kits\10\bin"
|
||||
) | Where-Object { Test-Path $_ }
|
||||
foreach ($root in $sdkRoots) {
|
||||
$versions = Get-ChildItem -Path $root -Directory -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Name -match '^\d+\.\d+\.\d+\.\d+$' } |
|
||||
Sort-Object Name -Descending
|
||||
foreach ($v in $versions) {
|
||||
$candidate = Join-Path $v.FullName "x64\$ToolName"
|
||||
if (Test-Path $candidate) { return [string]$candidate }
|
||||
}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
$makeappx = Find-WindowsSdkTool 'makeappx.exe'
|
||||
if (-not $makeappx) { throw 'makeappx.exe를 찾지 못했습니다. Windows 10 SDK가 필요합니다.' }
|
||||
Write-Host "makeappx: $makeappx"
|
||||
|
||||
if ($Sign) {
|
||||
$signtool = Find-WindowsSdkTool 'signtool.exe'
|
||||
if (-not $signtool) { throw 'signtool.exe를 찾지 못했습니다.' }
|
||||
Write-Host "signtool: $signtool"
|
||||
}
|
||||
|
||||
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||
if (-not (Test-Path $vswhere)) { throw 'vswhere.exe를 찾지 못했습니다.' }
|
||||
$msbuild = (& $vswhere -latest -find 'MSBuild\**\Bin\MSBuild.exe' | Select-Object -First 1)
|
||||
if (-not $msbuild) { throw 'MSBuild를 찾지 못했습니다.' }
|
||||
Write-Host "msbuild: $msbuild"
|
||||
|
||||
# ---- 1) .NET App publish ----
|
||||
Write-Host ''
|
||||
Write-Host '[1/5] .NET App publish'
|
||||
$publishOut = Join-Path $repoRoot ('artifacts\publish\app-' + $Platform.ToLower())
|
||||
if (Test-Path $publishOut) { Remove-Item $publishOut -Recurse -Force }
|
||||
$rid = if ($Platform -eq 'ARM64') { 'win-arm64' } else { 'win-x64' }
|
||||
$selfFlag = if ($SelfContained) { 'true' } else { 'false' }
|
||||
& dotnet publish $appProj -c $Configuration -r $rid --self-contained $selfFlag -o $publishOut | Out-Host
|
||||
if ($LASTEXITCODE -ne 0) { throw 'dotnet publish 실패' }
|
||||
|
||||
# ---- 2) C++ Shell DLL ----
|
||||
Write-Host ''
|
||||
Write-Host '[2/5] C++ Shell DLL 빌드'
|
||||
& $msbuild $shellProj /t:Restore /p:RestorePackagesConfig=true /p:Configuration=$Configuration /p:Platform=$Platform /v:minimal | Out-Host
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Shell restore 실패' }
|
||||
& $msbuild $shellProj /p:Configuration=$Configuration /p:Platform=$Platform /m /v:minimal | Out-Host
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Shell build 실패' }
|
||||
$shellDll = Join-Path $repoRoot ("src\EverythingToJpeg.Shell\$Platform\$Configuration\EverythingToJpeg.Shell.dll")
|
||||
if (-not (Test-Path $shellDll)) { throw "Shell DLL 산출물 없음: $shellDll" }
|
||||
|
||||
# ---- 3) Layout 디렉토리 ----
|
||||
Write-Host ''
|
||||
Write-Host '[3/5] Layout 디렉토리 구성'
|
||||
if (Test-Path $layoutDir) { Remove-Item $layoutDir -Recurse -Force }
|
||||
New-Item -ItemType Directory -Path $layoutDir | Out-Null
|
||||
|
||||
Copy-Item -Path (Join-Path $publishOut '*') -Destination $layoutDir -Recurse -Force
|
||||
Copy-Item -Path $shellDll -Destination $layoutDir -Force
|
||||
|
||||
$layoutAssets = Join-Path $layoutDir 'Assets'
|
||||
New-Item -ItemType Directory -Path $layoutAssets -Force | Out-Null
|
||||
Copy-Item -Path (Join-Path $assetsSrc '*') -Destination $layoutAssets -Force
|
||||
|
||||
Copy-Item -Path $manifestPath -Destination (Join-Path $layoutDir 'AppxManifest.xml') -Force
|
||||
|
||||
# ---- 4) makeappx pack ----
|
||||
Write-Host ''
|
||||
Write-Host '[4/5] makeappx pack'
|
||||
if (-not (Test-Path $distDir)) { New-Item -ItemType Directory -Path $distDir | Out-Null }
|
||||
$msixPath = Join-Path $distDir ("EverythingToJpeg-$($Platform.ToLower()).msix")
|
||||
if (Test-Path $msixPath) { Remove-Item $msixPath -Force }
|
||||
& $makeappx pack /d $layoutDir /p $msixPath /o | Out-Host
|
||||
if ($LASTEXITCODE -ne 0) { throw 'makeappx pack 실패' }
|
||||
Write-Host "✅ MSIX 산출: $msixPath"
|
||||
|
||||
# ---- 5) (선택) sign ----
|
||||
if ($Sign) {
|
||||
Write-Host ''
|
||||
Write-Host '[5/5] signtool sign'
|
||||
if ($CertThumbprint) {
|
||||
& $signtool sign /fd SHA256 /sha1 $CertThumbprint /tr 'http://timestamp.digicert.com' /td SHA256 $msixPath | Out-Host
|
||||
} elseif ($PfxPath) {
|
||||
if (-not $PfxPassword) {
|
||||
$PfxPassword = Read-Host -AsSecureString -Prompt 'PFX 비밀번호'
|
||||
}
|
||||
$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($PfxPassword)
|
||||
try {
|
||||
$plain = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)
|
||||
& $signtool sign /fd SHA256 /a /f $PfxPath /p $plain /tr 'http://timestamp.digicert.com' /td SHA256 $msixPath | Out-Host
|
||||
} finally {
|
||||
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
|
||||
}
|
||||
} else {
|
||||
throw '서명을 하려면 -CertThumbprint 또는 -PfxPath 가 필요합니다.'
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) { throw 'signtool sign 실패' }
|
||||
Write-Host '✅ 서명 완료'
|
||||
} else {
|
||||
Write-Host ''
|
||||
Write-Host '[5/5] 서명 건너뜀 (-Sign 미지정). 사이드로드 시 인증서 필요.'
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host "최종 산출물: $msixPath"
|
||||
Loading…
Add table
Add a link
Reference in a new issue