[CmdletBinding()] param( [string]$BaseUrl = $env:TSC_GIT_HOOK_BASE_URL, [string]$InstallDir = $env:TSC_GIT_HOOK_INSTALL_DIR ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if ([string]::IsNullOrWhiteSpace($BaseUrl)) { $BaseUrl = 'https://hook.tsc-security.tools' } if ([string]::IsNullOrWhiteSpace($InstallDir)) { $InstallDir = Join-Path $env:LOCALAPPDATA 'Programs\tsc-git-hook\bin' } function Write-Step { param([string]$Message) Write-Host "==> $Message" } function Write-Info { param([string]$Message) Write-Host " $Message" } function Resolve-TargetArch { $arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture switch ($arch.ToString()) { 'X64' { return 'x64' } 'Arm64' { return 'arm64' } default { throw "Unsupported CPU architecture: $arch" } } } $targetArch = Resolve-TargetArch $manifestUrl = "$BaseUrl/releases/releases.json" Write-Step "Fetching release manifest" $manifest = Invoke-RestMethod -Uri $manifestUrl $release = $null $artifact = $null foreach ($candidate in $manifest.releases) { foreach ($entry in $candidate.artifacts) { if ($entry.os -eq 'win' -and $entry.arch -eq $targetArch) { $release = $candidate $artifact = $entry break } } if ($null -ne $artifact) { break } } if ($null -eq $artifact) { throw "No compatible Windows release was found in $manifestUrl." } $tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("tsc-git-hook-" + [Guid]::NewGuid().ToString('N')) $archivePath = Join-Path $tempRoot $artifact.filename $extractDir = Join-Path $tempRoot 'extract' $binaryName = 'tsc-git-hook.exe' $targetPath = Join-Path $InstallDir $binaryName New-Item -ItemType Directory -Force -Path $tempRoot, $extractDir, $InstallDir | Out-Null try { Write-Step "Downloading tsc-git-hook $($release.version)" Invoke-WebRequest -Uri "$BaseUrl/releases/$($artifact.filename)" -OutFile $archivePath $actualSha256 = (Get-FileHash -Algorithm SHA256 -Path $archivePath).Hash.ToLowerInvariant() if ($actualSha256 -ne $artifact.sha256.ToLowerInvariant()) { throw "Checksum validation failed for $($artifact.filename)." } Write-Step "Installing into $InstallDir" Expand-Archive -Path $archivePath -DestinationPath $extractDir -Force Copy-Item -Path (Join-Path $extractDir $binaryName) -Destination $targetPath -Force $currentUserPath = [Environment]::GetEnvironmentVariable('Path', 'User') $pathEntries = @() if (-not [string]::IsNullOrWhiteSpace($currentUserPath)) { $pathEntries = $currentUserPath.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries) } if ($pathEntries -notcontains $InstallDir) { $newPath = @($InstallDir) + $pathEntries [Environment]::SetEnvironmentVariable('Path', ($newPath -join ';'), 'User') Write-Info "Added $InstallDir to the user PATH." } if (($env:Path.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries)) -notcontains $InstallDir) { $env:Path = "$InstallDir;$env:Path" } Write-Step "Installed tsc-git-hook $($release.version)" Write-Info "Run 'tsc-git-hook --help' in a new terminal to get started." } finally { if (Test-Path $tempRoot) { Remove-Item -Path $tempRoot -Recurse -Force } }