microsoft/winget-cli

Installing a package doesn't add it it to path

matifali opened this issue · 144 comments

Brief description of your issue

Installing a package doesn't add it it to path

Steps to reproduce

  1. Install Vim using
    winget install Vim
  2. Try to run it using
    Vim test.txt

Expected behavior

Vim should open the file if it exist or create a new file.

Actual behavior

Vim : The term 'Vim' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included,
verify that the path is correct and try again.
At line:1 char:1
+ Vim .\test.txt
+ ~~~
    + CategoryInfo          : ObjectNotFound: (Vim:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

Environment

Copyright (c) Microsoft Corporation. All rights reserved.

Windows: Windows.Desktop v10.0.19041.450
Package: Microsoft.DesktopAppInstaller v1.10.42241.0

As I know, winget don't add to PATH automatically. But if the installer itself have ability to add its exe to PATH that means Switches have problem. I'm gonna look at it.


I'm look at it and vim installer don't have any installer arguments (and there is no installer option to add PATH too). You have to add yourself until this ability comes to winget. There is some feature requests about this issue. It's gonna be added.

Have a nice day! 🥂

Thanks, I came from linux and is used to installing packages with apt command. So I was expecting the same behaviour with winget.

I think that linux users benefits WSL as a package manager when native gui server comes with it. I use WSLGentoo and when gui server comes with it, I will migrate fully and only use winget in order to contribute it.

@matifali winget is still in preview. Hope it will soon grow like apt and other package managers. We have to wait.

I've been working on the backlog recently. You can take a look at the milestones to see what we're planning.

We do plan on having the ability to add the path for .zip, .exe, and standalone/portable apps. I'm not sure what we'd be able to do with an installer that doesn't provide a path. If we specify a path during "install" time for packages like this, we might be able to add that path to the environment. Is that what you would be looking for here?

@matifali this path problem is not because of winget-cli.

The actual installer of vim does not add vim directory to the path. So this is not an issue of winget-cli.

I've been working on the backlog recently. You can take a look at the milestones to see what we're planning.

We do plan on having the ability to add the path for .zip, .exe, and standalone/portable apps. I'm not sure what we'd be able to do with an installer that doesn't provide a path. If we specify a path during "install" time for packages like this, we might be able to add that path to the environment. Is that what you would be looking for here?

@denelon I don't know how you gonna handle this but something like this will be good I think:

  1. Determine or override default installer location.
    1. Either pass location: variable as keyword to all manfiests files and install them under "..\winget-pkgs\$pgkname" to easily find winget-pkgs.
    2. Or pass default installer location as location: keyword.
  2. Add shortcut: keyword to manifests.
  3. Add createshortcut: keyword (can be overridden with winget install vim --shortcut y|n )
  4. Run script and add environ vars under winget-shortcuts name with powershell script after each successful installation according to location and shortcuts from manifests. (I don't know how to create environ vars from powershell and I'm too lazy to right now to find it. 😆)

In the end it turns something like this (example for vim manifest):

Id: vim.vim
Name: vim
AppMoniker: vim
Version: 8.2.1484
Publisher: vim
Author: vim
License: Copyright (C) 1991-2020 Bram Moolenaar [Bram@vim.org] - Charityware / GNU GPL compatible
LicenseUrl: https://github.com/vim/vim/blob/master/LICENSE
MinOSVersion: 10.0.0.0
Homepage: http://www.vim.org/
Description: Vim is a highly configurable text editor built to make creating and changing any kind of text very efficient
Tags: "vim,gvim,vi,text editor,text editing,code editor,utility,tool"
InstallerType: nullsoft
Installers:
  - Arch: x64
    Url: https://github.com/vim/vim-win32-installer/releases/download/v8.2.1484/gvim_8.2.1484_x64_signed.exe
    Sha256: 02c7e62b4c712af927d2f0a9635ca7746072feab81b6071a00d95dea2a4ec654
    Switches:
      Silent: /S
      SilentWithProgress: /S
    Location: "C:\Program Files\Vim\vim82" # Default install location.
    Shortcuts: "vim.exe, gvim.exe"
    CreateShortcut: y

Is this essentially a duplicate of #222 ?

This isn't an exact duplicate, because opening a new shell doesn't change the behavior. the Application is still not in the PATH.

This isn't an exact duplicate, because opening a new shell doesn't change the behavior. the Application is still not in the PATH.

Makes sense-- it does not appear to be a problem with winget, but with the vim package; I guess the question then becomes how should winget handle packages that don't add themselves to PATH? Is this exclusive to the vim package, or have other packages shown this behavior too?

Rather than populate your PATH like Chocolatey, winget should add shims like Scoop(UWP also has this feature)
#361

@matifali this path problem is not because of winget-cli.

The actual installer of vim does not add vim directory to the path. So this is not an issue of winget-cli.

Yes I understand But wiget-cli should not accept such packages in repo if they don't add themselves to path.

Is there any due diligence performed on the items in the winget repos? Does Microsoft do any validation of applications??

Is there any due diligence performed on the items in the winget repos? Does Microsoft do any validation of applications??

Most probably no.

Rather than populate your PATH like Chocolatey, winget should add shims like Scoop(UWP also has this feature)

@Witchilich Sorry to jump in. I just wanted to note that Chocolatey brought about the idea of shims not to clutter your PATH - it existed long before Scoop was created as a concept called batch redirects. Later we moved to shim exes. Luke thought shimming was great and added it to Scoop. Just want to make sure the credit is in the proper place on shims.

References:

I think your confusion might be in that Chocolatey might defer to an installer as well, which might put things on PATH. Scoop doesn't use the installer at all, it just unpacks, so the shims are much more necessary. Chocolatey's docs on this - https://docs.chocolatey.org/en-us/features/shim

I think windows already has its own version shim, if you look up wsl.exe, wt.exe. they are in windowsapp folder

So I’m a complete idiot. How can you figure out where vim actually installs. I’ve tried winget list vim and can’t figure it out. Do I look at the source. Same issue with winget install git by the way.

Is this still pending? Does anybody know a workaround? edit*: regarding vim

Being able to easily install vim with winget should be a core use case of winget.

I just tried winget for the first time on a brand new windows 11 system from dell and encountered this.

  • entered winget install whois ,
  • then restarted terminal / powershell,
  • entered whois and received whois : The term 'whois' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

This is completely unacceptable and an absolute non-starter for a package manager. this is the application level equivalent of installing an operating system without adding it to the bootloader. fixing this should be priority 1. thank you

workaround:
After installing vim.vim with winget, the quick way to add it to path:
right click start button, select run
run the following command: systempropertiesadvanced
go to Environment variables, select Path
[Edit...]
[New]
C:\Program Files (x86)\Vim\vim82\

Since the vim version number is used in the directory name, you might have to find the location of newer vim versions by searching for it in the start menu: right click 'open file location' and adjust the path accordingly

restart the command prompt/windows terminal for it to take effect

While that would surely work and the flow could be reduced to a PowerShell snippet I'm not sure about the manageability of adding hundreds of packages to the path (my workstation has a bit more than 110 packages, although not all should be in the global path).
I should note that chocolatey, which I used previously, creates a «shim» exe in a global directory that is added once to the path.

That approach seemed to work well for me.

a-gn commented

Rather than populate your PATH like Chocolatey, winget should add shims like Scoop(UWP also has this feature) #361

The shims have the advantage of not needing to update every program's environment after installing a package. This would help us implement CI/CD pipelines with Winget.

SO, as a stupid user. how can I run nvim after I just runed winget install neovim extacly same as any unix system??

m0rg commented

lol.

se316 commented

Being able to easily install vim with winget should be a core use case of winget.

+1, I was delighted to learn that a Windows package manager exists but alas. The minimum expectations I have for a package manager are:

  • Be able to search a repository for different packages.
  • Be able to install , update or uninstall those packages.
  • Have those packages be immediately callable after installation.

The third point is what I did not see after installing Vim. Since I am only installing a single package it is feasible to manually add it to path but winget still has a ways to go as a go-to resource.

I do think it is the role of the package manager to add installed packages to path automatically and like this suggestion if it's compatible with the other points

We do plan on having the ability to add the path for .zip, .exe, and standalone/portable apps. I'm not sure what we'd be able to do with an installer that doesn't provide a path. If we specify a path during "install" time for packages like this, we might be able to add that path to the environment. Is that what you would be looking for here?

Appreciate the hard work on this and looking forward to seeing winget reach its potential! Just throwing in my two cents to show that this is a looked-for feature and there is community interest in winget.

I'm very frustrated

Winget is shockingly close to being one of the most valuable programs on windows. However If a console program can't be called after installing it, it should not be included in the packages. Winget is a terminal command, and the things it installs should also be accessible. If I can install console programs programmatically and can't use those programs programmatically it really has no value in any sense. This is bundled with the paid product Microsoft Windows™ and provides critical functionality that prior Windows™ desperately lacks. I was embarrassed at work when I told coworkers about winget. Even git didn't work and I have no clue how to make it work.

  • I found the product backlog (thank you @denelon), why is this on the backlog? I can't find a single issue on the currently being worked list that I care more about than this issue. For me winget works fine, it just isn't usable for anything of value yet.
  • Why is it not a priority? This can be done, other windows package managers currently do it.
  • Where did git get installed and how do I find it? (I normally wouldn't even care, but it's not in my path, so I need to add it to the path somehow)
  • If its a manpower problem can we please hire more people to work on it? I think winget OUGHT to be installed by default, as it's critical infrastructure, but it should also work.

Hello @denelon , trying to give winget a second chance. I thought well maybe it's not really for terminal programs. I hit a similar (less impactful) issue again today where I followed the guide at https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.3 where I ran winget install --id Microsoft.Powershell --source winget and it installed... but initially I couldn't find it at all. I was able to find it by searching for powershell, as "powershell 7".

I'm not used to new versions installing as separate programs. Typically when I install a program it actually replaces the older version unless otherwise specified. If I use winget to provision machines, people are going to run the old old version of powershell that is still there because the old version comes up first in the search. When I call the powershell command, it still calls the old powershell. It feels like the team isn't dogfooding winget to provision their machines, because this tool is missing very simple and very critical comforts.

Normally I would make a new issue for something like this, but I'm afraid you would prioritize it over fixing the paths issue, and if that's the case I don't want this fixed. I would pay a fair bit of money to stop all other issues until this issue is fixed since it severely limits the default windows experience especially compared to the competition.

It's not a issue with WinGet, that's how PowerShell is handled even if you manually use the installer. They are installed as separate programs because the version from GitHub can't replace the system version without being handled in special ways. Having it as a separate program allows for faster upgrades and other things that would normally have to be locked behind the normal update cadence

Alright, thanks @Masamune3210 . I think that's a really dumb decision by msft that it's not possible to update powershell but also clearly not winget's fault here.

As an aside, that I guess I'll have to bring up in the powershell repo? I get that Microsoft might need a fixed version of powershell behind the scenes but I feel like bare minimum I should be able to update powershell for user space, where when I call powershell it uses the newest powershell, and the shortcut for the old old powershell no longer shows up... It's just bad UX. It gives the impression that it didn't work, since you open powershell and it's still old. Call the powershell command, still old.

@voronoipotato thanks for the detailed feedback here.

We've been discussing several challenges associated with required environment variables like "path". We implemented support for path in the portable install flow, but we're still dependent on the package installers to handle this correctly today for other installer types. Part of the "upgrade" challenge is also associated with what type of an installer is actually being used.

MSIX packages upgrade natively (although there are still some bumpy experiences like upgrading winget by running winget upgrade "App Installer" -s msstore). For MSI "upgrades" we use the Product Code or the Upgrade Code to upgrade the "same" package. Traditional ".exe" based installers are essentially still much more challenging as they might do anything.

As there aren't graceful ways to deal with switching installer technology, we prompt the user when the installer type changes, and inform them that they need to uninstall the version on their system before installing the newer version. We've added keys to the manifest for "uninstallPrevious" when we know the earlier version should be removed, but in some cases, a side-by-side install is "by design".

We're adding extra metadata to manifest so we know what the "default" path and "default" program are. Something like that may lead us to a point where we could decorate a manifest such that we know the installer doesn't add the path, so we could inform the user that it's something they could/should do. Changing environment variables will still require a shell restart or a reload environment variables to actually function. That's likely something we would still want a user to actively do so we don't inadvertently break something else running on the system "at the same time".

@denelon I'm going to talk a little bit about git, as a way of talking about strategy with installers. Git appears to have silent/unattended installation flags. https://github.com/git-for-windows/git/wiki/Silent-or-Unattended-Installation Does winget currently support the ability to use those? Or we could use the portable similar to how scoop does https://github.com/ScoopInstaller/Main/blob/master/bucket/git.json . It feels like critical packages have been broken for months or years, and that's really very bad for winget. If there's an incorrect way to do something, people will do it. If software that people rely on is provided in non-working condition, it will erode and eventually destroy winget. Nothing is more important than trust with a package manager.

I still have not "bought-in" to trusting winget, but the value it could provide if I can use it to quickly script installations on a developer's machine or server is incredibly obvious. If anyone doubts it, tell them to try Fedora or any other major Linux distro for a day where any software from any provider is available with ease. I can add external repositories from microsoft, dell, or anyone I want, as well as using a well curated list of packages that are certain to work well. It's something that I miss a lot every time I go to a windows server or my work computer.

Yes, today, WinGet uses the "known" installer types like inno, nullsoft, wix etc. as hints to determine things like installer switches for silent or "silent with progress" (our default mode). We also support switches for those modes when they are unique (this is very common with traditional .exe-based installers). They may also help with knowing how to do a "user" vs. a "machine wide" install.

For Git, winget install git.git --silent or winget install git.git -h should perform a silent installation without any output.

One of our critical goals early on was to meet developers (packages) where they are, and not to require them to be rewritten or to have to completely rewrite their installers. That does lead to challenges when the installer doesn't have parity in "silent" with what is supported via "interactive".

I see this as a bit of a "chicken and egg" challenge. We need a package manager as a bit of a forcing function to establish best practices. As you are stating, if the packages aren't ever "fixed", or the package manager can't reason about them, then it's a bad experience. We have taken the approach to assert rational "default" behaviors, and then extend with settings and arguments so the desired outcomes can be achieved. Today we have "--override" which allows overriding arguments WinGet defaults to with the manifest/installer type so users can specify the arguments they want. We also have an ask for an "augment" type of behavior so we can preserve the default switches and simply "add" to them.

So can we provide those flags in the installer.yaml, or is that not implemented yet? For example with Git, it would allow the path to be provided in the default experience.

If there are arguments that set the path correctly, then yes, they could be added to the manifest. It may still require a restart of the shell or the environment variables to make it available. You can test locally with "-m ". In some cases, those arguments haven't worked when the install location was modified by the user.

SO, as a stupid user. how can I run nvim after I just runed winget install neovim extacly same as any unix system??

On Windows 10, Neovim was installed on C:/Program Files/Neovim and the launch executable is C:/Program Files/Neovim/bin/nvim.exe. I had to restart the command prompt to be able to invoke nvim to start Neovim.

I just tried installing a package with winget. It was relatively easy to use winget search to find the package and winget install package similarly "just worked".

Then I got very sad when apparently it didn't "just work" and my package was seemingly not installed. It's not on the path, and since I have no idea where it got installed, it may as well just not have been installed at all.

This makes winget pretty much useless. Please prioritize this issue ASAP. Until this is fixed, I don't see any value in winget.

@Victor-N-Suadicani which package are you referring to? Can you share the WinGet logs?

Just trying to install vim and git with winget. How is this so incredibly more difficult than on Linux? How do you figure out what path to add to Windows? And even once found, the process to add a path on Windows?!?! Ridiculous. Agreed, winget is useless.

@Victor-N-Suadicani which package are you referring to? Can you share the WinGet logs?

@denelon It was GnuWin32.Make in my case, but clearly it happens to many other packages as well.

It's up to the installer to add the package to the path (unless it's a portable package in the community repository).

