Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/branch-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
- 'CMakeLists.txt'
- 'build.sh'
- '.github/actions/build-windows/**'
- '.github/workflows/**'
- '.github/workflows/branch-build.yml'
- 'requirements.txt'

permissions:
Expand Down
62 changes: 62 additions & 0 deletions .github/workflows/installer-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Installer Test (Windows)

# Runs scripts/install.ps1 for real on a clean Windows runner whenever the installer changes, so a
# change cannot break the default install path unnoticed (see #73).

on:
pull_request:
paths:
- "scripts/install.ps1"
- ".github/workflows/installer-test.yml"
workflow_dispatch:

permissions:
contents: read

jobs:
install:
name: Install (${{ matrix.mode }})
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
mode: [default, dev-build, zip-path, nested-zip]
steps:
- name: Check out repository
uses: actions/checkout@v7

- name: Run installer
shell: powershell
run: |
$ErrorActionPreference = "Stop"
$dir = Join-Path $env:RUNNER_TEMP "utv-install"
# -SkipDeps: the 390 MB dependency installer is not what is under test here.
$common = @{ InstallDir = $dir; SkipDeps = $true; NoShortcuts = $true }
$devUrl = "https://github.com/OpenUTV/utv/releases/download/dev-build/UTV-dev-windows-x64.zip"

switch ("${{ matrix.mode }}") {
"default" { .\scripts\install.ps1 @common }
"dev-build" { .\scripts\install.ps1 @common -Version dev-build }
"zip-path" {
$zip = Join-Path $env:RUNNER_TEMP "utv.zip"
Invoke-WebRequest -Uri $devUrl -OutFile $zip -UseBasicParsing
.\scripts\install.ps1 @common -ZipPath $zip
if (-not (Test-Path $zip)) { throw "-ZipPath deleted the user's zip" }
}
"nested-zip" {
# What the GitHub web page gives for a CI artifact: a zip containing the UTV zip.
$inner = Join-Path $env:RUNNER_TEMP "inner\UTV-branch-windows-x64.zip"
New-Item -ItemType Directory -Force -Path (Split-Path $inner) | Out-Null
Invoke-WebRequest -Uri $devUrl -OutFile $inner -UseBasicParsing
$outer = Join-Path $env:RUNNER_TEMP "artifact.zip"
Compress-Archive -Path $inner -DestinationPath $outer -CompressionLevel NoCompression
.\scripts\install.ps1 @common -ZipPath $outer
}
}

