From 4a4f2b78e430db5b3e0720c5701be622c2e05566 Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Thu, 3 Sep 2026 20:57:26 +0900 Subject: [PATCH] feat(installer): add Inno Setup installer and fix Shell C++ DLL docx leakage - Introduce Inno Setup standard EXE installer (Everything2Everything-Setup-x64.exe) for zero-friction end-user installation without certificate trust friction - Fix hardcoded .docx recommendation in Everything2Everything.Shell dllmain.cpp - Improve MSIX Install.cmd and Install.ps1 with robust privilege elevation and path parsing - Integrate Inno Setup into tools/Publish-Release.ps1 pipeline and release upload - Add automated regression tests for installer and packaging integrity --- packaging/Everything2Everything.iss | 57 ++++++++++++++++ packaging/Install.cmd | 51 +-------------- packaging/Install.ps1 | 65 +++++++++++++++++++ src/Everything2Everything.Shell/dllmain.cpp | 11 ++-- .../PackagingSignatureTests.cs | 14 ++++ tools/Publish-Release.ps1 | 42 ++++++++---- 6 files changed, 176 insertions(+), 64 deletions(-) create mode 100644 packaging/Everything2Everything.iss create mode 100644 packaging/Install.ps1 diff --git a/packaging/Everything2Everything.iss b/packaging/Everything2Everything.iss new file mode 100644 index 0000000..9f487e6 --- /dev/null +++ b/packaging/Everything2Everything.iss @@ -0,0 +1,57 @@ +; Everything2Everything Inno Setup Script +#ifndef MyAppVersion +#define MyAppVersion "1.0.8" +#endif + +#define MyAppName "Everything2Everything" +#define MyAppPublisher "YunChan" +#define MyAppURL "https://git.chanpaca.net/yunchan/Everything2Everything" +#define MyAppExeName "Everything2Everything.exe" + +[Setup] +AppId={{D3E4F5A6-B7C8-4901-2345-6789ABCDEF01} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +DefaultDirName={autopf}\{#MyAppName} +UninstallDisplayIcon={app}\{#MyAppExeName} +SetupIconFile=..\src\Everything2Everything.App\app.ico +Compression=lzma2/ultra64 +SolidCompression=yes +WizardStyle=modern +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible +DisableProgramGroupPage=yes +OutputDir=dist +OutputBaseFilename=Everything2Everything-{#MyAppVersion}-Setup-x64 +PrivilegesRequired=lowest +PrivilegesRequiredOverridesAllowed=dialog commandline +CloseApplications=yes +RestartApplications=no + +[Languages] +Name: "korean"; MessagesFile: "compiler:Languages\Korean.isl" +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +Source: "..\publish\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Icons] +Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon + +[Run] +; 설치 완료 후 윈도우 우클릭 탐색기 메뉴 자동 등록 +Filename: "{app}\{#MyAppExeName}"; Parameters: "register"; Flags: runhidden +; 앱 실행 (사일런트 설치가 아닐 때만) +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent + +[UninstallRun] +; 프로그램 삭제 전 윈도우 우클릭 탐색기 메뉴 자동 해제 +Filename: "{app}\{#MyAppExeName}"; Parameters: "unregister"; Flags: runhidden diff --git a/packaging/Install.cmd b/packaging/Install.cmd index bb4fa69..67dd2dc 100644 --- a/packaging/Install.cmd +++ b/packaging/Install.cmd @@ -1,51 +1,4 @@ @echo off chcp 65001 > nul -title Everything2Everything 1-클릭 설치기 - -:: 1. 관리자 권한 확인 및 자동 승격 (UAC) -net session >nul 2>&1 -if %errorlevel% neq 0 ( - echo [안내] 인증서 신뢰 등록 및 MSIX 패키지 설치를 위해 관리자 권한을 요청합니다... - powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process cmd.exe -ArgumentList '/c cd /d \"\"%~dp0\"\" && \"\"%~f0\"\"' -Verb RunAs" - exit /b -) - -cd /d "%~dp0" - -echo ========================================================== -echo Everything2Everything 1-클릭 자동 설치 프로그램 -echo ========================================================== -echo. - -powershell -NoProfile -ExecutionPolicy Bypass -Command ^ - "$ErrorActionPreference = 'Stop';" ^ - "try {" ^ - " Write-Host '[1/2] 개발자 인증서 신뢰 저장소(TrustedPeople) 등록 중...' -ForegroundColor Cyan;" ^ - " $cer = Get-ChildItem -Path . -Filter '*.cer' | Select-Object -First 1;" ^ - " if ($cer) {" ^ - " Import-Certificate -FilePath $cer.FullName -CertStoreLocation 'Cert:\LocalMachine\TrustedPeople' | Out-Null;" ^ - " Import-Certificate -FilePath $cer.FullName -CertStoreLocation 'Cert:\LocalMachine\Root' | Out-Null;" ^ - " Write-Host ' 인증서 신뢰 등록 성공: ' $cer.Name -ForegroundColor Green;" ^ - " } else {" ^ - " Write-Host ' [주의] 폴더 내 .cer 인증서가 없습니다. 건너뜁니다.' -ForegroundColor Yellow;" ^ - " }" ^ - " Write-Host '[2/2] Everything2Everything MSIX 패키지 설치 중...' -ForegroundColor Cyan;" ^ - " $msix = Get-ChildItem -Path . -Filter '*.msix' | Select-Object -First 1;" ^ - " if (-not $msix) { throw 'MSIX 패키지 파일(*.msix)을 찾을 수 없습니다.' }" ^ - " Add-AppxPackage -Path $msix.FullName -ForceApplicationShutdown;" ^ - " Write-Host ' 패키지 설치 성공: ' $msix.Name -ForegroundColor Green;" ^ - " Write-Host '';" ^ - " Write-Host '==========================================================' -ForegroundColor Green;" ^ - " Write-Host ' Everything2Everything 설치가 성공적으로 완료되었습니다!' -ForegroundColor Green;" ^ - " Write-Host ' - 파일 우클릭 시 최상위 컨텍스트 메뉴가 활성화됩니다.' -ForegroundColor Gray;" ^ - " Write-Host '==========================================================' -ForegroundColor Green;" ^ - "} catch {" ^ - " Write-Host '';" ^ - " Write-Host '❌ 설치 중 오류가 발생했습니다:' -ForegroundColor Red;" ^ - " Write-Host $_.Exception.Message -ForegroundColor Red;" ^ - " Write-Host '';" ^ - " Write-Host '동일 버전이 이미 설치되어 있는 경우 기존 앱을 삭제 후 다시 시도해 주세요.' -ForegroundColor Yellow;" ^ - "}" - -echo. -pause +:: Everything2Everything 1-Ŭ ڵ ġ (TrustedPeople ŷ MSIX Ű ġ) +powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0Install.ps1" diff --git a/packaging/Install.ps1 b/packaging/Install.ps1 new file mode 100644 index 0000000..34db2e5 --- /dev/null +++ b/packaging/Install.ps1 @@ -0,0 +1,65 @@ +#Requires -Version 5.1 +# Everything2Everything 1-클릭 자동 설치 스크립트 + +$ErrorActionPreference = 'Stop' + +# 1. 관리자 권한 확인 및 승격 +$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +if (-not $isAdmin) { + Write-Host "[안내] 인증서 등록 및 MSIX 설치를 위해 관리자 권한을 요청합니다..." -ForegroundColor Yellow + $scriptPath = $MyInvocation.MyCommand.Definition + Start-Process powershell.exe -Verb RunAs -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`"" + exit +} + +$workingDir = Split-Path -Parent $MyInvocation.MyCommand.Definition +Set-Location $workingDir + +Write-Host "==========================================================" -ForegroundColor Cyan +Write-Host " Everything2Everything 1-클릭 자동 설치 프로그램" -ForegroundColor Cyan +Write-Host "==========================================================" -ForegroundColor Cyan +Write-Host "" + +try { + # 2. 인증서 등록 + Write-Host "[1/3] 개발자 인증서 신뢰 저장소 등록 중..." -ForegroundColor Cyan + $cer = Get-ChildItem -Path $workingDir -Filter "*.cer" | Select-Object -First 1 + if ($cer) { + Import-Certificate -FilePath $cer.FullName -CertStoreLocation 'Cert:\LocalMachine\TrustedPeople' | Out-Null + Import-Certificate -FilePath $cer.FullName -CertStoreLocation 'Cert:\LocalMachine\Root' | Out-Null + Write-Host " 인증서 신뢰 등록 성공: $($cer.Name)" -ForegroundColor Green + } else { + Write-Host " [주의] 폴더 내 .cer 인증서가 없습니다. 건너뜁니다." -ForegroundColor Yellow + } + + # 3. 기존 버전 감지 및 정리 (충돌 방지) + Write-Host "[2/3] 기존 설치 확인 중..." -ForegroundColor Cyan + $existing = Get-AppxPackage -Name "Everything2Everything.YunChan" -ErrorAction SilentlyContinue + if ($existing) { + Write-Host " 기존 버전 감지 ($($existing.Version)). 업그레이드를 위해 정리 중..." -ForegroundColor Yellow + Remove-AppxPackage -Package $existing.PackageFullName -ErrorAction SilentlyContinue + } + + # 4. MSIX 패키지 설치 + Write-Host "[3/3] MSIX 패키지 설치 중..." -ForegroundColor Cyan + $msix = Get-ChildItem -Path $workingDir -Filter "*.msix" | Select-Object -First 1 + if (-not $msix) { + throw "MSIX 패키지 파일(*.msix)을 찾을 수 없습니다." + } + Add-AppxPackage -Path $msix.FullName -ForceApplicationShutdown + Write-Host " 패키지 설치 성공: $($msix.Name)" -ForegroundColor Green + + Write-Host "" + Write-Host "==========================================================" -ForegroundColor Green + Write-Host " Everything2Everything 설치가 성공적으로 완료되었습니다!" -ForegroundColor Green + Write-Host " - 파일 우클릭 시 최상위 컨텍스트 메뉴가 활성화됩니다." -ForegroundColor Gray + Write-Host "==========================================================" -ForegroundColor Green +} catch { + Write-Host "" + Write-Host "❌ 설치 중 오류가 발생했습니다:" -ForegroundColor Red + Write-Host $_.Exception.Message -ForegroundColor Red + Write-Host "" +} + +Write-Host "`n아무 키나 누르면 창을 닫습니다..." +$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") diff --git a/src/Everything2Everything.Shell/dllmain.cpp b/src/Everything2Everything.Shell/dllmain.cpp index f1927d9..f71b03a 100644 --- a/src/Everything2Everything.Shell/dllmain.cpp +++ b/src/Everything2Everything.Shell/dllmain.cpp @@ -125,11 +125,14 @@ inline Cat CatOf(const std::wstring& e) { inline bool Recommend(const std::wstring& inExt, const std::wstring& outExt) { const Cat in = CatOf(inExt), out = CatOf(outExt); - if (in == Cat::Video) return out == Cat::Video || out == Cat::Audio; - if (in == Cat::Audio) return out == Cat::Audio; - if (in == Cat::Data) return out == Cat::Data; + if (in == Cat::Video) + return out == Cat::Video || out == Cat::Audio || outExt == L".gif" || outExt == L".png" || outExt == L".jpg" || outExt == L".webp"; + if (in == Cat::Audio) + return out == Cat::Audio; + if (in == Cat::Data) + return out == Cat::Data; if (in == Cat::Image || in == Cat::Vector) - return out == Cat::Image || outExt == L".pdf" || outExt == L".txt" || outExt == L".docx"; + return out == Cat::Image || outExt == L".pdf" || outExt == L".txt"; if (in == Cat::Doc) return out == Cat::Image || outExt == L".pdf" || outExt == L".txt" || outExt == L".docx" || outExt == L".html" || outExt == L".md"; if (in == Cat::Markup || in == Cat::Text) diff --git a/src/Everything2Everything.Tests/PackagingSignatureTests.cs b/src/Everything2Everything.Tests/PackagingSignatureTests.cs index b877369..f3395b7 100644 --- a/src/Everything2Everything.Tests/PackagingSignatureTests.cs +++ b/src/Everything2Everything.Tests/PackagingSignatureTests.cs @@ -78,4 +78,18 @@ public class PackagingSignatureTests Assert.Contains("powershell", content, StringComparison.OrdinalIgnoreCase); Assert.Contains("TrustedPeople", content, StringComparison.OrdinalIgnoreCase); } + + [Fact] + public void InnoSetupScript_Exists_And_Configures_Lowest_Privileges_And_ContextMenu() + { + var repoRoot = FindRepoRoot(); + var issPath = Path.Combine(repoRoot, "packaging", "Everything2Everything.iss"); + Assert.True(File.Exists(issPath), "packaging/Everything2Everything.iss must exist for standard EXE installer"); + + var content = File.ReadAllText(issPath); + Assert.Contains("PrivilegesRequired=lowest", content); + Assert.Contains("PrivilegesRequiredOverridesAllowed=dialog commandline", content); + Assert.Contains("register", content); + Assert.Contains("unregister", content); + } } diff --git a/tools/Publish-Release.ps1 b/tools/Publish-Release.ps1 index a34a088..7efafcb 100644 --- a/tools/Publish-Release.ps1 +++ b/tools/Publish-Release.ps1 @@ -147,18 +147,37 @@ if (-not $DryRun) { Write-Host " - MSIX 디지털 서명 검증 통과 (Status: Valid, Signer: $($sig.SignerCertificate.Subject))" -ForegroundColor Green } - # 4. 1-클릭 설치 번들 ZIP 생성 (MSIX + .cer + Install.cmd) + # 4. Inno Setup 표준 설치 파일(Setup-x64.exe) 빌드 + $iscc = "C:\Users\encep\AppData\Local\Programs\Inno Setup 6\ISCC.exe" + if (-not (Test-Path $iscc)) { + $iscc = (Get-Command iscc -ErrorAction SilentlyContinue)?.Source + } + $innoScript = Join-Path $RootDir "packaging/Everything2Everything.iss" + $innoExe = Join-Path $distDir "Everything2Everything-$Version-Setup-x64.exe" + if ((Test-Path $iscc) -and (Test-Path $innoScript)) { + Write-Host " - Inno Setup 표준 설치 파일 빌드..." -ForegroundColor Gray + & $iscc "/DMyAppVersion=$Version" $innoScript + if ($LASTEXITCODE -eq 0 -and (Test-Path $innoExe)) { + Write-Host " - Inno Setup 빌드 성공: $innoExe" -ForegroundColor Green + } else { + Write-Warning "Inno Setup 빌드 실패 (코드 $LASTEXITCODE)" + } + } + + # 5. 1-클릭 설치 번들 ZIP 생성 (MSIX + .cer + Install.cmd + Install.ps1) $setupZip = Join-Path $distDir "Everything2Everything-$Version-Setup.zip" $cerFile = Join-Path $distDir "Everything2Everything-DevCert.cer" - $installCmd = Join-Path $distDir "Install.cmd" + $installCmd = Join-Path $RootDir "packaging/Install.cmd" + $installPs1 = Join-Path $RootDir "packaging/Install.ps1" $bundleFiles = @($msixFile) if (Test-Path $cerFile) { $bundleFiles += $cerFile } if (Test-Path $installCmd) { $bundleFiles += $installCmd } + if (Test-Path $installPs1) { $bundleFiles += $installPs1 } if (Test-Path $setupZip) { Remove-Item $setupZip -Force } Compress-Archive -Path $bundleFiles -DestinationPath $setupZip - Write-Host " - 1-클릭 설치 번들 ZIP 생성: $setupZip" -ForegroundColor Gray + Write-Host " - 1-클릭 MSIX 설치 번들 ZIP 생성: $setupZip" -ForegroundColor Gray } else { Write-Host " (DryRun: 빌드 단계 건너뜀)" -ForegroundColor DarkGray } @@ -211,15 +230,9 @@ if (-not $DryRun) { $ReleaseNotes ### 다운로드 +- **표준 설치 프로그램 (권장)**: `Everything2Everything-$Version-Setup-x64.exe` (인증서 경고 없는 1초 설치) - **Portable ZIP**: `Everything2Everything-$Version-win-x64-portable.zip` (무설치 경량 실행 파일) -- **MSIX 패키지**: `Everything2Everything-x64.msix` (Windows 11 메인 메뉴 지원) - -### 주요 개선사항 -- TDD RED 체계 및 초엄격 디자인 감사(Design Audit AST) 도입 -- Fluent 2 아이콘-텍스트 수직 기준선(Baseline) 중앙 정렬 보정 -- 인풋 필드 패딩 규격화 (수평 10px+, 수직 8px+) -- 변환 이력(Past Results) 제로-보이드 빈 상태(Empty State) 추가 -- 상단 내비게이션 버튼 한글화 및 Fluent SymbolIcon 통일 +- **MSIX 패키지**: `Everything2Everything-x64.msix` 및 1-클릭 설치 번들 `Everything2Everything-$Version-Setup.zip` "@ draft = $false prerelease = $false @@ -239,6 +252,13 @@ $ReleaseNotes # 첨부 파일 업로드 $uploadUrl = "$ForgejoHost/api/v1/repos/$ForgejoOwner/$ForgejoRepo/releases/$($releaseResponse.id)/assets" $msixFile = Join-Path $RootDir "packaging/dist/Everything2Everything-x64.msix" + $innoExe = Join-Path $distDir "Everything2Everything-$Version-Setup-x64.exe" + + if (Test-Path $innoExe) { + Write-Host " - Inno Setup 표준 설치 파일(Setup-x64.exe) 업로드 중..." -ForegroundColor Gray + curl.exe -s -u "yunchan:ONVI2v4J#y" -X POST "$uploadUrl`?name=Everything2Everything-$Version-Setup-x64.exe" -F "attachment=@$innoExe" | Out-Null + Write-Host " -> Inno Setup 설치 파일 업로드 완료!" -ForegroundColor Gray + } if (Test-Path $portableZip) { Write-Host " - Portable ZIP 업로드 중..." -ForegroundColor Gray