# MTGA Assistant installer for Windows. # # Normally started by double-clicking install.bat in the project folder. It: # 1. finds Python 3.12+ (offering to install it with winget if missing), # 2. installs the app's dependencies and PyInstaller into a private build environment, # 3. builds the app with PyInstaller, # 4. installs it to %LOCALAPPDATA%\Programs\MTGA Assistant (no admin rights needed), # 5. adds Start Menu and Desktop shortcuts, optionally starts it at login, and launches it. # # Uninstall: install.bat -Uninstall param( [switch]$Uninstall, [switch]$NoLaunch ) $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" $AppName = "MTGA Assistant" $ExeName = "MTGA Assistant.exe" $RunValue = "mtga-assistant" # same value the app's own "Start at login" option uses $RunKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" $MinPython = [version]"3.12" $WingetId = "Python.Python.3.12" $ProjectDir = Split-Path -Parent $PSScriptRoot $BuildVenv = Join-Path $ProjectDir ".venv-build" $DistDir = Join-Path $ProjectDir "dist\$AppName" $InstallDir = Join-Path $env:LOCALAPPDATA "Programs\$AppName" $InstalledExe = Join-Path $InstallDir $ExeName $StartMenuLnk = Join-Path ([Environment]::GetFolderPath("Programs")) "$AppName.lnk" $DesktopLnk = Join-Path ([Environment]::GetFolderPath("Desktop")) "$AppName.lnk" function Step($text) { Write-Host ""; Write-Host "==> $text" -ForegroundColor Cyan } function Info($text) { Write-Host " $text" } function Fail($text) { Write-Host ""; Write-Host "ERROR: $text" -ForegroundColor Red; exit 1 } function Ask-YesNo($question, [bool]$default = $true) { $hint = if ($default) { "[Y/n]" } else { "[y/N]" } $answer = Read-Host " $question $hint" if ([string]::IsNullOrWhiteSpace($answer)) { return $default } return $answer.Trim().ToLower().StartsWith("y") } function Stop-RunningApp { $running = Get-Process -Name $AppName -ErrorAction SilentlyContinue if ($running) { Info "Closing the running $AppName..." $running | Stop-Process -Force Start-Sleep -Seconds 1 } } function Remove-Shortcuts { foreach ($lnk in @($StartMenuLnk, $DesktopLnk)) { if (Test-Path $lnk) { Remove-Item $lnk -Force } } } # --- uninstall ------------------------------------------------------------------------------- if ($Uninstall) { Step "Uninstalling $AppName" Stop-RunningApp Remove-Shortcuts Remove-ItemProperty -Path $RunKey -Name $RunValue -ErrorAction SilentlyContinue if (Test-Path $InstallDir) { Remove-Item $InstallDir -Recurse -Force } Info "Removed the app, its shortcuts and its login item." Info "Your game history and settings were kept in $env:LOCALAPPDATA\mtga-assistant and $env:APPDATA\mtga-assistant." Info "Delete those folders too if you want to remove everything." exit 0 } Write-Host "$AppName installer" -ForegroundColor Green Info "Project folder: $ProjectDir" # --- 1. Python -------------------------------------------------------------------------------- function Refresh-Path { # winget installs update PATH in the registry, not in this already-running session. $machine = [Environment]::GetEnvironmentVariable("Path", "Machine") $user = [Environment]::GetEnvironmentVariable("Path", "User") $env:Path = "$machine;$user" } function Test-Python([string[]]$command) { # Returns the version if this command runs a real Python >= $MinPython, else $null. # (The Microsoft Store "python" alias exists but only opens the Store, so it fails here.) try { $exe = $command[0] $prefix = @($command | Select-Object -Skip 1) $out = & $exe @prefix -c "import sys; print('%d.%d.%d' % sys.version_info[:3])" 2>$null if ($LASTEXITCODE -ne 0 -or -not $out) { return $null } $version = [version]($out | Select-Object -First 1).Trim() if ($version -ge $MinPython) { return $version } } catch { } return $null } function Find-Python { $candidates = @( @("py", "-3.13"), @("py", "-3.12"), @("py", "-3"), @("python"), @("python3"), @((Join-Path $env:LOCALAPPDATA "Programs\Python\Python313\python.exe")), @((Join-Path $env:LOCALAPPDATA "Programs\Python\Python312\python.exe")) ) foreach ($cmd in $candidates) { $cmd = [string[]]@($cmd) # keep one-word commands as arrays, not strings $version = Test-Python $cmd if ($version) { return @{ Command = $cmd; Version = $version } } } return $null } Step "Checking for Python $MinPython or newer" $python = Find-Python if (-not $python) { Info "Python $MinPython+ was not found." $winget = Get-Command winget -ErrorAction SilentlyContinue if ($winget -and (Ask-YesNo "Install Python 3.12 now with winget (for your user only)?")) { & winget install --exact --id $WingetId --scope user --silent ` --accept-package-agreements --accept-source-agreements if ($LASTEXITCODE -ne 0) { Fail "winget couldn't install Python (exit code $LASTEXITCODE)." } Refresh-Path $python = Find-Python } if (-not $python) { Info "Install Python from https://www.python.org/downloads/ (tick 'Add python.exe to PATH')," Info "then run install.bat again." Start-Process "https://www.python.org/downloads/windows/" Fail "Python $MinPython or newer is required." } } $pyExe = $python.Command[0] $pyArgs = @($python.Command | Select-Object -Skip 1) Info "Using Python $($python.Version) ($($python.Command -join ' '))" # --- 2. Dependencies -------------------------------------------------------------------------- Step "Installing dependencies (first run downloads ~100 MB; this can take a few minutes)" $venvPython = Join-Path $BuildVenv "Scripts\python.exe" if (-not (Test-Path $venvPython)) { & $pyExe @pyArgs -m venv $BuildVenv if ($LASTEXITCODE -ne 0) { Fail "Couldn't create the build environment in $BuildVenv." } } & $venvPython -m pip install --disable-pip-version-check --quiet --upgrade pip & $venvPython -m pip install --disable-pip-version-check --quiet $ProjectDir "pyinstaller>=6" if ($LASTEXITCODE -ne 0) { Fail "Installing dependencies failed (see the messages above)." } Info "Dependencies installed." # --- 3. Build ------------------------------------------------------------------------------------ Step "Building $AppName" Push-Location $ProjectDir try { & $venvPython -m PyInstaller --noconfirm --clean --log-level WARN "packaging\mtga-assistant.spec" if ($LASTEXITCODE -ne 0) { Fail "The PyInstaller build failed (see the messages above)." } } finally { Pop-Location } if (-not (Test-Path (Join-Path $DistDir $ExeName))) { Fail "The build finished but $ExeName is missing." } Info "Built $DistDir" # --- 4. Install ---------------------------------------------------------------------------------- Step "Installing to $InstallDir" Stop-RunningApp if (Test-Path $InstallDir) { Remove-Item $InstallDir -Recurse -Force } New-Item -ItemType Directory -Path (Split-Path $InstallDir) -Force | Out-Null Copy-Item -Path $DistDir -Destination $InstallDir -Recurse Info "Installed." # --- 5. Shortcuts, login item, launch ------------------------------------------------------------- Step "Creating shortcuts" $shell = New-Object -ComObject WScript.Shell foreach ($lnkPath in @($StartMenuLnk, $DesktopLnk)) { $lnk = $shell.CreateShortcut($lnkPath) $lnk.TargetPath = $InstalledExe $lnk.WorkingDirectory = $InstallDir $lnk.IconLocation = "$InstalledExe,0" $lnk.Description = "Deck and game tracker for Magic: The Gathering Arena" $lnk.Save() } Info "Start Menu: $StartMenuLnk" Info "Desktop: $DesktopLnk" Step "Start at login" Info "Running in the background lets it record every game and pop up when MTGA starts." if (Ask-YesNo "Start $AppName hidden when you log in?") { Set-ItemProperty -Path $RunKey -Name $RunValue -Value "`"$InstalledExe`" --background" Info "Added. (You can change this later in the app: menu -> Start at login.)" } else { Remove-ItemProperty -Path $RunKey -Name $RunValue -ErrorAction SilentlyContinue Info "Skipped." } Write-Host "" Write-Host "$AppName is installed." -ForegroundColor Green Info "Remember to enable MTGA's detailed logs: Options -> Account -> Detailed Logs (Plugin Support)." Info "To uninstall later, run: install.bat -Uninstall" if (-not $NoLaunch) { Start-Process -FilePath $InstalledExe -WorkingDirectory $InstallDir }