<# .SYNOPSIS Newtil Essential Installer - Core Installation Engine .DESCRIPTION 윈도우 포맷/재설치 후 선택된 필수 소프트웨어를 winget 및 제조사 공식 CDN을 통해 순차적으로 무인(Silent) 자동 설치하는 Newtil 코어 엔진입니다. #> [CmdletBinding()] param( [Parameter(Mandatory=$false)] [string]$Packages = "Google.Chrome,Kakao.KakaoTalk,Bandisoft.Bandizip,voidtools.Everything", [Parameter(Mandatory=$false)] [switch]$Unattended, [Parameter(Mandatory=$false)] [string]$ConfigUrl = "https://newtil.com/essential-installer/config/packages.json" ) # 1. 관리자 권한 확인 및 자동 상승 $IsAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $IsAdmin) { Write-Host "[Newtil] 관리자 권한이 필요합니다. UAC 승인 창을 띄웁니다..." -ForegroundColor Yellow $scriptPath = $MyInvocation.MyCommand.Path if ($scriptPath -and (Test-Path $scriptPath)) { $CommandLine = "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`"" if ($Packages) { $CommandLine += " -Packages `"$Packages`"" } if ($Unattended) { $CommandLine += " -Unattended" } } else { # 스트리밍 방식(irm | iex)으로 호출된 경우 인메모리 명령어로 UAC 상승 $pkgParam = if ($Packages) { "?p=$Packages" } else { "" } $CommandLine = "-NoProfile -ExecutionPolicy Bypass -Command `"& { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; `$code = (Invoke-RestMethod -Uri 'https://newtil.com/install$pkgParam' -UseBasicParsing); Invoke-Expression `$code }`"" } Start-Process powershell.exe -Verb RunAs -ArgumentList $CommandLine exit } # 2. 콘솔 환경 및 인코딩 설정 [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 $Host.UI.RawUI.WindowTitle = "Newtil Essential Installer - 필수 프로그램 자동 설치" $LogDir = Join-Path $env:LOCALAPPDATA "NewtilInstaller" if (-not (Test-Path $LogDir)) { New-Item -ItemType Directory -Force -Path $LogDir | Out-Null } $LogFile = Join-Path $LogDir "install_log.txt" function Write-NewtilLog { param([string]$Message, [string]$Color = "White") $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $LogLine = "[$Timestamp] $Message" Add-Content -Path $LogFile -Value $LogLine -Encoding UTF8 Write-Host $Message -ForegroundColor $Color } Clear-Host Write-Host "================================================================" -ForegroundColor Cyan Write-Host " Newtil 윈도우 필수 프로그램 무인 자동 설치기 " -ForegroundColor Cyan Write-Host " https://newtil.com/essential-installer " -ForegroundColor Gray Write-Host "================================================================" -ForegroundColor Cyan Write-Host "" Write-NewtilLog "Newtil Essential Installer 세션을 시작합니다." "Green" Write-NewtilLog "로그 파일 위치: $LogFile" "DarkGray" # 3. 필수 패키지 정의 사전 (오프라인/단독 실행 대비 내장 데이터) $BuiltInCatalog = @{ "Google.Chrome" = @{ Name="구글 크롬"; Type="direct_msi"; Url="https://dl.google.com/dl/chrome/install/googlechromestandaloneenterprise64.msi"; SilentArgs="/qn /norestart"; WingetId="Google.Chrome" } "NAVER.Whale" = @{ Name="네이버 웨일"; Type="direct_exe"; Url="https://installer-whale.pstatic.net/downloads/sa_installers/WhaleSetupX64.exe"; SilentArgs=""; Interactive=$true; WingetId="NAVER.Whale" } "Brave.Brave" = @{ Name="브레이브"; Type="winget"; WingetId="Brave.Brave" } "Mozilla.Firefox" = @{ Name="파이어폭스"; Type="winget"; WingetId="Mozilla.Firefox" } "Microsoft.Edge" = @{ Name="마이크로소프트 엣지"; Type="winget"; WingetId="Microsoft.Edge" } "Perplexity.Comet" = @{ Name="Comet (Perplexity)"; Type="direct_exe"; Url="https://www.perplexity.ai/rest/browser/download?platform=win_x64&channel=stable"; SilentArgs="--install --silent"; WingetId="Perplexity.Comet" } "Kakao.KakaoTalk" = @{ Name="카카오톡"; Type="direct_exe"; Url="https://app-pc.kakaocdn.net/talk/win32/x64/KakaoTalk_Setup.exe"; SilentArgs="/S"; WingetId="Kakao.KakaoTalk" } "Discord.Discord" = @{ Name="디스코드"; Type="winget"; WingetId="Discord.Discord" } "Telegram.TelegramDesktop" = @{ Name="텔레그램"; Type="winget"; WingetId="Telegram.TelegramDesktop" } "Zoom.Zoom" = @{ Name="Zoom"; Type="winget"; WingetId="Zoom.Zoom" } "SlackTechnologies.Slack" = @{ Name="Slack"; Type="winget"; WingetId="SlackTechnologies.Slack" } "Bandisoft.Bandizip" = @{ Name="반디집"; Type="winget"; WingetId="Bandisoft.Bandizip"; FallbackUrl="https://kr.bandisoft.com/bandizip/dl.php?web"; FallbackArgs="/S" } "7zip.7zip" = @{ Name="7-Zip"; Type="winget"; WingetId="7zip.7zip" } "RARLab.WinRAR" = @{ Name="WinRAR"; Type="winget"; WingetId="RARLab.WinRAR" } "voidtools.Everything" = @{ Name="Everything"; Type="winget"; WingetId="voidtools.Everything" } "Google.QuickShare" = @{ Name="Quick Share"; Type="winget"; WingetId="Google.QuickShare" } "9PM860492SZD" = @{ Name="Microsoft PC Manager"; Type="msstore"; WingetId="9PM860492SZD" } "Daum.PotPlayer" = @{ Name="팟플레이어 (64-bit)"; Type="winget"; WingetId="Daum.PotPlayer" } "VideoLAN.VLC" = @{ Name="VLC Media Player"; Type="winget"; WingetId="VideoLAN.VLC" } "Bandisoft.Honeyview" = @{ Name="꿀뷰"; Type="direct_exe"; Url="https://bandisoft.app/honeyview/HONEYVIEW-SETUP.EXE"; SilentArgs="/S"; WingetId="Bandisoft.Honeyview" } "Bandisoft.BandiView" = @{ Name="반디뷰"; Type="winget"; WingetId="Bandisoft.BandiView" } "TheDocumentFoundation.LibreOffice" = @{ Name="리브레오피스 (LibreOffice)"; Type="winget"; WingetId="TheDocumentFoundation.LibreOffice" } "SumatraPDF.SumatraPDF" = @{ Name="수마트라 PDF (Sumatra PDF)"; Type="winget"; WingetId="SumatraPDF.SumatraPDF" } "geeksoftwareGmbH.PDF24-Creator" = @{ Name="PDF24 Creator"; Type="winget"; WingetId="geeksoftwareGmbH.PDF24-Creator" } "Adobe.Acrobat.Reader.64-bit" = @{ Name="Adobe Acrobat Reader"; Type="winget"; WingetId="Adobe.Acrobat.Reader.64-bit" } "Hancom.HancomOfficeViewer" = @{ Name="한컴오피스 2024 뷰어"; Type="winget"; WingetId="Hancom.HancomOfficeViewer" } "ShareX.ShareX" = @{ Name="ShareX"; Type="winget"; WingetId="ShareX.ShareX" } "Greenshot.Greenshot" = @{ Name="Greenshot"; Type="winget"; WingetId="Greenshot.Greenshot" } "BlenderFoundation.Blender" = @{ Name="블렌더 (Blender)"; Type="winget"; WingetId="BlenderFoundation.Blender" } "Microsoft.PowerToys" = @{ Name="Microsoft PowerToys"; Type="winget"; WingetId="Microsoft.PowerToys" } "AntibodySoftware.WizTree" = @{ Name="WizTree"; Type="winget"; WingetId="AntibodySoftware.WizTree" } "Revouninstaller.Revouninstaller" = @{ Name="Revo Uninstaller"; Type="winget"; WingetId="Revouninstaller.Revouninstaller" } "RustDesk.RustDesk" = @{ Name="RustDesk"; Type="winget"; WingetId="RustDesk.RustDesk" } "AnyDeskSoftwareGmbH.AnyDesk" = @{ Name="AnyDesk"; Type="winget"; WingetId="AnyDeskSoftwareGmbH.AnyDesk" } "Bitwarden.Bitwarden" = @{ Name="비트워든 (Bitwarden)"; Type="winget"; WingetId="Bitwarden.Bitwarden" } "Tailscale.Tailscale" = @{ Name="테일스케일 (Tailscale)"; Type="winget"; WingetId="Tailscale.Tailscale" } "Google.GoogleDrive" = @{ Name="Google Drive for Desktop"; Type="winget"; WingetId="Google.GoogleDrive" } "Dropbox.Dropbox" = @{ Name="Dropbox"; Type="winget"; WingetId="Dropbox.Dropbox" } "OBSProject.OBSStudio" = @{ Name="OBS Studio"; Type="winget"; WingetId="OBSProject.OBSStudio" } "HandBrake.HandBrake" = @{ Name="HandBrake"; Type="winget"; WingetId="HandBrake.HandBrake" } "Anysphere.Cursor" = @{ Name="커서 (Cursor)"; Type="winget"; WingetId="Anysphere.Cursor" } "GitHub.GitHubDesktop" = @{ Name="GitHub Desktop"; Type="winget"; WingetId="GitHub.GitHubDesktop" } "Docker.DockerDesktop" = @{ Name="Docker Desktop"; Type="winget"; WingetId="Docker.DockerDesktop" } "WinMerge.WinMerge" = @{ Name="WinMerge"; Type="winget"; WingetId="WinMerge.WinMerge" } "TimKosse.FileZilla.Client" = @{ Name="FileZilla"; Type="winget"; WingetId="TimKosse.FileZilla.Client" } "Microsoft.VCRedist.2015+.x64" = @{ Name="Visual C++ 2015-2022 All-in-One"; Type="winget"; WingetId="Microsoft.VCRedist.2015+.x64" } "KakaoEntertainmentCorp.MelonPlayer" = @{ Name="멜론"; Type="direct_exe"; Url="https://cdnimg.melon.co.kr/svc/pcp/apps/MelonSetup.exe"; SilentArgs="/S"; WingetId="KakaoEntertainmentCorp.MelonPlayer" } "NHNBugs.Bugs" = @{ Name="벅스"; Type="direct_exe"; Url="https://dl.bugsm.co.kr/BugsPlayer4/BugsSetup.exe"; SilentArgs="/S"; WingetId="NHNBugs.Bugs" } "Spotify.Spotify" = @{ Name="스포티파이"; Type="winget"; WingetId="Spotify.Spotify" } "PeterPawlowski.foobar2000" = @{ Name="foobar2000"; Type="winget"; WingetId="PeterPawlowski.foobar2000" } "AIMP.AIMP" = @{ Name="AIMP"; Type="direct_exe"; Url="https://aimp.ru/?do=download.file&id=3"; SilentArgs="/AUTO /SILENT"; WingetId="AIMP.AIMP" } "Notion.Notion" = @{ Name="노션 (Notion)"; Type="winget"; WingetId="Notion.Notion" } "Obsidian.Obsidian" = @{ Name="옵시디언 (Obsidian)"; Type="winget"; WingetId="Obsidian.Obsidian" } "Anthropic.Claude" = @{ Name="Claude Desktop"; Type="winget"; WingetId="Anthropic.Claude" } "9PLM9XGG6VKS" = @{ Name="ChatGPT / Codex"; Type="msstore"; WingetId="9PLM9XGG6VKS" } "Google.GeminiDesktop" = @{ Name="Gemini Desktop"; Type="winget"; WingetId="Google.GeminiDesktop" } "Microsoft.VisualStudioCode" = @{ Name="VS Code"; Type="winget"; WingetId="Microsoft.VisualStudioCode" } "Google.Antigravity" = @{ Name="안티그래비티 (Antigravity)"; Type="script"; WingetId="Google.Antigravity" } "Git.Git" = @{ Name="Git"; Type="winget"; WingetId="Git.Git" } "Python.Python.3.14" = @{ Name="Python 3.14"; Type="winget"; WingetId="Python.Python.3.14" } "OpenJS.NodeJS.LTS" = @{ Name="Node.js (LTS)"; Type="winget"; WingetId="OpenJS.NodeJS.LTS" } "EclipseAdoptium.Temurin.25.JDK" = @{ Name="Java JDK (Temurin 25)"; Type="winget"; WingetId="EclipseAdoptium.Temurin.25.JDK" } "Notepad++.Notepad++" = @{ Name="Notepad++"; Type="winget"; WingetId="Notepad++.Notepad++" } "dbeaver.dbeaver" = @{ Name="DBeaver CE"; Type="winget"; WingetId="dbeaver.dbeaver" } "Valve.Steam" = @{ Name="스팀 (Steam)"; Type="winget"; WingetId="Valve.Steam" } "Blizzard.BattleNet" = @{ Name="배틀넷 (Battle.net)"; Type="winget"; WingetId="Blizzard.BattleNet"; Interactive=$true } "EpicGames.EpicGamesLauncher" = @{ Name="에픽게임즈 런처"; Type="winget"; WingetId="EpicGames.EpicGamesLauncher" } } # 4. winget 설치 상태 점검 $HasWinget = (Get-Command "winget.exe" -ErrorAction SilentlyContinue) -ne $null if (-not $HasWinget) { Write-NewtilLog "[주의] 시스템에 winget(앱 설치 관리자)이 감지되지 않았습니다." "Yellow" Write-NewtilLog "winget 미지원 항목은 제조사 공식 CDN 직접 다운로드로 대체 진행합니다." "Yellow" } # 5. 설치 대상 목록 결정 $InstallList = @() if ($Packages) { $RawIds = $Packages -split '[,;|\s]+' | Where-Object { $_ -ne "" } foreach ($r in $RawIds) { $foundKey = $BuiltInCatalog.Keys | Where-Object { $_ -eq $r -or $_ -like "*$r*" -or $BuiltInCatalog[$_].Name -like "*$r*" } | Select-Object -First 1 if ($foundKey) { $item = $BuiltInCatalog[$foundKey] $item.Id = $foundKey $InstallList += $item } else { # 카탈로그에 없더라도 winget ID로 직접 처리 시도 $InstallList += @{ Id=$r; Name=$r; Type="winget"; WingetId=$r } } } } if ($InstallList.Count -eq 0) { # 기본 프리셋: 미니멀 필수 세트 (크롬, 카톡, 반디집, Everything) Write-NewtilLog "선택된 패키지가 없어 기본 추천 세트(크롬, 카톡, 반디집, Everything)를 로드합니다." "Cyan" $InstallList = @( $BuiltInCatalog["Google.Chrome"] + @{ Id="Google.Chrome" }, $BuiltInCatalog["Kakao.KakaoTalk"] + @{ Id="Kakao.KakaoTalk" }, $BuiltInCatalog["Bandisoft.Bandizip"] + @{ Id="Bandisoft.Bandizip" }, $BuiltInCatalog["voidtools.Everything"] + @{ Id="voidtools.Everything" } ) } Write-Host "----------------------------------------------------------------" -ForegroundColor Gray Write-Host "총 $($InstallList.Count)개 프로그램이 설치 대기열에 등록되었습니다:" -ForegroundColor Green $idx = 1 foreach ($app in $InstallList) { Write-Host " $idx. $($app.Name) ($($app.Id))" -ForegroundColor White $idx++ } Write-Host "----------------------------------------------------------------" -ForegroundColor Gray Write-Host "" $SuccessCount = 0 $FailCount = 0 $CurrentStep = 0 $TotalSteps = $InstallList.Count # 6. 순차 무인 설치 루프 foreach ($app in $InstallList) { $CurrentStep++ $Progress = [math]::Round(($CurrentStep / $TotalSteps) * 100) Write-Host "[$CurrentStep/$TotalSteps] ($Progress%) $($app.Name) 설치 진행 중..." -ForegroundColor Cyan Write-NewtilLog "[$CurrentStep/$TotalSteps] 시작: $($app.Name) ($($app.Id))" "White" $Installed = $false # 1) Direct MSI 설치 방식 (예: 크롬 기업용) if ($app.Type -eq "direct_msi" -and $app.Url) { try { $TempMsi = Join-Path $env:TEMP ("newtil_" + [IO.Path]::GetFileName($app.Url)) Write-Host " -> 공식 서버에서 다운로드 중: $($app.Url)" -ForegroundColor DarkGray Invoke-WebRequest -Uri $app.Url -OutFile $TempMsi -UseBasicParsing -TimeoutSec 60 Write-Host " -> 무인 설치(msiexec) 실행 중..." -ForegroundColor DarkGray $p = Start-Process msiexec.exe -ArgumentList "/i `"$TempMsi`" $($app.SilentArgs)" -Wait -PassThru if ($p.ExitCode -eq 0 -or $p.ExitCode -eq 3010) { $Installed = $true } else { Write-NewtilLog " [경고] msiexec 반환코드: $($p.ExitCode)" "Yellow" } Remove-Item $TempMsi -Force -ErrorAction SilentlyContinue } catch { Write-NewtilLog " [오류] 직접 MSI 다운로드/설치 실패: $($_.Exception.Message)" "Yellow" } } # 2) Direct EXE 설치 방식 (예: 카카오톡, 멜론, 꿀뷰) if (-not $Installed -and $app.Type -eq "direct_exe" -and $app.Url) { try { $TempExe = Join-Path $env:TEMP ("newtil_" + [IO.Path]::GetFileName($app.Url)) Write-Host " -> 공식 서버에서 다운로드 중: $($app.Url)" -ForegroundColor DarkGray Invoke-WebRequest -Uri $app.Url -OutFile $TempExe -UseBasicParsing -TimeoutSec 60 Write-Host " -> 무인 설치 실행 중..." -ForegroundColor DarkGray $Args = if ($app.SilentArgs) { $app.SilentArgs } else { "" } $p = Start-Process $TempExe -ArgumentList $Args -Wait -PassThru if ($p.ExitCode -eq 0 -or $p.ExitCode -eq 3010) { $Installed = $true } else { Write-NewtilLog " [경고] EXE 반환코드: $($p.ExitCode)" "Yellow" } Remove-Item $TempExe -Force -ErrorAction SilentlyContinue } catch { Write-NewtilLog " [오류] 직접 EXE 다운로드/설치 실패: $($_.Exception.Message)" "Yellow" } } # 3) Antigravity 전용 설치 방식 (pip / npm / CLI) if (-not $Installed -and ($app.Id -eq "Google.Antigravity" -or $app.Type -eq "script")) { Write-Host " -> Google Antigravity 개발 환경 점검 및 구성..." -ForegroundColor DarkGray try { if (Get-Command "pip.exe" -ErrorAction SilentlyContinue) { Write-Host " -> pip를 통해 google-antigravity 설치 중..." -ForegroundColor DarkGray $p = Start-Process pip.exe -ArgumentList "install --upgrade google-antigravity" -Wait -PassThru -NoNewWindow if ($p.ExitCode -eq 0) { $Installed = $true } } elseif (Get-Command "npm.cmd" -ErrorAction SilentlyContinue) { Write-Host " -> npm을 통해 @google/antigravity 설치 중..." -ForegroundColor DarkGray $p = Start-Process npm.cmd -ArgumentList "install -g @google/antigravity" -Wait -PassThru -NoNewWindow if ($p.ExitCode -eq 0) { $Installed = $true } } } catch { Write-NewtilLog " [오류] Antigravity 설치 시도 중 예외: $($_.Exception.Message)" "Yellow" } } # 4) winget 설치 방식 (기본 및 대체 수단) if (-not $Installed -and $HasWinget) { $SourceParam = if ($app.Type -eq "msstore") { "--source msstore" } else { "--source winget" } Write-Host " -> Microsoft winget 무인 설치 요청..." -ForegroundColor DarkGray $WingetArgs = "install --id $($app.WingetId) --exact --silent --accept-package-agreements --accept-source-agreements --disable-interactivity $SourceParam" $p = Start-Process winget.exe -ArgumentList $WingetArgs -Wait -PassThru -NoNewWindow # winget 성공 코드: 0 또는 0x8A15002B(이미 설치됨) if ($p.ExitCode -eq 0 -or $p.ExitCode -eq 2316632107 -or $p.ExitCode -eq -1978335189) { $Installed = $true } else { Write-NewtilLog " [경고] winget 종료 코드: $($p.ExitCode)" "Yellow" } } # 결과 판정 및 피드백 if ($Installed) { Write-Host " [완료] $($app.Name) 설치가 정상적으로 완료되었습니다." -ForegroundColor Green Write-NewtilLog " [성공] $($app.Name)" "Green" $SuccessCount++ } else { Write-Host " [실패] $($app.Name) 설치에 실패했습니다. (수동 설치 필요)" -ForegroundColor Red Write-NewtilLog " [실패] $($app.Name)" "Red" $FailCount++ } Write-Host "" } # 7. 설치 완료 리포트 Write-Host "================================================================" -ForegroundColor Cyan Write-Host " 전체 설치 작업 종료 " -ForegroundColor Cyan Write-Host "================================================================" -ForegroundColor Cyan Write-NewtilLog "총 $($TotalSteps)개 중 성공: $($SuccessCount)개, 실패: $($FailCount)개" "Cyan" Write-Host "" Write-Host "상세 로그가 저장되었습니다: $LogFile" -ForegroundColor Gray Write-Host "Newtil Essential Installer를 이용해 주셔서 감사합니다!" -ForegroundColor Green Write-Host "" if (-not $Unattended) { Write-Host "창을 닫으려면 아무 키나 누르세요..." -ForegroundColor DarkGray $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") }