microsoft/winget-cli

Handle DLLs for Symlinked executables

caiohamamura opened this issue · 17 comments

Description of the new feature / enhancement

With current zip support, the only types of installer allowed are:

  1. Portable standalone exe files
  2. Compressed installers

But what about supporting regular compressed binary packages, such as those having exe along with dynamic dll libraries?

Proposed technical implementation details

To use the dynamic dll libraries, they must either be:

  1. in PATH environment variable
  2. linked by specifying Files -> RelativeFilePath within the installer manifest

It is not possible to link dll files to make it work using the 2 approach, because the links are forced to have .exe extension.

This could be fixed either by:

  1. Adding the dlls folder to PATH environment variable
  2. Allowing to link dll files specifying something like NestedInstallerType: library. Then it would also be useful to provide pattern matching RelativeFilePath, such as *.dll
  3. Instead of making a symlink inside WinGet\Links, make an executable bat with the command @call "path\to\exe" %*, then the working directory will be the same where the exe actually is (this would the easiest path in my opinion)

iirc, the entire Zip folder is extracted and moved to the default install location. Take the Paint.Net Pull Request for example. If you comment out everything except for the zip-portable installer entry, it still installs and runs correctly, meaning that all the DLLs needed to run it are referenced. The DLLs aren’t needed in the installer files because they aren’t installers

iirc, the entire Zip folder is extracted and moved to the default install location. Take the Paint.Net Pull Request for example. If you comment out everything except for the zip-portable installer entry, it still installs and runs correctly, meaning that all the DLLs needed to run it are referenced. The DLLs aren’t needed in the installer files because they aren’t installers

Maybe I wasnt' clear enough. By binary files I mean plain binary, not an exe installer, that's the case for Paint.Net. What I mean is plain binary packages, which is just a bunch of exe files along with dll files, many development tools are deployed this way such as nginx, php, etc. And they won't run unless:

  1. They are launched from the same directory where they were extracted, which a symlink will break
  2. They are added to PATH
  3. They are launched through a .bat file as I proposed: this strategy is used throughout many different packages.

If the community agrees with the bat implementation I would gladly like to contribute. I think it is much better than polluting the PATH or forcing the manifest to declare a whole bunch of DLL files which could also drive other side effects such as DLL versions conflicts.

iirc, the entire Zip folder is extracted and moved to the default install location. Take the Paint.Net Pull Request for example. If you comment out everything except for the zip-portable installer entry, it still installs and runs correctly, meaning that all the DLLs needed to run it are referenced. The DLLs aren’t needed in the installer files because they aren’t installers

Maybe I wasnt' clear enough. By binary files I mean plain binary, not an exe installer, that's the case for https://github.com/microsoft/winget-pkgs/pull/88065/files. What I mean is plain binary packages, which is just a bunch of exe files along with dll files, many development tools are deployed this way such as nginx, php, etc. And they won't run unless:

  1. They are launched from the same directory where they were extracted, which a symlink will break
  2. They are added to PATH
  3. They are launched through a .bat file as I proposed: this strategy is used throughout many different packages.

If the community agrees with the bat implementation I would gladly like to contribute. I think it is much better than polluting the PATH or forcing the manifest to declare a whole bunch of DLL files which could also drive other side effects such as DLL versions conflicts.

I think you should look at the Paint.Net manifest and zip file more closely. Specifically, this entry -

- Architecture: x64
   NestedInstallerType: portable
   NestedInstallerFiles:
   - RelativeFilePath: paintdotnet.exe
     PortableCommandAlias: paint.net
   InstallerUrl: https://github.com/paintdotnet/release/releases/download/v4.3.12/paint.net.4.3.12.portable.x64.zip
   InstallerSha256: AF58C12B92BC759F8E38C8623E356C8B8A2534809C44058AB76055DFA15DE4F8

That specific entry is exactly what you described - a loose .exe file with a bunch of DLLs and other assets included.

If you comment out all the other entries in the manifest, and then use winget to install this specific entry, it will still run perfectly fine