foreach ($f in @("bin\utv.exe", "bin\utvio.exe")) {
if (-not (Test-Path (Join-Path $dir $f))) { throw "Installer did not produce $f" }
}
Write-Host "Installed files present in $dir"
# The installer copies with robocopy, whose success codes are non-zero and would fail the step.
exit 0
67 changes: 62 additions & 5 deletions scripts/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,27 @@
Installs OpenUTV to Program Files (or AppData for non-admin users),
automatically verifies and installs OpenUTVDeps runtime dependencies,
configures system PATH, and creates Start Menu and Desktop shortcuts.
.PARAMETER Version
"latest" (default) installs the latest release. A release tag such as "2026.9" installs that
release. "dev-build" installs the newest development pre-release built from main.
.PARAMETER ZipPath
Install from a UTV Windows zip already on disk (for example a CI build artifact) instead of
downloading a release.
.EXAMPLE
irm https://openutv.com/install.ps1 | iex
irm https://github.com/ghraw/OpenUTV/utv/main/scripts/install.ps1 | iex
.EXAMPLE
# Development pre-release (parameters can't be passed through "irm | iex")
& ([scriptblock]::Create((irm https://openutv.com/install.ps1))) -Version dev-build
.EXAMPLE
.\install.ps1 -ZipPath .\UTV-windows-x64.zip
#>
[CmdletBinding()]
param(
[string]$Version = "latest",
[string]$DepsVersion = "26.5",
[string]$InstallDir = "",
[string]$ZipPath = "",
[switch]$SkipDeps = $false,
[switch]$NoShortcuts = $false,
[switch]$Uninstall = $false
Expand Down Expand Up @@ -164,8 +176,39 @@ if ($depsFound) {
}

# Step 2: Resolve UTV Release Asset
Write-Host "`n--- Downloading OpenUTV ($Version) ---" -ForegroundColor Cyan
if ($Version -eq "latest") {
#
# The default (no parameters) installs the latest release. The opt-in paths below, -ZipPath and
# -Version dev-build, must not change what the default does.
$zipUrl = $null
$localZip = $null
if ($ZipPath) {
if (-not (Test-Path -LiteralPath $ZipPath -PathType Leaf)) {
Write-Error "-ZipPath: file not found: $ZipPath"
return
}
$localZip = (Resolve-Path -LiteralPath $ZipPath).Path
if ($Version -eq "latest") { $Version = "local" }
Write-Host "`n--- Installing OpenUTV from $localZip ---" -ForegroundColor Cyan
} else {
Write-Host "`n--- Downloading OpenUTV ($Version) ---" -ForegroundColor Cyan
}

if (-not $localZip -and $Version -eq "dev-build") {
# The development pre-release keeps one moving tag, and its asset name does not contain the tag.
$apiUrl = "https://github.com/ghapi/repos/$RepoOwner/$RepoName/releases/tags/dev-build"
try {
$releaseData = Invoke-RestMethod -Uri $apiUrl -UseBasicParsing
$asset = $releaseData.assets | Where-Object { $_.name -match "UTV-.*-windows-x64\.zip" } | Select-Object -First 1
if ($asset) {
$zipUrl = $asset.browser_download_url
}
} catch {
Write-Warning "Could not query GitHub API for the dev-build release. Falling back to the default asset URL."
}
if (-not $zipUrl) {
$zipUrl = "https://github.com/$RepoOwner/$RepoName/releases/download/dev-build/UTV-dev-windows-x64.zip"
}
} elseif (-not $localZip -and $Version -eq "latest") {
$apiUrl = "https://github.com/ghapi/repos/$RepoOwner/$RepoName/releases/latest"
try {
$releaseData = Invoke-RestMethod -Uri $apiUrl -UseBasicParsing
Expand All @@ -179,16 +222,21 @@ if ($Version -eq "latest") {
}
}

if (-not $zipUrl) {
if (-not $localZip -and -not $zipUrl) {
if ($Version -eq "latest") { $Version = "2026.9" }
$zipUrl = "https://github.com/$RepoOwner/$RepoName/releases/download/$Version/UTV-$Version-windows-x64.zip"
}

Write-Host "Release Version: $Version" -ForegroundColor White
Write-Host "Download URL: $zipUrl" -ForegroundColor Gray

$tempZip = Join-Path $env:TEMP "OpenUTV-$Version-windows-x64.zip"
Invoke-WebRequest -Uri $zipUrl -OutFile $tempZip -UseBasicParsing
if ($localZip) {
# Work on a copy: the temporary zip is deleted after extraction.
Copy-Item -LiteralPath $localZip -Destination $tempZip -Force
} else {
Write-Host "Download URL: $zipUrl" -ForegroundColor Gray
Invoke-WebRequest -Uri $zipUrl -OutFile $tempZip -UseBasicParsing
}

# Step 3: Extract & Install Application Files
Write-Host "`n--- Installing OpenUTV into $InstallDir ---" -ForegroundColor Cyan
Expand All @@ -197,6 +245,15 @@ New-Item -ItemType Directory -Force -Path $tempExtract | Out-Null
Expand-Archive -Path $tempZip -DestinationPath $tempExtract -Force
Remove-Item -Force $tempZip -ErrorAction SilentlyContinue

# A CI artifact downloaded from the GitHub web page is a zip that contains the UTV zip.
if ($localZip -and -not (Test-Path (Join-Path $tempExtract "utv-windows-x64"))) {
$innerZips = @(Get-ChildItem -Path $tempExtract -Filter "*.zip" -File)
if ($innerZips.Count -eq 1) {
Expand-Archive -Path $innerZips[0].FullName -DestinationPath $tempExtract -Force
Remove-Item -Force $innerZips[0].FullName -ErrorAction SilentlyContinue
}
}

$sourceDir = Join-Path $tempExtract "utv-windows-x64"
if (-not (Test-Path $sourceDir)) {
$sourceDir = $tempExtract
Expand Down
Loading