# Sidekit installer for Windows — https://sidekit.net # # Usage: iwr -useb https://sidekit.net/install.ps1 | iex # # The PowerShell twin of install.sh. Sidekit is a .NET global tool and this is a # wrapper around `dotnet tool install`, not a replacement: MSBuildLocator loads the # *installed SDK's* MSBuild at runtime and the runner shells out to `dotnet`, so the # SDK is a hard requirement no packaging choice can remove. Checking for it up front # turns a confusing Roslyn failure later into one clear sentence now. # # The package is served from GitHub Releases on the public repository. This script is # the indirection layer: it is fetched fresh from sidekit.net every time, so the # download host can move without the install command people share ever changing. # # Environment overrides: # SIDEKIT_VERSION version to install (default: whatever latest.txt names) # SIDEKIT_INSTALL_DIR install here via --tool-path instead of --global # SIDEKIT_BASE_URL releases host (default: the public repo's releases) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest $BaseUrl = if ($env:SIDEKIT_BASE_URL) { $env:SIDEKIT_BASE_URL } else { 'https://github.com/sidekit-net/sidekit/releases' } $MinSdkMajor = 10 function Write-Info { param($Message) Write-Host $Message } function Write-Warn { param($Message) Write-Warning $Message } function Stop-Install { param($Message) Write-Error $Message; exit 1 } # The SDK, not just the runtime. `dotnet --version` resolves through any global.json # in the current directory, so it reports what this directory wants rather than what # is installed; --list-sdks reports the truth. function Test-DotnetSdk { if (-not (Get-Command dotnet -ErrorAction SilentlyContinue)) { Stop-Install @" The .NET SDK is required but 'dotnet' is not on PATH. Sidekit builds and runs your tests on the real toolchain, so the SDK is not optional. Install .NET $MinSdkMajor from https://dotnet.microsoft.com/download "@ } $sdks = & dotnet --list-sdks 2>$null if (-not $sdks) { Stop-Install @" 'dotnet' is on PATH but reports no installed SDKs. This usually means only the runtime is installed. Sidekit needs the SDK: https://dotnet.microsoft.com/download "@ } $newest = ($sdks | ForEach-Object { [int]($_ -split '\.')[0] } | Sort-Object -Descending)[0] if ($newest -lt $MinSdkMajor) { Stop-Install ".NET $MinSdkMajor SDK or newer is required; the newest installed is $newest. Install it from https://dotnet.microsoft.com/download" } $newestFull = ($sdks | Where-Object { [int]($_ -split '\.')[0] -eq $newest } | Select-Object -Last 1) -replace '\s*\[.*$', '' Write-Info "Found .NET SDK $newestFull." } # Read from a release asset rather than the GitHub API, which allows only 60 # unauthenticated requests per hour per IP — one corporate NAT exhausts that for # everyone behind it. /releases/latest resolves to the newest release NOT marked as a # prerelease, which is why release.yml does not mark betas as prereleases. function Resolve-LatestVersion { try { (Invoke-WebRequest -UseBasicParsing -Uri "$BaseUrl/latest/download/latest.txt").Content.Trim() } catch { Stop-Install "Could not determine which version to install: $($_.Exception.Message)" } } function Install-Sidekit { param($Version, $SourceDir) if ($env:SIDEKIT_INSTALL_DIR) { New-Item -ItemType Directory -Force -Path $env:SIDEKIT_INSTALL_DIR | Out-Null $scope = @('--tool-path', $env:SIDEKIT_INSTALL_DIR) } else { $scope = @('--global') } # `dotnet tool install` fails outright if the tool is already present, so an # installer that only ever installs breaks on its second run — which is exactly # how people upgrade. Pick the verb from what is actually installed. $installed = & dotnet tool list @scope 2>$null | Select-String -Pattern '^sidekit\s' if ($installed) { Write-Info "Updating Sidekit to $Version" $verb = 'update' } else { Write-Info "Installing Sidekit $Version" $verb = 'install' } # --ignore-failed-sources: the package comes from the local directory, but NuGet # still consults every configured source. A restricted corporate proxy failing on # an unrelated feed must not block an install whose package is already on disk. & dotnet tool $verb @scope Sidekit ` --version $Version ` --add-source $SourceDir ` --ignore-failed-sources if ($LASTEXITCODE -ne 0) { Stop-Install "'dotnet tool $verb' failed." } } function Test-ToolPath { $target = if ($env:SIDEKIT_INSTALL_DIR) { $env:SIDEKIT_INSTALL_DIR } else { Join-Path $env:USERPROFILE '.dotnet\tools' } if (($env:PATH -split ';') -notcontains $target) { # Not fatal: the tool is installed and correct, it just is not reachable yet. Write-Warn "$target is not on your PATH." Write-Info " Add it, then reopen the shell:" Write-Info " [Environment]::SetEnvironmentVariable('PATH', `"`$env:PATH;$target`", 'User')" } } Write-Info 'Sidekit installer' Test-DotnetSdk $version = if ($env:SIDEKIT_VERSION) { $env:SIDEKIT_VERSION } else { Resolve-LatestVersion } if (-not $version) { Stop-Install 'Could not determine which version to install.' } $tmp = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName()) New-Item -ItemType Directory -Force -Path $tmp | Out-Null try { # Saved under NuGet's canonical ..nupkg name whatever the URL was: # --add-source reads the directory as a local feed and the filename is part of # that contract. $nupkg = "Sidekit.$version.nupkg" $url = "$BaseUrl/download/v$version/$nupkg" Write-Info "Downloading $nupkg" try { Invoke-WebRequest -UseBasicParsing -Uri $url -OutFile (Join-Path $tmp $nupkg) } catch { Stop-Install "Download failed: $url - $($_.Exception.Message)" } Install-Sidekit -Version $version -SourceDir $tmp Test-ToolPath Write-Info '' Write-Info "Installed. Next: run 'sidekit doctor' in a .NET repository." } finally { Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue }