aboutsummaryrefslogtreecommitdiff
path: root/distribution
diff options
context:
space:
mode:
authorJJ Liebig <jonathan.liebig@gmail.com>2026-09-05 00:47:00 +0200
committerGitHub <noreply@github.com>2026-09-05 01:47:00 +0300
commit8162e26509f7b91eb8dcfd47387d43af3f348bbb (patch)
treedfbd1a95356f4aaecee5749e45ede7fc73271bca /distribution
parenta2b128978a154c02311b90da778ca115758f75d7 (diff)
fix(windows): use active release directory on PATH (#3618)
* fix(windows): use active release directory on PATH refs #3611 * fix(windows): expand variables in PATH comparisons refs #3611 * fix(windows): restrict installer channel detection refs #3611 --------- Co-authored-by: Can Celik <ogulcancelik@gmail.com>
Diffstat (limited to 'distribution')
-rw-r--r--distribution/install.ps1170
1 files changed, 117 insertions, 53 deletions
diff --git a/distribution/install.ps1 b/distribution/install.ps1
index 94a57314..89de1307 100644
--- a/distribution/install.ps1
+++ b/distribution/install.ps1
@@ -55,56 +55,86 @@ function Get-HerdrCommandSource {
return $existing.Source
}
-function Test-PathStartsWith {
- param(
- [string]$Path,
- [string]$Prefix
- )
-
- if ([string]::IsNullOrWhiteSpace($Path) -or [string]::IsNullOrWhiteSpace($Prefix)) {
- return $false
+function Get-HerdrMigrationFallback {
+ param([string]$CurrentDir)
+
+ if (Test-IsJunction -Path $CurrentDir) {
+ $target = [string](Get-Item -LiteralPath $CurrentDir -Force).Target
+ $candidate = Join-Path $target "herdr.exe"
+ if (Test-RegularFile -Path $candidate) {
+ return $candidate
+ }
}
- try {
- $normalizedPath = [System.IO.Path]::GetFullPath($Path)
- $normalizedPrefix = [System.IO.Path]::GetFullPath($Prefix).TrimEnd("\") + "\"
- return $normalizedPath.StartsWith($normalizedPrefix, [System.StringComparison]::OrdinalIgnoreCase)
- } catch {
- return $false
- }
+ return $null
}
-function Path-Contains {
+function Get-HerdrExecutableKind {
param(
- [string]$PathValue,
- [string]$Entry
+ [string]$Path,
+ [string]$ReleasesDir,
+ [string]$CurrentDir,
+ [string]$VisibleBinDir
)
- if ([string]::IsNullOrWhiteSpace($PathValue)) {
- return $false
+ if ([string]::IsNullOrWhiteSpace($Path) -or
+ -not [System.IO.Path]::GetFileName($Path).Equals("herdr.exe", [System.StringComparison]::OrdinalIgnoreCase)) {
+ return $null
}
- $needle = $Entry.TrimEnd("\")
- foreach ($segment in $PathValue.Split(";", [System.StringSplitOptions]::RemoveEmptyEntries)) {
- if ($segment.TrimEnd("\") -ieq $needle) {
- return $true
+ try {
+ $fullPath = [System.IO.Path]::GetFullPath($Path)
+ foreach ($alias in @($CurrentDir, $VisibleBinDir)) {
+ $aliasHerdr = [System.IO.Path]::GetFullPath((Join-Path $alias "herdr.exe"))
+ if ($fullPath.Equals($aliasHerdr, [System.StringComparison]::OrdinalIgnoreCase)) {
+ return "alias"
+ }
+ }
+
+ $parent = Split-Path -Parent $fullPath
+ if ([System.IO.Path]::GetFullPath((Split-Path -Parent $parent)).TrimEnd("\").Equals(
+ [System.IO.Path]::GetFullPath($ReleasesDir).TrimEnd("\"),
+ [System.StringComparison]::OrdinalIgnoreCase
+ )) {
+ return "release"
}
+ } catch {
+ return $null
}
- return $false
+ return $null
}
function Prepend-PathEntry {
param(
[string]$PathValue,
- [string]$Entry
+ [string]$Entry,
+ [string[]]$OwnedEntriesToRemove = @(),
+ [string]$OwnedEntryParentToRemove
)
- $needle = $Entry.TrimEnd("\")
+ $normalize = {
+ param([string]$Value)
+ $comparison = [Environment]::ExpandEnvironmentVariables($Value.Trim().Trim('"')).TrimEnd("\")
+ try { [System.IO.Path]::GetFullPath($comparison).TrimEnd("\") } catch { $comparison }
+ }
+ $needle = & $normalize $Entry
+ $owned = @($OwnedEntriesToRemove | ForEach-Object { & $normalize $_ })
+ $ownedParent = if ([string]::IsNullOrWhiteSpace($OwnedEntryParentToRemove)) {
+ $null
+ } else {
+ & $normalize $OwnedEntryParentToRemove
+ }
$segments = @($Entry)
if (-not [string]::IsNullOrWhiteSpace($PathValue)) {
$segments += $PathValue.Split(";", [System.StringSplitOptions]::RemoveEmptyEntries) |
- Where-Object { $_.TrimEnd("\") -ine $needle }
+ Where-Object {
+ $segment = & $normalize $_
+ try { $parent = [System.IO.Path]::GetDirectoryName($segment) } catch { $parent = $null }
+ $segment -ine $needle -and
+ -not ($owned -icontains $segment) -and
+ ($null -eq $ownedParent -or $parent -ine $ownedParent)
+ }
}
return ($segments -join ";")
@@ -113,7 +143,9 @@ function Prepend-PathEntry {
function Update-PathRegistryEntry {
param(
[Microsoft.Win32.RegistryKey]$EnvironmentKey,
- [string]$Entry
+ [string]$Entry,
+ [string[]]$OwnedEntriesToRemove = @(),
+ [string]$OwnedEntryParentToRemove
)
$options = [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames
@@ -123,7 +155,11 @@ function Update-PathRegistryEntry {
} else {
$EnvironmentKey.GetValueKind("Path")
}
- $newValue = Prepend-PathEntry -PathValue $value -Entry $Entry
+ $newValue = Prepend-PathEntry `
+ -PathValue $value `
+ -Entry $Entry `
+ -OwnedEntriesToRemove $OwnedEntriesToRemove `
+ -OwnedEntryParentToRemove $OwnedEntryParentToRemove
if ($newValue -ceq $value) {
return $false
}
@@ -703,10 +739,17 @@ try {
$allowLegacyVisibleBinMigration = $false
}
-$existingHerdr = Get-HerdrCommandSource
-if (-not [string]::IsNullOrWhiteSpace($existingHerdr) -and -not (Test-PathStartsWith -Path $existingHerdr -Prefix $visibleBinDir)) {
- Write-Step "Detected existing Herdr command at $existingHerdr"
- Write-WarningStep "PATH order decides which Herdr runs. This installer will put $visibleBinDir first for future and current PowerShell sessions."
+$commandHerdr = Get-HerdrCommandSource
+$channelHerdr = $commandHerdr
+if ([string]::IsNullOrWhiteSpace($channelHerdr)) { $channelHerdr = Get-HerdrMigrationFallback -CurrentDir $currentDir }
+$existingHerdrKind = Get-HerdrExecutableKind `
+ -Path $channelHerdr `
+ -ReleasesDir $releasesDir `
+ -CurrentDir $currentDir `
+ -VisibleBinDir $visibleBinDir
+if (-not [string]::IsNullOrWhiteSpace($commandHerdr) -and $null -eq $existingHerdrKind) {
+ Write-Step "Detected existing Herdr command at $commandHerdr"
+ Write-WarningStep "PATH order decides which Herdr runs. This installer will put the active versioned release first for future and current PowerShell sessions."
}
if ($useLocalPackage) {
@@ -717,8 +760,11 @@ if ($useLocalPackage) {
}
} else {
if (-not $channelWasExplicit) {
- if (-not [string]::IsNullOrWhiteSpace($existingHerdr)) {
- $detectedChannel = [string](& $existingHerdr channel show 2>$null | Select-Object -Last 1)
+ if (-not [string]::IsNullOrWhiteSpace($channelHerdr) -and $null -eq $existingHerdrKind) {
+ throw "Refusing to run unrecognized Herdr command at $channelHerdr to detect its update channel. Rerun with -Channel stable or -Channel preview."
+ }
+ if (-not [string]::IsNullOrWhiteSpace($channelHerdr)) {
+ $detectedChannel = [string](& $channelHerdr channel show 2>$null | Select-Object -Last 1)
$detectedChannel = $detectedChannel.Trim()
if ($LASTEXITCODE -ne 0 -or $detectedChannel -notin @("stable", "preview")) {
throw "Could not determine the existing Herdr update channel. Rerun with -Channel stable or -Channel preview."
@@ -776,8 +822,9 @@ Write-Step "Installing Herdr $versionIdentity for $targetTriple"
$tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("herdr-install-" + [System.Guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Force -Path $tempDir | Out-Null
+$userPathChanged = $false
try {
- Invoke-WithInstallLock -LockPath $lockPath -Script {
+ $userPathChanged = Invoke-WithInstallLock -LockPath $lockPath -Script {
Remove-StaleInstallArtifacts -ReleasesDir $releasesDir
if (-not (Test-HerdrReleaseComplete -ReleaseDir $releaseDir -Format $asset.Format)) {
@@ -834,35 +881,52 @@ try {
Set-ManagedJunction -LinkPath $currentDir -TargetPath $releaseDir -ManagedTargetPrefix $releasesDir
Set-ManagedJunction -LinkPath $visibleBinDir -TargetPath $releaseDir -ManagedTargetPrefix $standaloneRoot -AllowLegacyHerdrBinMigration $allowLegacyVisibleBinMigration
+ $ownedPathEntries = @($visibleBinDir, $currentDir)
+ $userEnvironmentKey = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey("Environment")
+ if ($null -eq $userEnvironmentKey) {
+ throw "Unable to open the current user's environment registry key."
+ }
+ try {
+ $pathChanged = Update-PathRegistryEntry `
+ -EnvironmentKey $userEnvironmentKey `
+ -Entry $releaseDir `
+ -OwnedEntriesToRemove $ownedPathEntries `
+ -OwnedEntryParentToRemove $releasesDir
+ } finally {
+ $userEnvironmentKey.Dispose()
+ }
+
+ $env:Path = Prepend-PathEntry `
+ -PathValue $env:Path `
+ -Entry $releaseDir `
+ -OwnedEntriesToRemove $ownedPathEntries `
+ -OwnedEntryParentToRemove $releasesDir
Remove-OldReleases -ReleasesDir $releasesDir -CurrentReleaseDir $releaseDir -Keep $Retain
+ return $pathChanged
}
} finally {
Remove-Item -LiteralPath $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
-$userEnvironmentKey = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey("Environment")
-if ($null -eq $userEnvironmentKey) {
- throw "Unable to open the current user's environment registry key."
-}
-try {
- $userPathChanged = Update-PathRegistryEntry -EnvironmentKey $userEnvironmentKey -Entry $visibleBinDir
-} finally {
- $userEnvironmentKey.Dispose()
-}
if ($userPathChanged) {
Publish-EnvironmentChange
Write-Step "PATH updated for future PowerShell sessions."
} else {
- Write-Step "$visibleBinDir is already first on PATH."
-}
-
-$newProcessPath = Prepend-PathEntry -PathValue $env:Path -Entry $visibleBinDir
-if ($newProcessPath -cne $env:Path) {
- $env:Path = $newProcessPath
+ Write-Step "$releaseDir is already first on PATH."
}
$resolvedHerdr = Get-HerdrCommandSource
-if (-not (Test-PathStartsWith -Path $resolvedHerdr -Prefix $visibleBinDir)) {
+$resolvedHerdrKind = Get-HerdrExecutableKind `
+ -Path $resolvedHerdr `
+ -ReleasesDir $releasesDir `
+ -CurrentDir $currentDir `
+ -VisibleBinDir $visibleBinDir
+$releaseHerdr = Join-Path $releaseDir "herdr.exe"
+if ($resolvedHerdrKind -ne "release" -or
+ -not [System.IO.Path]::GetFullPath($resolvedHerdr).Equals(
+ [System.IO.Path]::GetFullPath($releaseHerdr),
+ [System.StringComparison]::OrdinalIgnoreCase
+ )) {
Write-WarningStep "PowerShell still resolves herdr to $resolvedHerdr. Open a new PowerShell window or inspect PATH order manually."
}