diff options
| author | JJ Liebig <jonathan.liebig@gmail.com> | 2026-09-05 00:47:00 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-05 01:47:00 +0300 |
| commit | 8162e26509f7b91eb8dcfd47387d43af3f348bbb (patch) | |
| tree | dfbd1a95356f4aaecee5749e45ede7fc73271bca /distribution | |
| parent | a2b128978a154c02311b90da778ca115758f75d7 (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.ps1 | 170 |
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." } |
