Skip to content

winget packaging as portable prevents ssh usage #1180

@meop

Description

@meop

Observed Issue

Winget is packaging zoxide as a portable app.

This means that Winget puts a symlink here:
~/AppData\Local\Microsoft\WinGet\Links\zoxide.exe

The target is actually here:
~/AppData\Local\Microsoft\WinGet\Packages\ajeetdsouza.zoxide_Microsoft.Winget.Source_8wekyb3d8bbwe\zoxide.exe

This is fine for locally running zoxide, via Powershell or Nushell.

But when remoting to a Windows machine and targeting either Powershell or Nushell, neither will launch zoxide.
The error given is variations of "I/O error
╰─▶ × Could not spawn foreground child: The path cannot be traversed because it contains an untrusted mount point. (os error 448)"

Possible Workaround

I tried to just enable remote symlink evaluation, but it did not help:
fsutil behavior set SymlinkEvaluation R2L:1 R2R:1
Not sure if it needs a restart, or if it is just not the reason for the error.
It would also not be ideal to globally enable something like this anyway.

Powershell is able to bypass the symlink:

function Get-RealPath($n) {
  (Get-Command $n -Type Application | Select-Object -First 1).Path |
    Get-Item |
    ForEach-Object { $_.Target ?? $_.FullName }
}

function Initialize-Tool($n) {
  Invoke-Expression "function global:$n { & '$((Get-RealPath $n))' @args }"
  & $n init powershell | Out-String | Invoke-Expression
}

Initialize-Tool zoxide

Nushell is not able to, via its built-ins.. but it can wrap Powershell or Cmd, which can:

## cmd, works over ssh
# def get-realpath [n] {
#   let p = (which $n | get 0?.path?)
#   if ($p | is-empty) { return $n }
#   let target = (
#     ^cmd /c dir /a $p |
#       decode utf-8 |
#       str replace --all --regex '\x1b\[[0-9;]*[mK]' '' |
#       lines |
#       find "[" |
#       first 1 |
#       parse -r '.*\[(?P<t>.*)\]' |
#       get 0?.t?
#   )
#   if ($target | is-empty) {
#     $p
#   } else {
#     $target | str replace --all --regex '^[0-9;]*m' ''
#   }
# }

## pwsh, works over ssh
def get-realpath [n] {
  let ps_cmd = ([
    '(Get-Command ', $n, ' -Type Application | Select-Object -First 1).Path | ',
    'Get-Item | ForEach-Object { $_.Target ?? $_.FullName }'
  ] | str join "")
  pwsh -NoProfile -Command $ps_cmd | str trim
}

## nushell, does not work over ssh
#def get-realpath [n] {
#  which $n | get 0?.path? | path expand
#}

Then for Nushell, I can mutate what zoxide init produces, force it to bypass the symlink:

def initialize-tool [n init_args] {
  let p = (get-realpath $n)
  $"def --env ($n) [...args] { ^'($p)' ...$args }\n(
    ^$p ...$init_args | str replace --all --regex $"\\^($n)\\b" $"^'($p)'"
  )" | save -f ([$NU_VENDOR_DATA_DIR, $"($n).nu"] | path join)
}

## preserves external cmd calls, does not work over ssh
# def initialize-tool [n init_args] {
#   ^$n ...$init_args | save -f ([$NU_VENDOR_DATA_DIR, $"($n).nu"] | path join)
# }

initialize-tool zoxide [init nushell]

But I do not really want Nushell dependent on Powershell or Cmd, just to bypass a symlink

Proposed Solution

I am not sure if there are benefits to Winget packaging zoxide as a portable app, other than a simpler installer

I would like to propose that we produce a non-portable installer for Winget to use for zoxide, so that it installs to a real path somewhere, no symlink

This would put it more in line with how starship is packaged

Then I would not have to do any workarounds as above, which are complex and slow

I can help to create a pull request for this if it would be approved

Thank you

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions