[CmdletBinding()] param() Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' function Get-Sha256([string]$Path) { return (Get-FileHash -Algorithm SHA256 -LiteralPath $Path).Hash.ToLowerInvariant() } function Get-UserPathValue { if ($env:THT_USER_PATH_FILE) { if (Test-Path -LiteralPath $env:THT_USER_PATH_FILE) { return [System.IO.File]::ReadAllText($env:THT_USER_PATH_FILE) } return '' } return [Environment]::GetEnvironmentVariable('Path', 'User') } function Set-UserPathValue([string]$Value) { if ($env:THT_USER_PATH_FILE) { [System.IO.File]::WriteAllText($env:THT_USER_PATH_FILE, $Value) return } [Environment]::SetEnvironmentVariable('Path', $Value, 'User') } function Add-InstallDirectoryToUserPath([string]$InstallDirectory) { $currentPath = Get-UserPathValue $entries = @($currentPath -split ';' | Where-Object { $_ -ne '' }) if ($entries | Where-Object { $_.TrimEnd('\\') -ieq $InstallDirectory.TrimEnd('\\') }) { return $false } $newPath = (@($entries) + $InstallDirectory) -join ';' Set-UserPathValue $newPath return $true } function Replace-InstalledBinary([string]$StagedBinary, [string]$TargetBinary) { $backupBinary = $null $replacedExistingBinary = $false try { if (Test-Path -LiteralPath $TargetBinary -PathType Leaf) { $backupBinary = Join-Path (Split-Path -Parent $TargetBinary) ('.tht-previous-' + [guid]::NewGuid() + '.exe') [System.IO.File]::Replace($StagedBinary, $TargetBinary, $backupBinary, $true) $replacedExistingBinary = $true } else { [System.IO.File]::Move($StagedBinary, $TargetBinary) } & $TargetBinary --help | Out-Null if ($LASTEXITCODE -ne 0) { throw 'installed tht did not pass its help check' } if ($backupBinary -and (Test-Path -LiteralPath $backupBinary -PathType Leaf)) { Remove-Item -LiteralPath $backupBinary -Force -ErrorAction Stop } } catch { $installFailure = $_ if ($replacedExistingBinary -and $backupBinary -and (Test-Path -LiteralPath $backupBinary -PathType Leaf)) { try { [System.IO.File]::Replace($backupBinary, $TargetBinary, $null, $true) $backupBinary = $null } catch { throw "tht replacement failed; the previous executable remains at $backupBinary. Original failure: $installFailure. Restore failure: $_" } } throw $installFailure } finally { Remove-Item -LiteralPath $StagedBinary -Force -ErrorAction SilentlyContinue } } $repositoryRoot = Split-Path -Parent $PSScriptRoot $buildOutput = $null $sourceArtifactForInstall = $null $installDirectory = if ($env:THT_INSTALL_DIRECTORY) { $env:THT_INSTALL_DIRECTORY } else { Join-Path $env:LOCALAPPDATA 'ThothII\bin' } $installDirectory = [System.IO.Path]::GetFullPath($installDirectory) if ($env:THT_BUILD_ARTIFACT) { $sourceArtifact = $env:THT_BUILD_ARTIFACT if (-not (Test-Path -LiteralPath $sourceArtifact -PathType Leaf)) { throw 'THT_BUILD_ARTIFACT is not a regular file' } if (-not $env:THT_BUILD_ARTIFACT_SHA256 -or $env:THT_BUILD_ARTIFACT_SHA256 -notmatch '^[0-9A-Fa-f]{64}$') { throw 'THT_BUILD_ARTIFACT_SHA256 must be a 64-character SHA-256 digest' } if ((Get-Sha256 $sourceArtifact) -ne $env:THT_BUILD_ARTIFACT_SHA256.ToLowerInvariant()) { throw 'packaged tht artifact checksum does not match' } $sourceArtifactForInstall = $sourceArtifact } else { if ($env:PROCESSOR_ARCHITECTURE -notin @('AMD64', 'x86_64')) { throw "unsupported Windows processor architecture: $env:PROCESSOR_ARCHITECTURE (supported: AMD64)" } $buildOutput = Join-Path ([System.IO.Path]::GetTempPath()) ("tht-build-" + [guid]::NewGuid()) New-Item -ItemType Directory -Path $buildOutput -Force | Out-Null $previousOutput = $env:THT_THT_OUTPUT_DIRECTORY $env:THT_THT_OUTPUT_DIRECTORY = $buildOutput try { & bash (Join-Path $repositoryRoot 'scripts/build-tht.sh') --all if ($LASTEXITCODE -ne 0) { throw 'the repository-pinned Docker build failed' } } finally { $env:THT_THT_OUTPUT_DIRECTORY = $previousOutput } $sourceArtifactForInstall = Join-Path $buildOutput 'tht-windows-amd64.exe' if (-not (Test-Path -LiteralPath $sourceArtifactForInstall -PathType Leaf)) { throw 'the repository-pinned Docker build did not produce tht-windows-amd64.exe' } } if ((Get-Item -LiteralPath $sourceArtifactForInstall).Length -eq 0) { throw 'native artifact is empty' } New-Item -ItemType Directory -Path $installDirectory -Force | Out-Null $stagedBinary = Join-Path $installDirectory ('.tht-' + [guid]::NewGuid() + '.exe') try { Copy-Item -LiteralPath $sourceArtifactForInstall -Destination $stagedBinary -Force & $stagedBinary --help | Out-Null if ($LASTEXITCODE -ne 0) { throw 'native artifact did not pass its help check' } Replace-InstalledBinary $stagedBinary (Join-Path $installDirectory 'tht.exe') } finally { Remove-Item -LiteralPath $stagedBinary -Force -ErrorAction SilentlyContinue if ($buildOutput) { Remove-Item -LiteralPath $buildOutput -Recurse -Force -ErrorAction SilentlyContinue } } $pathChanged = Add-InstallDirectoryToUserPath $installDirectory if ($env:Path -notlike "*$installDirectory*") { $env:Path = "$installDirectory;$env:Path" } & (Join-Path $installDirectory 'tht.exe') --help | Out-Null if ($LASTEXITCODE -ne 0) { throw 'installed tht did not pass its help check' } Write-Output "Installed tht at $(Join-Path $installDirectory 'tht.exe')" if ($pathChanged) { Write-Output 'Added ThothII to your user PATH. Open a new terminal before using tht there.' }