Is there a way to force PowerShell or whatever happens to be the terminal that is being used to grab a fresh environment block or at least a fresh copy of the path var? I'm assuming a lot of this comes from the fact that it never gets updated except for when its first launched

That would probably be my next guess as the best way to handle this, since most people seem to just be confused why their portable program doesn't just magically immediately work after installation, maybe have it as the last step if winget knows that a path registration is needed

It's up to the installer to add the package to the path (unless it's a portable package in the community repository).

Why is winget different in this respect to practically all other package managers? Every user is going to expect winget to add the package executable to the path. Not doing that is tantamount to a bug, especially from the perspective of a user.

I assume you are comparing the Windows Package Manager against Linux package managers. There are some very subtle differences between Linux and Windows. The Windows registry and the various "public" and "private" APIs add a bunch of complexity (read constraints). There is a long history of building an "installer" for Windows whether it's a custom .exe-based installer, or MSI, and more recently MSIX. These are of various different quality. They also generate several different "classes" of behavior that we need to be able to reason about.

Given an arbitrary installer, there could be any number of behaviors it supports (or not). Some installers don't add the executable location to the path, and WinGet doesn't "know" this information beforehand. We also don't want to go down a path where we're writing branching logic for each version of any available package. That has led us to building a more robust manifest to deal with many of these variations.