@Trenly, it does work if you install it and launch it through run dialog (Win + R) paint.net.exe, but that's because it looks like it follows the symlink working directory somehow, but if you try to launch it through cmd or powershell it won't work at all!

Although this is fine for Desktop Apps, it is not the expected behavior for development tools and won't work for CLI apps.

But even for Desktop Apps you need to specify paint.net.exe which is not what I would expect to type in the run dialog, I usually just type paint.net, code, etc. I do not type the .exe, I think this is another issue with the SymLinking strategy.

@Trenly, it does work if you install it and launch it through run dialog (Win + R) paint.net.exe, but that's because somehow it looks like it follows the symlink working directory somehow, but if you try to launch it through cmd or powershell it won't work at all!

Although this is fine for Desktop Apps, it is not the expected behavior for development tools and won't work for CLI apps.

But even for Desktop Apps you need to specify paint.net.exe which is not what I would expect to type in the run dialog, I usually just type paint.net, code, etc. I do not type the .exe, I think this is another issue with the SymLinking strategy.

It works fine for me from powershell when I use start paint.net

That's my point, start shouldn't be needed at all, you won't be running development tools using start by default, besides you probably won't be launching these development tools yourself but they will be launched by other development tools which you don't have control over.

Ahhh, I see. Thank you for your patience and the clarification!

The issue title should be better renamed to something like "Handle DLLs for symlinked executables."

This could be fixed either by:

  1. Adding the dlls folder to PATH environment variable
  2. Allowing to link dll files specifying something like NestedInstallerType: library. Then it would also be useful to provide pattern matching RelativeFilePath, such as *.dll
  3. Instead of making a symlink inside WinGet\Links, make an executable bat with the command @call "path\to\exe" %*, then the working directory will be the same where the exe actually is (this would the most easier path in my opinion)

Another solution would be to use shims, like Scoop or Chocolatey:

iirc, the entire Zip folder is extracted and moved to the default install location. Take the Paint.Net Pull Request for example. If you comment out everything except for the zip-portable installer entry, it still installs and runs correctly, meaning that all the DLLs needed to run it are referenced. The DLLs aren’t needed in the installer files because they aren’t installers

Maybe I wasnt' clear enough. By binary files I mean plain binary, not an exe installer, that's the case for Paint.Net. What I mean is plain binary packages, which is just a bunch of exe files along with dll files, many development tools are deployed this way such as nginx, php, etc. And they won't run unless:

  1. They are launched from the same directory where they were extracted, which a symlink will break
  2. They are added to PATH
  3. They are launched through a .bat file as I proposed: this strategy is used throughout many different packages.

If the community agrees with the bat implementation I would gladly like to contribute. I think it is much better than polluting the PATH or forcing the manifest to declare a whole bunch of DLL files which could also drive other side effects such as DLL versions conflicts.

Use bat might cause problem while execute it in another bat script.

iirc, the entire Zip folder is extracted and moved to the default install location. Take the Paint.Net Pull Request for example. If you comment out everything except for the zip-portable installer entry, it still installs and runs correctly, meaning that all the DLLs needed to run it are referenced. The DLLs aren’t needed in the installer files because they aren’t installers

Maybe I wasnt' clear enough. By binary files I mean plain binary, not an exe installer, that's the case for Paint.Net. What I mean is plain binary packages, which is just a bunch of exe files along with dll files, many development tools are deployed this way such as nginx, php, etc. And they won't run unless:

  1. They are launched from the same directory where they were extracted, which a symlink will break
  2. They are added to PATH
  3. They are launched through a .bat file as I proposed: this strategy is used throughout many different packages.

If the community agrees with the bat implementation I would gladly like to contribute. I think it is much better than polluting the PATH or forcing the manifest to declare a whole bunch of DLL files which could also drive other side effects such as DLL versions conflicts.

Use bat might cause problem while execute it in another bat script.

I really don't know, I'm not that versed in BAT scripts. But AFAIK @call does not cause any side effects inside other bat files, it won't change the running environment as a regular call and as the content is just a single call to an exe and won't change any variable, it will be just like calling the exe itself which could also break/stop the bat by itself.

I suggested this solution because it is used by many development tools such as Anaconda, python tools, msys, osgeo4w, this is not a clever hack that came out of my mind.

iirc, the entire Zip folder is extracted and moved to the default install location. Take the Paint.Net Pull Request for example. If you comment out everything except for the zip-portable installer entry, it still installs and runs correctly, meaning that all the DLLs needed to run it are referenced. The DLLs aren’t needed in the installer files because they aren’t installers

Maybe I wasnt' clear enough. By binary files I mean plain binary, not an exe installer, that's the case for Paint.Net. What I mean is plain binary packages, which is just a bunch of exe files along with dll files, many development tools are deployed this way such as nginx, php, etc. And they won't run unless:

  1. They are launched from the same directory where they were extracted, which a symlink will break
  2. They are added to PATH
  3. They are launched through a .bat file as I proposed: this strategy is used throughout many different packages.

If the community agrees with the bat implementation I would gladly like to contribute. I think it is much better than polluting the PATH or forcing the manifest to declare a whole bunch of DLL files which could also drive other side effects such as DLL versions conflicts.

Use bat might cause problem while execute it in another bat script.

I really don't know, I'm not that versed in BAT scripts. But AFAIK @call does not cause any side effects inside other bat files, it won't change the running environment as a regular call and as the content is just a single call to an exe and won't change any variable, it will be just like calling the exe itself which could also break/stop the bat by itself.

I suggested this solution because it is used by many development tools such as Anaconda, python tools, msys, osgeo4w, this is not a clever hack that came out of my mind.

If you run the second bat script directly in the first bat script, the remaining commands in the first script will not be executed after the second script is executed(even if you use the @call command in the second script). Unless the call command is also used when invoking the second bat script in the first bat script. But bat scripts from the network and the average users doesn't know this, because the difference doesn't occur if a exe file is invoked. In addition, Microsoft documentation shows that the use of the @call command is not recommended with redirection and pipes, so I'm not sure if this will affect the behavior of using redirection and pipes when running a bat script containing the call command as a command.

+1

I've got a .NET Core 7.0 app
image
but winget isn't capable of handling its XCOPY-Install scenario. Trying it as a 'portable' app

Installers:
- InstallerUrl:         https://github.com/DrusTheAxe/AppData/releases/download/2.0.0/AppData-2.0.0.zip
  Architecture:         x64
  InstallerType:        zip
  NestedInstallerType:  portable
  NestedInstallerFiles:
  - RelativeFilePath:   appdata.exe
  InstallerSha256:      7717bd116f15e6debfc581e6bde36ec8854e60eced1c9cb5c5496a7f640af0f3

only ends in tears
image

Looking forward to a solution

I found out in microsoft/winget-pkgs#109711 that this impacts non-DLL files as well. That is somewhat concerning as it means there could be other programs that validate normally but do not work properly due to symlinking/expected reachable files. Not all self-contained portable applications store the info they within the executable so I'm not sure how this would be validated against until implementation.

Since no one seems to have posted it here, there is a workaround, kindly described by @mdanish-kh here:

"A not so pretty workaround (as it has the tendency to quickly fill up your PATH variable limit) is to install the application in a non-admin shell with developer mode disabled in Windows. In this case, WinGet cannot create a symlink and will resort to putting the entire package path (i.e., %LOCALAPPDATA%\Microsoft\Winget\Packages) into the PATH variable. That way all the required libraries, .dlls will be referenced properly."

It look like another problem with the winget while during install, https://download.wsusoffline.net/wsusoffline120.zip

Well the PATH issue can be mitigated by having separate variable for winget package path eg WINGET so you can add stuff to path like %WINGET% or another option is to hold all winget path variables in WINGET variable and just add that to path. You can extend path by just chaining variables togather.https://stackoverflow.com/questions/34491244/environment-variable-is-too-large-on-windows-10

Any progress on this issue? Seems to be blocked many, many packages.