We've been discussing the notion of refreshing the path (or any other environment variables) after install or upgrade, but we don't necessarily know which shell we're being executed in to know which variables to refresh. There are differences between cmd.exe and PowerShell so there is no "one size fits all" solution there. There are other shells that handle things differently so we wouldn't know what to do for them either. Even if we were to add a prompt to "refresh the path", it might not apply in all cases.

Best practices include building a better installer (which isn't viable in the short term for many organizations) and testing the experiences with WinGet. Different packages (publishers) make different assumptions about their users, so the advent of Windows having a package manager is still "new".

@denelon Upthread, there are comparisons against Scoop and Chocolatey, both of which are Windows package managers. They both solve the "add-to-path" problem, albeit in different ways. So there are two existing solutions in the Windows ecosystem.

Scoop uses Junctions, and even chocolatey runs into the very same issue that winget is running into. Hacky solutions exist, but the hope is and should be to avoid them unless absolutely necessary
image

If closing and reopening the shell worked that would be an outstanding improvement over the present situation . I can understand that these are difficult compromises, but if avoiding them means a completely broken experience, then I'd say your definition of "absolutely necessary" needs to be reevaluated. Scoop currently works, so I'd strongly disagree with your characterization of this as "the very same issue". This will unambiguously kill winget in the long term if this issue is not fixed. We all want winget to succeed, and we should not put our heads in the sand and pretend there aren't currently working solutions to this. We may not want to choose the existing solution, that's fine, but this project is broadly doomed without a solution. It's not great that windows is decades behind the competition in automation, and this is even more extreme for windows server where the need is even more important.

image

This is after I closed and reopened powershell. At the very least allow me to run the winget installed programs by doing winget run program , while I would be very unhappy with that as a solution it would at least allow it to be possible to use the things I've installed.

@voronoipotato I'm not familiar with neovim. If you download and "run" the installer, does it add itself to the path? I've seen a couple of cases where the "interactive" installer has an option to add the directory to the path, but when run "silently" it doesn't. I'm not sure if that's the case here or not. If it's just a switch that needs to be passed, the manifest can be updated to include that switch for "silent" and "silent with progress" variations of install.

I just ran winget install neovim in Windows Terminal (PowerShell) in user mode. I agreed to the UAC prompt. After it succeeded I closed and reopened Windows Terminal, and it appears to have worked.

nvim

@denelon Ah, I closed the individual tab of windows terminal without closing the whole program. Very strange behavior but that worked, thank you. I had expected that each tab ran independently. I wonder how many other people here ran into the same snag.

@Masamune3210 That's technically not quite the same issue, since the screenshot you show is for installing Chocolatey itself. Once you've installed it, it provides a function (aliased as refreshenv) to reload your env vars, and the installation text output prompts the user to run it after something new has been installed.

FWIW, from a user perspective, I have no problem with junctions. It's a bit annoying that they require admin permissions to create (IIRC), but otherwise I've been using them without issue since...probably 2015ish, I think.

@Victor-N-Suadicani
Why is winget different in this respect to practically all other package managers? Every user is going to expect winget to add the package executable to the path. Not doing that is tantamount to a bug, especially from the perspective of a user.

I think the cause of confusion here is the fact that winget supports multiple installer types. When you have a non-portable app (e.g. msi, inno), it is the installer's responsibility to add what's necessary to PATH. The entire point of an installer is to setup the app completely, including updating the PATH if needed. Even if winget wanted to work around that, some installers are just black boxes where retrieving the installation directory itself is a challenge.

The situation is different when you have a portable app (e.g. portable, zip) since if the app is portable, then winget itself becomes the installer, which means it's now the winget manifest's responsibility to know which executables to make available in PATH. Portable app support of winget is relatively new (#182), and according to the following comment, it now supports adding the new app to PATH (technically, it uses shims like Scoop instead of touching PATH with every installation, but the result is the same):

@denelon (in original thread)
...Windows Package Manager 1.3 release candidate use symbolic links for portable applications to avoid cluttering a user's path environment variable...

Implementation-wise, I believe shims are indeed the way to go as using them doesn't require the PATH to be updated with every app and it also makes the new app immediately available without restarting the shell. The use of symbolic links on the other hand sounds a bit odd considering they require elevated access to create, which is why Scoop uses junctions instead (here's a related complaint: #2802).

Also, I can't find anything in the schema regarding defining what gets a shim when you have multiple executables you want (or explicitly do not want) in PATH.


The tldr; is making applications available in PATH (with or without shims) is winget's responsibility only for portable apps. If a non-portable app (an app with an installer) needs to be called from the console, then its installer needs to update PATH accordingly.

I appreciate that this would sound more complicated than necessary to folks coming from Linux. Unfortunately, Windows app ecosystem has been all about GUIs and installers where you had to keep clicking "next" until something is installed, mostly due to the lack of good CLI apps and package managers. So, it's now up to the app creators and the community to add proper support for winget. App creators can either fix their installers to add their CLI apps to PATH, or better yet, publish their apps as portable binaries (single exe or zipped) so that winget could automatically make the app available after installation.

What winget needs to do. I think, is extend the manifest so we can specify which executables we want (all? some? none?) in PATH, but again, this only applies to portable apps where winget itself acts as the installer.

I think the cause of confusion here is the fact that winget supports multiple installer types. When you have a non-portable app (e.g. msi, inno), it is the installer's responsibility to add what's necessary to PATH.

I'm honestly surprised winget bothers supporting non-portable apps. Why is that? I would not expect winget to support msi installers, yet it does. This is surprising. If I wanted to install something with an msi installer, I would expect that I would have to run it manually. I wouldn't want winget to run an installer like that for me.

I think the cause of confusion here is the fact that winget supports multiple installer types. When you have a non-portable app (e.g. msi, inno), it is the installer's responsibility to add what's necessary to PATH.

I'm honestly surprised winget bothers supporting non-portable apps. Why is that? I would not expect winget to support msi installers, yet it does. This is surprising. If I wanted to install something with an msi installer, I would expect that I would have to run it manually. I wouldn't want winget to run an installer like that for me.

On the contrary! I think it's quite nice that multiple installer types are supported because that way I can actually use winget to install nearly everything and even provide invocation flags for the installers to fully automate them. See for example here what I mean.

I feel that the main purpose of a package manager is to provide portable packages, not to automate or configure installers. Perhaps this functionality should have been kept behind a flag or something to avoid the confusion that occurs when users install something that then isn't actually available in the command line once installation is complete.

@Okeanos It would be a good idea if it worked that way out of the box. It's the only package manager I've ever seen where you must provide bespoke override flags for individual packages to install as expected. Packages should work without --override or they should be removed until they are fixed.

Any package manager I can currently think of (homebrew, apt, yum, dnf, …) allows installing complex, non-portable software. Even Scoop and Chocolatey allow that.

The overrides and additional flags I use for e.g. Git and Firefox in my example I use by choice because I wanted to maximise automation for my use case. If I wanted to do non-default things with other package managers I would have to similarly provide instructions.
So yes, I add the completely optional flags so it installs "as expected". That is expected by me. However, my expectations are likely very different from other people's expectations.
Take the Git example – there's a huge number of flags and settings users are asked to modify and choosing the "right" one for everybody is effectively impossible. winget will defer to the vendor defaults during installation if nothing else is specified and "just work". If you want different defaults that'd be on the original installer's maintainers, not winget.

**yigitemres ** commented Aug 25, 2020

What happened to @yigitemres's idea? This is where everyone is correct but too far into the infrastructural weeds.

Some simple interface emitting to the effect of "winget could not confirm an installation or path location and it's possible the installer cannot either, here are some options and info, ie run THIS COMMAND and restart your terminal"

I heard @denelon talk about how a design goal was to make it easy and inviting for package maintainers - how about the same courtesy for users of the tool? and by users, I mean at the API level. I think we are okay with some flag argument configuration, but this seems like an easy win.

I think it's hilarious there's comments about "MS is different than linux", lol then why make the tool at all? it's clearly a clone of Apt/etc (which is a good thing!).

Hey all, I've been talking with the team about adding some additional information after installing a portable package. The behavior depends on a few different factors. We're looking at better messaging as a hint to let the user know if they need to restart their shell/terminal or not.

In most cases, the first portable install will require the shell/terminal to be restarted.
For portable packages installed in user mode (and developer mode disabled) the shell/terminal restart will most likely be required.
For subsequent portable packages installed in admin mode the shell/terminal restart will most likely not be required.
For subsequent portable packages installed with developer mode enabled the shell/terminal restart will most likely not be required.

In this example (Vim), and with many "installers"; restarting the shell/terminal will be necessary.
In many cases with upgrade (assuming it's an upgrade to an existing package and not a side-by-side installation); restarting the shell/terminal will not be necessary.

We don't have a good reliable mechanism for dealing with a refresh of the Path environment variable as we could be called by any number of shells that all deal with environment variables a bit differently, and we aren't given the context necessary to figure out exactly what needs to be done in all cases.

We do have additional affordances possible with PowerShell and the WinGet cmdlets that may make it better, but that's not the case with cmd.exe and many others.

@denelon I think I may be hitting a flavor of this. I just installed ripgrep (winget install ripgrep). Even after restarting wt/pwsh, it's still not on my path. I do however see it in my user environment variable's PATH. Any idea what gives?

Do you have developer mode enabled on your machine?
Did you install via administrator or user (Terminal, PowerShell, CMD.exe).

If it appears in your user environment path, it most likely means you didn't have developer mode enabled or use an administrator shell.

I was going to test, but I get two results. Which did you install?

winget search ripgrep
Name         Id                      Version Source
----------------------------------------------------
RipGrep MSVC BurntSushi.ripgrep.MSVC 13.0.0  winget
RipGrep GNU  BurntSushi.ripgrep.GNU  13.0.0  winget

@denelon I didn't have dev mode enabled, but doing so didn't change it. I installed it as a user in a non-admin windows terminal instance using powershell.

winget install BurntSushi.ripgrep.MSVC

The first time I tried this it added the full path to %PATH%:

C:\Users\$NAME\AppData\Local\Microsoft\WinGet\Packages\BurntSushi.ripgrep.MSVC_Microsoft.Winget.Source_8wekyb3d8bbwe\ripgrep-13.0.0-x86_64-pc-windows-msvc\rg.exe

This time it added a symlink here:

C:\Users\$NAME\AppData\Local\Microsoft\WinGet\Links

But when I restart the shell it still isn't at the end of the path, either in wt or cmd.

Ah. I wonder if this is tripping on needing a logout/in for user environment changes? That's annoying if so since system path just works..

The critical timing for the symbolic link creation is when you install the package. Once it's installed, it should either be in the path with the symbolic links, or as a path entry in the environment variable. Unfortunately, a new terminal Window isn't sufficient to get the environment. The entire Windows Terminal needs to be restarted if you're using Windows Terminal.

@billwert interesting that my winget (version v1.5.441-preview) doesn't do either of those things lol. it added C:\Users\username\AppData\Local\Microsoft\WinGet\Packages\BurntSushi.ripgrep.MSVC_Microsoft.Winget.Source_8wekyb3d8bbwe\ to my PATH, which obviously isn't the correct path since, the executable is one level deeper in ripgrep-13.0.0-x86_64-pc-windows-msvc.

I also wonder when those symlinks are created. it did for yt-dlp, but I installed that with an older version of winget, so maybe that's why?

@zeratax That is related to #2909 and the fix for it was merged in #3002. The fix will likely appear in the next release for WinGet.

I think you need to see the product by

winget search vim

Then you choose the product that you like to install, this applies to search any other application

I would really like some movement on this. This issue defeats the point; if I cannot immediately use a command after installing with winget, then I'm not even going to waste the time. It's easier for me to just click through an msi installer.

...You cant use a command after installing it through a msi either. The issue isn't just with winget not adding to the path, the issue also lies with Terminal, Powershell, and the legacy command prompt all also not adhering to any new path changes due to only picking up environment blocks at launch and having no automatic way to refresh it if changed.

This has been a challenge for decades. If you are in a Windows process (eg in Powershell), and run an MSI that adds to the Path environment variable,, unless you do something yourself, your copy of that environment variable;e is not updated. You just restart the process (eg PowerShell).

I mean it's not impossible to do, Winget could do it.

It's not a winget issue though. Anything done would be a hack at best. Terminal already has a issue open about refreshing the environment block iirc

If it were easy, we;d have had a solution years (decades?) ago. I would argue winget is working as designed and this is not a bug.

If it were easy, we;d have had a solution years (decades?) ago. I would argue winget is working as designed and this is not a bug.

I agree with this. I forgot how powershell and terminals in windows are supposed to work, hence why I (and probably most people) got here.

It's not a winget issue though. Anything done would be a hack at best. Terminal already has a issue open about refreshing the environment block iirc

@Masamune3210 A hack would be vastly better than the abysmal and confusing experience we have. If you're going to pass the buck to a different team, you should link to the related issue. The closest I could find was an improvement for refreshing environment variables, which could be useful to us in implementing a fix but the fix needs to happen from winget's side. Most of the time people are not refreshing the environment variables, so it does not make much sense to do it constantly. Additionally the proposed solution of kicking the responsibility to terminal does nothing for people using windows server.

microsoft/terminal#15102

@doctordns This is almost always false. Many problems are not addressed not because they are hard, but because they are lower priority. Software development has a triage process and breaking bugs and issues get first priority. This can mean that important but not required usability improvements go unaddressed, and if long enough even forgotten. User experience issues tend to be the items that are disruptive to use, but nonetheless never get fixed. We blame the users for being incompetent and move on. This sounds great until we ourselves are the users, and we ourselves have to remember little strange quirks for 80 different small applications that we use once every 3 years. I could understand that maybe it doesn't refresh env on git-bash or whatever but it should definitely work on powershell.

# too hard??? Really????
$Env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")

@Hathoute If your users are routinely falling into a confusing trap, you should fix it. Winget in windows is supposed to work in an unsurprising way. This behavior is very surprising, as this thread indicates. We can say this is how it does work, but it is not supposed to work this way.

Also, FWIW, microsoft/terminal#15102 is not the solution to this issue. That's tracking a perf improvement to the way that Terminal reloads env variables in 1.18+. In 1.18, the Terminal will reload environment variables when making a new tab (so you'll no longer need to quit out of the Terminal and restart). You'll be able to just open a new tab (or restart the connection).

But ultimately, there's no way for the Terminal to force a shell (cmd, pwsh) to update its own environment variables.

@voronoipotato :

# too hard??? Really????
$Env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")

Even supposing that were possible (you would have to do that to a different process (the shell process)), you would be surprised how much stuff that would break: there are many systems out there that depend on environment customization from the parent process (so reading fresh values straight from the registry would wipe those out).

So what you would have to do is compute a diff: read the Machine and User values straight from the registry, do the install, read the values from the registry again, and compute the diff (and assume that if anything else was messing with the registry values concurrently, "it's fiiiine"). But then you still have the problem of adding the new stuff to the correct shell process.

However, your posting of PS script gives me an idea of a hack/workaround that may be both "good enough", and not so hacky/gross as to disqualify it as a legit step forward:

What if winget.exe also shipped with a winget.ps1 file next to it? When in powershell, if a user were to run winget, they by default would get the .ps1 before the .exe... so then the .ps1 could handle doing the path diff'ing and apply it in-process... @denelon, what do you think of that?

If it were easy, we;d have had a solution years (decades?) ago. I would argue winget is working as designed and this is not a bug.

Oh it's too difficult to solve. OK, nevermind then; didn't realize this would be hard to solve. Let's just forget the issue and deal with it then.

@jazzdelightsme,

That's an interesting idea. It's something we could experiment with.

The real challenge is the App Execution Alias for winget might get resolved before the .ps1. If we're running a PowerShell cmdlet we might be able to do that more cleanly than in the CMD.exe shell.

The real challenge is the App Execution Alias for winget might get resolved before the .ps1.

Ugh, I was thinking of resolution preference between e.g. .ps1 and .cmd (where .ps1 would win), but you're right that it would be a problem for .exe versus .ps1; my bad.

But having a winget command exported from a PowerShell module would take preference. It would have to be a "simple function" (as opposed to an "advanced function") so as to be able to function as a completely transparent stand-in for the winget.exe command.

However I think it is still worth considering dealing with %PATH% order, in order to also provide a similar .cmd solution for cmd.exe users. I.e. if we put winget.ps1 and winget.cmd files into C:\windows\system32, they should be ahead of the windowsapps alias EXEs on the PATH; and then if a user just says "winget" (instead of "winget.exe") then the right thing would magically happen in either shell. (Nobody likes putting more stuff into system32, but I think it is justified in this case.)

Could the exe possibly detect what terminal its running in and call into the .ps1 if it detects powershell?

Sadly, no. There is a distinction between the shell used to communicate with the kernel and the terminal used for rendering input/output. The winget.exe cli is being called in a process, and it doesn't have a reliable way to look up and see what called it. There are special cases that allow us to know when the PowerShell cmdlets are being invoked by virtue of the code being in a PowerShell module so that may be one area where we can relatively easily enlighten ourselves.

I'd be okay with a minimum viable product of "it works with powershell" , with cmd fix provided later. I could tell my team, "Hey this is a temporary thing, but the experience is less confusing if you use it in powershell". It's not perfect but as a workaround for the immediate term it would still be very valuable. Thank you @denelon for being consistently supportive.

First, thank you so much for all the efforts made in this space. I want to share my opinion about this. As a first principle, making the package manager seamlessly install and make available the applications is its fundamental main use-case. Anything that hinders or adds obstacles to attain this goal is a barrier for adoption and a risk to the success of the project.

Think about your own out of the box experiences and how they affect your perception of a product.

I want to contribute to this conversation with some technical details that can assist with the implementation of such a feature:

To check if you are running on CMD, Windows Powershell or Powershell 7

(dir 2>&1 *`|echo CMD);&<# rem #>echo ($PSVersionTable).PSEdition
# Returns one of: CMD, Core, Desktop

image

To update the PATH variable from withing a CMD prompt

MSDN says:
To programmatically add or modify system environment variables, add them to the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment registry key, then broadcast a WM_SETTINGCHANGE message with lParam set to the string "Environment".

This allows applications, such as the shell, to pick up your updates.

To reload the environment variables in powershell

$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") 

To set the PATH variable in all unix systems

export PATH=$PATH:the/file/path
set PATH=$PATH:the/file/path

The current behavior is not customer friendly since it doesn't even show a warning about this issue, I would recommend as a stop-gap to add a yellow warning with the description of the current behavior (which is not to set the PATH) and describe in detail what are the manual steps that must be taken to overcome this pain

As a bit of an explanation of why this is an issue with winget that other package managers don't have:

Unlike other package managers, winget doesn't have a package format like .deb, .rpm or .msix. Instead, what winget uses are the existing installers for applications, like .msi or .exe files. I remember seeing an issue that it shouldn't be called a "package manager" because of that.

With a package file we know the contents of and we manually place on the system, it's relatively easy to add a single directory to the path the first time, then put links there pointing to the known location of the installed executable. Then everything works right after installing since the new file is already on a directory on the path. And this is actually what happens with portable apps in winget.
But with an existing installer, we'd have to know if the installer needs something added to the PATH, whether it does it on its own, and then re-load the environment to have the PATH changes take place.

Another issue is that on Linux there are more standard paths that are already on the PATH, like /bin/, so you just put something there and you don't even need to edit the PATH. But on Windows, apps tend to go under C:\Program Files\<app>\, so you need to add the specific app to the PATH. (Though this wouldn't be a problem if we had actual packages.)

Doing things this way does cause problems, like this, or not being able to ensure clean uninstalls. On the other hand, it lets us work with existing apps without having the publishers repackage them. It also lets us manage apps that one has already installed on the machine, instead of only the ones installed through winget.

BTW, Windows already has a package manager/package format that guarantees a lot of the stuff one would want, for example clean uninstalls or having commands immediately available. That is MSIX, but it requires some effort to re-package an app into that format, so many popular apps are not available in that format.

Here's a full write-up of the key design constraints that I observe for a solution to automagically update the PATH environment variable in a user's shell, along with my suggested approach.

TL;DR: if you want to try my suggested solution, put the files at the bottom of this post into your C:\windows\system32 directory, and as long as you always say "winget" instead of "winget.exe", things will probably work as you expect (environment will be updated) in cmd.exe, powershell.exe, and pwsh.exe.

Also, first some important caveats:

  1. I do not work on the winget team, nor do I have special visibility into their scheduling/priorities. I believe the standard place to point to for the question "when will this be addressed?" is this roadmap page and the milestones page.
  2. They may choose a different approach (though it will necessarily be constrained as described below), and they may face additional design constraints or challenges (for example, though you can try my solution by dropping a few scripts into your system32 folder, I'm pretty sure the DesktopAppInstaller package can be installed without elevation, so they likely will not want to use that location). Though my proof of concept works great for things like nvim, I know that they face much more complicated situations/packages, too.
  3. There may be additional work for them to do; for example, to figure out how to expose this as an experimental feature in order to evaluate the impact on a very large and diverse ecosystem.

With all that said, let's jump in.

The crux of the problem is that environment variables need to be updated in the user’s shell, which is going to be a different process than the winget.exe process. This leads to the main constraint for any possible solution:

Requirement 1: somehow, someway, there will need to be code that runs in the shell process.

Where does that code come from? One could imagine that it could be built into the shell itself: for instance, if pwsh.exe itself created a hidden window, in order to listen for WM_SETTINGCHANGE messages; upon receipt of said message, it could refresh its environment block.

But there are some problems with that:

  • Not all installers will broadcast a WM_SETTINGCHANGE message.
    • We could work around this by having winget.exe blindly broadcast it.
  • I’m not sure how well this might work in a service session (Session 0), where Windowing operations are more locked down.
  • Once the message comes, we know “something changed”, and we need to “refresh” our environment… but what exactly should we add? I will explain more about this below, but the most solid solution must do some kind of “diff” operation, and with the one-shot "something changed” broadcast, there is no clear opportunity to capture the “before” state.
  • Probably most importantly: not all shells will be willing to make this change.
    • In particular, cmd.exe and powershell.exe are incredibly unlikely to even consider such a change. They are frozen.

So in my mind that idea (code being built in to the shell itself) is a non-starter.

More about the “diff” problem: it is absolutely not a simple operation to “just refresh the PATH environment variable”. The PATH variables (I’m thinking specifically of PATH and PSModulePath) are stored in the registry, but you can’t just read the current registry value and say “well, that’s the current path!”, because it’s not. Many shells and environments, including powershell.exe and pwsh.exe, modify their PATH environment variables at runtime, depending on myriad and complicated factors. (Want a headache? Just try to figure out how powershell.exe and pwsh.exe heuristically update PSModulePath at runtime (it can become… problematic).) And it’s not just the shells themselves; many shell-based “operating environments” also perform heavy and critical customization of PATH-related environment variables. So you absolutely cannot “just read the current values from the registry” and apply them in-memory. Doing so would break approximately a million things.

So how could we update the environment, in as safe and non-breaking a way as possible? The critical thing is to not mess up any in-memory customizations, so ideally we just tack on the bare minimum of “what actually changed” onto the very end. (It’s possible that an installer does something super fancy, and purposefully updates %PATH% to stick something new in the middle, before some other paths; but I think that’s a rare case, and getting it wrong by updating the current process’s PATH to add the new thing at the very end is at least still not disruptive to any other existing in-memory customizations.)

This is what gives rise to:

Requirement 2: There has to be a “diff”: we need to know what actually changed, so we can add just that to the end of the current, in-memory value.

So we have to compare a “before” and “after”… and with the WM_SETTINGCHANGE broadcast there is no clear “before” point. Trying to compare an in-memory, potentially highly customized value to what’s in the registry, with no frame of reference, is doomed to be extraordinarily fragile and impossible to get completely right. Like a three-way merge without the “base”.

Okay, back to requirement 1: we have to have code that runs in the shell process. How can we do that? I see only two options:

  1. Invasively inject a thread into the shell’s process.
    a. This is a nuclear option. The CreateRemoteThread API is one of those scary APIs with documentation that says things like "the application can deadlock if the thread attempts to obtain ownership of locks that another thread is using". Given that we have a narrow list of shell processes to target, it could probably be made to work… but it's a pretty serious thing to go injecting code into an unsuspecting process.
  2. Use a shell-specific mechanism.
    a. For cmd: a .cmd/.bat script.
    b. For PowerShell: a .ps1 script.
    c. Bash: a .sh script.
    d. Etc.
    e. Because winget is Windows-specific, just handling cmd and PS is good enough; who in the world is running winget commands from bash, amiright? :D

Okay, so we’re going to have a script.

This brings us to the last piece of the puzzle: how is the [shell-specific] script going to get executed in the proper shell process? Winget.exe can’t do it directly (and don’t say CreateRemoteThread again)…

Easy: just have the user do it! :D

We train people to just run “winget <arguments>”… but that does not have to directly be winget.EXE. If we have winget.ps1 and winget.cmd, which come before winget.exe on the %PATH%, then when you are in cmd.exe, and you run “winget”, you will run winget.CMD; when in pwsh.exe, you will get winget.PS1.

And here’s what the script will do:

  1. [If we are doing an "install",] read the current PATH values out of the registry (the “before” snapshot). This is possibly very different from what’s in memory, but that’s okay; this is just a reference point to find out what the installer has actually changed.
  2. Run winget.exe, passing through all arguments (%*/$args).
  3. Read the current PATH values out of the registry (the “after” snapshot), compare to “before” to see what the installer changed, and tack the additions onto the end of the in-memory environment values.

Et voilà!

(This is actually a pretty standard trick; to wrap things in a script wrapper for various reasons.)

If you want to try this out, a convenient, guaranteed-to-exist-and-be-on-the-PATH-and-come-before-the-windowsapps-dir path is C:\windows\system32. So you can put the script files below into system32 as an easy way to evaluate this, but remember that this is probably not an attractive "real" solution for the winget package (which means somebody needs to do some work to arrange for a different, user-specific (so it won't require elevation) path to be on the %PATH%, before the windowsapps dir (where the winget.exe command is found).

(Also, note that you don't actually need the .Tests.ps1 file; that's just test code, which is convenient if you want to play around with it. Note that it requires you to install Pester v5.)

Script files:

winget.cmd:

@ECHO off
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION

REM This wrapper script is a straight "pass-through" to winget.exe, and then after running
REM an install, it will update your in-process PATH environment variable.

IF "%1" == "install" (
    SET TheHelperCommand=powershell.exe -NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command . %~dp0wingetHelper.ps1 ; GetStaticPathFromRegistry PATH
    FOR /F "tokens=* USEBACKQ" %%i IN (`!TheHelperCommand!`) DO (
        SET StaticPathBefore=%%i
    )
)

winget.exe %*

IF NOT "%StaticPathBefore%" == "" (
    FOR /F "tokens=* USEBACKQ" %%i IN (`!TheHelperCommand!`) DO (
        SET StaticPathAfter=%%i
    )

    SET TheHelperCommand=powershell.exe -NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command . %~dp0wingetHelper.ps1 ; CalculateAdditions 'PATH' '%StaticPathBefore%' '!StaticPathAfter!'

    FOR /F "tokens=* USEBACKQ" %%i IN (`!TheHelperCommand!`) DO (
        SET Additions=%%i
    )
    REM ECHO Additions are: !Additions!
)

IF NOT "%Additions%" == "" (
    ENDLOCAL & SET PATH=%PATH%;%Additions%
)

winget.ps1:

# This wrapper script is a straight "pass-through" to winget.exe, and then after running
# an install, it will update your in-process Path environment variables (in your current
# shell).
#
# N.B. This is a "simple function" (as opposed to an "advanced function") (no
# "[CmdletBinding()]" attribute). This is important so that the PowerShell parameter
# binder does not get involved, and we can pass everything straight to winget.exe as-is.

try
{
    $pathBefore = ''
    $psModulePathBefore = ''
    if( $args -and ($args.Length -gt 0) -and ($args[ 0 ] -eq 'install') )
    {
        . $PSScriptRoot\wingetHelper.ps1

        $pathBefore = GetStaticPathFromRegistry 'PATH'
        $psModulePathBefore = GetStaticPathFromRegistry 'PSModulePath'
    }

    winget.exe @args

    if( $pathBefore )
    {
        UpdateCurrentProcessPathBasedOnDiff 'PATH' $pathBefore
        UpdateCurrentProcessPathBasedOnDiff 'PSModulePath' $psModulePathBefore
    }
}
catch
{
    Write-Error $_
}

wingetHelper.ps1:

# Split out for mocking.
function GetEnvVar
{
    [CmdletBinding()]
    param( $EnvVarName, $Target )

    # (the cast is so that a null return value gets converted to an empty string)
    return [string] ([System.Environment]::GetEnvironmentVariable( $EnvVarName, $Target ))
}

# Gets the "static" (as stored in the registry) value of a specified PATH-style
# environment variable (combines the Machine and User values with ';'). Note that this may
# be significantly different than the "live" environment value in the memory of the
# current process.
function GetStaticPathFromRegistry
{
    [CmdletBinding()]
    param( $EnvVarName )

    (@( 'Machine', 'User' ) | ForEach-Object { GetEnvVar $EnvVarName $_ }) -join ';'
}

# Split out for mocking.
function UpdateCurrentProcessPath
{
    [CmdletBinding()]
    param( $EnvVarName, $Additions )

    Set-Content Env:\$EnvVarName -Value ((Get-Content Env:\$EnvVarName) + ';' + $additions)
}

function UpdateCurrentProcessPathBasedOnDiff
{
    [CmdletBinding()]
    param( $EnvVarName, $Before )

    $pathAfter = GetStaticPathFromRegistry $EnvVarName

    $additions = CalculateAdditions $EnvVarName $Before $pathAfter

    if( $additions )
    {
        UpdateCurrentProcessPath $EnvVarName $additions
    }
}

# Given two strings representing PATH-like environment variables (a set of strings
# separated by ';'), returns the PATHs that are present in the second ($After) but not in
# the first ($Before) and not in the current (in-memory) variable, in PATH format (joined
# by ';'). (Does not do anything about removals or reordering.)
function CalculateAdditions
{
    [CmdletBinding()]
    param( [string] $EnvVarName, [string] $Before, [string] $After )

    try
    {
        $additions = @()
        $setBefore = @( $Before.Split( ';' ) )
        $currentInMemory = @( (GetEnvVar $EnvVarName 'Process').Split( ';' ) )

        foreach( $p in $After.Split( ';' ) )
        {
            if( ($setBefore -notcontains $p) -and ($currentInMemory -notcontains $p) )
            {
                $additions += $p
            }
        }

        return $additions -join ';'
    }
    finally { }
}

wingetHelper.Tests.ps1:

BeforeAll {
    $parentDir = Split-Path $PSCommandPath -Parent

    . $PSCommandPath.Replace('.Tests.ps1', '.ps1')

    function ResetFakeRegistry
    {
        $script:FakeRegistry = @{
            User = @{
                Path = ''
                PSModulePath = ''
            }
            Machine = @{
                Path = 'testPath1;testPath2;testPath3'
                PSModulePath = 'testPsModulePath1'
            }
            Process = @{
                Path = 'testPath1;testPath2;testPath3;runtimePath1'
                PSModulePath = 'testPsModulePath1;runtimePsModulePath1'
            }
        }
    }

    Mock GetEnvVar {
        return ($script:FakeRegistry)[ $Target ][ $EnvVarName ]
    }

    Mock UpdateCurrentProcessPath {
        # (nothing)
    }

    # Hide the real winget.exe:
    function winget.exe
    {
        # (nothing)
    }
}

Describe 'winget.ps1' {
    BeforeEach {
        ResetFakeRegistry
    }

    It 'should do nothing if not an install command' {

        # N.B. Dot sourcing here is important, so that it executes in the current scope.
        . $PSScriptRoot\winget.ps1 some other command

        Should -Invoke -CommandName 'GetEnvVar' -Exactly 0
        Should -Invoke -CommandName 'UpdateCurrentProcessPath' -Exactly 0
    }

    It 'should do nothing if no change in path' {

        # N.B. Dot sourcing here is important, so that it executes in the current scope.
        . $PSScriptRoot\winget.ps1 install something

        Should -Invoke -CommandName 'GetEnvVar' -Exactly 10
        Should -Invoke -CommandName 'UpdateCurrentProcessPath' -Exactly 0
    }

    It 'should update current process paths if install updated paths' {

        $newPaths = 'newPath1;newPath2'
        $newPsModulePaths = 'newPath3'
        Mock winget.exe {
            # Simulate install updating the registry:
            $script:FakeRegistry[ 'User' ][ 'PATH' ] = $newPaths
            $script:FakeRegistry[ 'Machine' ][ 'PSModulePath' ] = $newPsModulePaths
        }

        # N.B. Dot sourcing here is important, so that it executes in the current scope.
        . $PSScriptRoot\winget.ps1 install something

        Should -Invoke -CommandName 'GetEnvVar' -Exactly 10
        Should -Invoke -CommandName 'UpdateCurrentProcessPath' -Exactly 1 -ParameterFilter {
            ($EnvVarName -eq 'PATH') -and ($Additions -eq $newPaths)
        }
        Should -Invoke -CommandName 'UpdateCurrentProcessPath' -Exactly 1 -ParameterFilter {
            ($EnvVarName -eq 'PSModulePath') -and ($Additions -eq $newPsModulePaths)
        }
    }

    It 'should not add duplicate paths' {

        $newPsModulePaths = 'newPath3'
        $script:FakeRegistry[ 'Process' ][ 'PSModulePath' ] = $newPsModulePaths
        Mock winget.exe {
            # Simulate install updating the registry:
            $script:FakeRegistry[ 'Machine' ][ 'PSModulePath' ] = $newPsModulePaths
        }

        # N.B. Dot sourcing here is important, so that it executes in the current scope.
        . $PSScriptRoot\winget.ps1 install something

        Should -Invoke -CommandName 'GetEnvVar' -Exactly 10
        Should -Invoke -CommandName 'UpdateCurrentProcessPath' -Exactly 0
    }
}

(I forgot to add: a small downside to the "script wrapper" approach is that if someone runs "winget.exe" instead of "winget", then they don't get the magical path-refresh behavior. But there's a very good upside to this: it means that if there were some targeted situation where you didn't want path refresh, you could avoid it by just running winget.exe instead.)

Good news, at least in the realm of stop gaps, Terminal now has the ability to get a new environment block on tap open, at least in preview. At least we don't have to close the whole terminal anymore, just opening a new tab should be enough

I do suggest closing the "first" tab though, I've confused myself more than once switching to a different tab that hadn't been updated.

@denelon Do you think we'll get this for powershell users in the near future?

"Near future" is subjective. My preference is to have an experimental feature before just rolling it out to "everyone". We're looking at the challenges with Windows PowerShell vs. PowerShell 7 currently. I'm expecting we will make incremental progress with this one.

I'm also looking at the nuances between how we do experimental features in WinGet and how they are done in PowerShell. I want to make sure we're following the right idioms between the two types of experiences (Windows CLI vs. native PowerShell).

There are lots of 👍 on this Issue so it does bring more priority to this issue compared with many others.

Thanks for the update :). I understand that there's a lot of important work but I'm grateful that it's being treated as a priority.

I do suggest closing the "first" tab though, I've confused myself more than once switching to a different tab that hadn't been updated.

Not a bad idea, I see how that would get pretty confusing. I get confused enough just having multiple types of processor in the same window lol

In an idle moment, it occurred to me that there is another way you could arrange for the wrapper scripts to take precedence over the EXE, without having to fiddle with %PATH%, if you were, hm, not sure how best to describe it... willing to take drastic action in service of the user, maybe?: you could rename the EXE (e.g. winget-real.exe, or maybe winget-cli.exe, ha) (and add additional wrapper scripts named winget.exe.cmd and winget.exe.ps1 in order to keep invocations of winget.exe working).

The price (what would stop working) is anything that expected "winget.exe" to be an actual PE file (like someone using CreateProcess( "winget.exe", ... ) directly, instead of ShellExecute or such). I would expect such usage to be very low. (There would still be a "don't want the wrapper script" workaround; it would be to just call winget-real.exe instead.)

What is most interesting (to me) about this thought experiment is the utter revulsion I feel about renaming the EXE. Why does it seem so gross? From the point of view of the end user, I think that they don't care at all (99.9% wouldn't even notice); they just want to run "winget install whatever" and have it work. So why does renaming the EXE just so these shell wrapper scripts can take precedence feel like some kind of violation? It makes me wonder if I am holding things sacred that I should not be.

We do have plenty of cases where the MSIX behavior isn't what customers want, and they are sensitive to changes in the name of the "primary" executable. If this does actually end up being the right thing to do to resolve the challenges associated with path, it might be OK though.

Several users have automated solutions calling winget.exe directly in the path where the files get laid down rather than using the App Execution Alias that typically catches "winget" via Windows Terminal, PowerShell, or what ever shell they happen to be using.

Our preference for integration is to call the COM API rather than winget.exe. In the case of PowerShell users, I think we could just make that work as we would like, but we'll have to continue investigating.

I am not sure if this is the same issue but I am observing this behavior :

image

image

The same applies with Powershell.
Everytime I launch cmd or powershell binaries previously installed with winget seem not to be in PATH, I click on new tab, then I can use the binaries ...

@amoscatelli Winget does not currently refresh the path. It is still effectively in alpha from a UX perspective. It can be used, and is stable, but until this issue is fixed I would not recommend it to anyone other than power users who want to play with something new. It's too confusing and will make people resent an otherwise pretty good tool.