Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 217 additions & 0 deletions doc/specs/#140 - ZIP Support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
---
author: Alexis Bekhdadi @midoriiro
created on: 2021-11-16
last updated: 2021-11-30
issue id: 140
---

# Zip Support

For [#140](https://github.com/microsoft/winget-cli/issues/140)

## Abstract

This feature adds to winget-cli the ability to install application delivered through a ZIP archive.

## Inspiration

A bunch of applications are only provided by developer's through a ZIP archive. There are three cases:
1) The archive contains only one binary (a portable / standalone executable).
2) The archive contains only one supported installer (msix, msi, or exe).
3) The archive contains more than one file.

## Solution Design

A new installation flow need to be created to execute different installation strategies regarding the three cases above.

First thing first, the archive needs to be extracted in a specific path (TEMP folder can be a good candidate).

Regarding case 1, installation flow check if TEMP folder contain a single binary and pass instruction to
installation flow (including setting PATH env var).

Regarding case 2, installation flow check if TEMP folder contain an installer (.exe, .msi, .msix, etc) and pass control
to proper installation flow.

Regarding case 3, installation flow check if TEMP folder does not contain an installer nor a single binary.
This case is the complex one, example with 'vagrant' hierarchy:
- root
- bin
- vagrant.exe
- other.exe
- embedded
- bin
- some other folders

This hierarchy can be packed into an MSI(X) installer or register the application in ARP at the end of installation
flow (see section below about repack or ARP entry).

This specification take on consideration schema on version 1.1.0.
A schema update should be necessary to take all the cases in consideration.

Take a look into the following manifest examples

### Case 1 YAML manifest

```
PackageIdentifier: Editor.App
PackageVersion: 1.0
MinimumOSVersion: 10.0.0.0
InstallerType: zip
Scope: ...
InstallModes: ...
InstallerSwitches:
Silent: ...
SilentWithProgress: ...
UpgradeBehavior: install
Installers:
- Architecture: x64
InstallerUrl: https://editor.com/app/1.0.zip
InstallerSha256: ...
ManifestType: installer
ManifestVersion: 1.0.0
```

This is compliant with the actual schema v1.1.0

### Case 2 YAML manifest

If the archive contains an installer but not located at root folder (e.g. archive.zip/somefolder/installer.exe)

```
PackageIdentifier: Editor.App
PackageVersion: 1.0
MinimumOSVersion: 10.0.0.0
InstallerType: zip
Scope: ...
InstallModes: ...
InstallerSwitches:
Silent: ...
SilentWithProgress: ...
UpgradeBehavior: install
Installers:
- Architecture: x64
InstallerUrl: https://editor.com/app/1.0.zip
InstallerSha256: ...
InstallerPath: somefolder/installer.exe

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we may also need a key defining the secondary installer type.

It is likely that some of the exe’s shipped inside zip packages are actually nullsoft or inno, which should be preferred to exe since the CLI already handles their silent switches without having to define them

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A key definition already exist on schema 1.1.0 (line 459)

Maybe we can use it to tell which installation flow to choose?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That key, by definition, would be "zip" since the primary installer is inside of a .zip file. The issue I'm trying to get ahead of is that the zip file could contain different types of exes which have their own installation flow

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key I'm pointing could be resolve that issue with something like this:

PackageIdentifier: Editor.App
PackageVersion: 1.0
MinimumOSVersion: 10.0.0.0
InstallerType: zip
Scope: ...
InstallModes: ...
InstallerSwitches:
  Silent: ...
  SilentWithProgress: ...
UpgradeBehavior: install
Installers:
- Architecture: x64
  InstallerUrl: https://editor.com/app/1.0.zip
  InstallerSha256: ...
  InstallerPath: somefolder/installer.exe
  InstallerType: inno # or something else
ManifestType: installer
ManifestVersion: 1.0.0

@Trenly Trenly Nov 22, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe that is valid. Having the key at the manifest level is not compatible with the key at the installer level. One of them will override the other.

Then, what if a publisher distributes some apps via msi and others via zip => nullsoft ? Its possible that not all of the installers inside of a manifest will be zipped, and so we would need to use the installer level key to specify which ones are zip files and which ones are msi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not saying multiple installers may be in a .Zip archive. I’m saying a manifest may have different types of installers, and a zip archive may be one of them.

For example - the 7-zip manifest has installers for both exe and msi. Both are in the same manifest.

Some publishers pubish exe’s, msi’s, and zip files, which should all be able to be in a single manifest

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I understand now. And I agree with you about defining another key to specify the installer type. Maybe InstallerSubType ?

I need to clarify some points on the specification about previous conversations. I'll commit soon.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#1242 is how we're planning to capture some of the "nested installer type" issues. For example, PowerToys is distributed as an .exe, but it installs as an .msi. The installer is a wrapper to help with dependencies.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, I think that as long as that issue is referenced in the spec, then there won't be an issue here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should close this issue. Thank for your feedback.

NestedInstallerType: msi
ManifestType: installer
ManifestVersion: 1.0.0
```

This is not compliant with the actual schema v1.1.0.
```InstallerPath``` key should be added to schema.
This optional key define where to find installer file after extraction if not located at root.

To specify installer type, ```NestedInstallerType``` key must be defined.
As mentioned in issue [#1242](https://github.com/microsoft/winget-cli/issues/1242).

### Case 3 YAML manifest

```
PackageIdentifier: Editor.App
PackageVersion: 1.0
MinimumOSVersion: 10.0.0.0
InstallerType: zip
Scope: ...
InstallModes: ...
InstallerSwitches:
Silent: ...
SilentWithProgress: ...
UpgradeBehavior: install
Installers:
- Architecture: x64
InstallerUrl: https://editor.com/app/1.0.zip
InstallerSha256: ...
BinaryPath: bin (e.g. bin folder where vagrant.exe is located in my example)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be simplified to "InstallerPath" as well, and have the cli determine if it is an installer or binary?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Case 2 cover installer (.exe, .msi, etc). You're pointing case 3, which is for complex hierarchy that does not include installer, only binary with complex folders structure. The base idea is to implement all the cases. Nonetheless, "BInaryPath" maybe not a good name, any idea ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m saying that for case 2, you are suggesting that a key be added to define the path to the exe/msi/etc.

Why can’t that same key be used for case 3?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got confused you was quoting case 3. On case 3, BinaryPath key is for indicate to register a path after the zip extraction into user or system environment variables (need to be defined how to).

Correct me if I'm wrong, but from my point of view regarding case 2 (excluding my example manifest 2.1). Case 2 assuming that the zip archive only contains one file, the installer. Case 2 also assuming that the installer will set the proper environment variables if needed (specifically $PATH) or not. that depend of the nature of the application and how editors package their applications. I don't know, how do you manage that kind of thing, winget-cli is agnostic regarding this aspect ?

To answer your question, case 3 does not handle installer file. That the job of case 2 and 2.1. Case 2 does not need to define InstallerPath key because, the archive only contains the installer at the root archive. On the other hand, case 2.1 zip archive maybe not contains only one file and maybe not located at root, so InstallerPath key is needed to indicate where to find the installer after the extraction and execute it.

To summarize, BinaryPath and InstallerPath are optional keys that depend on the context and they should be set properly by the package maintainer. To avoid messing up with these settings, updating the documentation and the helper script YAMLCreate.ps1 should be needed, in my opinion. For all cases, if the manifest is miss-configured the procedure should fail in an organic way.

I don't know if I'm clear, my english is not perfect. Maybe I should clarify these aspects on the specification.

ManifestType: installer
ManifestVersion: 1.0.0
```

This is not compliant with the actual schema v1.1.0.
We need to specify which binary folder to add in PATH env var.
```BinaryPath``` attribute should be added to schema.

### Processing uncompressed files after extraction

Two solutions can be taken in consideration. Repack uncompressed files into an installer or add an ARP entry in
registry.

For case 2, the manifest should contain all information needed to other installation flow. There is no need to repack
or add an ARP entry.

For all others cases, repacking a file structure might not be a good solution. This process require installing on client
side a third party repacking solution or installing Windows SDK that provide MakeAppx that repack our file structure
into a MSIX installer. But not everyone needs to install Windows SDK and repacking can take time and resources.

Adding an ARP entry into registry can be done more easily, actually winget-cli already read ARP entries through
[ARPHelper](https://github.com/microsoft/winget-cli/blob/master/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp)
struct.

Windows [documentation](https://docs.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key) define properties for
an ARP entry (these properties are also defined in ARPHelper struct).

An ARP entry can be defined in two location:
- HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall (scope machine)
- HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall (scope user)

Uninstall key contains sub keys identified by the application product code, but for our case 1 and 3 a product code
might not exist. Two solutions can be considered, the package maintainer can generate a random GUID but this identifier
should be reused on each manifest for a particular application. The second solution is to set the sub key name by the
application name which can be retrieved on the manifest with the ```PackageName``` key.

Then the installation flow should register an ARP entry with the proper properties defined in manifest.

Our file structure should be moved into a proper installation folder.
Depending on the scope and the architecture we have different folders:
- %HOMEDRIVE%%HOMEPATH%/AppData/Local (scope user)
- %PROGRAMFILES% (scope machine, architecture x64)
- %PROGRAMFILES(X86)% (scope machine, architecture x86)

> Above section might need more specification about where to install the file structure

Finally, if manifest key ```BinaryPath``` is defined the installation flow should add to PATH environment variable the
resolved binary path folder.

## UI/UX Design

If we exclude repacking solutions and prefer add/edit ARP entry, that should be transparent to the end-user.

- For case 2, the existing installation flow will take control and behave as expected
- For case 1 and 3, the new installation flow need to behave like other installation flow (showing steps and progress)

## Capabilities

### Accessibility

This should have no direct impact on accessibility.

### Security

> Need more specification

### Reliability

This is not expected to impact reliability.

### Compatibility

This feature is intended be an experimental one, at least for a first step.
End-user need to toggle on the feature in order to install zip packages.

No code breaking, reuse existing code and add code to support zip installation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would not be a backward compatible behavior. Only clients with the support for this feature would be able to install packages of this type. We would need to add something like "zip" to the enumeration of installer types. The initial implementation would also be an experimental feature until we have covered enough of the different use cases to confirm viability of the solution.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right. For the moment this draft is only intended to schema 1.1.0. Like i suggested, we maybe need to update the schema (1.1.1?) to handle case 3 and other new cases.


### Performance, Power, and Efficiency

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Building the installer is likely to take additional time and resources on the client. There will be performance considerations here with the client in the packaging phase.


Add/edit an ARP entry should not be an issue for performance, power and efficiency

## Potential Issues

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MSIX packages are typically signed. There would likely be security issues related to signing. If the MSI route or some other mechanism is used for the package, we would also need to look at those considerations. Even if we just move the standalone / portable application (or set of files) to a directory path, any changes to those files could have unintended consequences. Some packages also generate additional files in their directories so those may need to be considered during an upgrade or uninstall flow.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we just move the standalone / portable application (or set of files) to a directory path, any changes to those files could have unintended consequences.

which is true of any package winget currently installs, as with admin rights (or for user scoped packages, normal rights) I can go mess around with anything the installer created. I think that might be a separate issue.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like someone said on issue #140, if a file is modified is not the packager responsibility but it's an user fault. If files are created by a process delivered by the package, these files should be referenced somewhere to prevent overwriting them, or rather we can specify to overwrite theses files during an upgrade. Typically for config files, user need to edit them, that could be a problem if on each upgrade these files are overwritten.

Concerning signing a MSIX package, A self-signed package could be acceptable ?


> Need more specification

## Future considerations
Comment thread
midoriiro marked this conversation as resolved.

Initial implementation will only take in consideration .zip archive. However, when the feature will be stable enough,
we can consider other types of archives (e.g. *.gz. *.7z, etc).

## Resources
[MakeAppx.exe Documentation](https://docs.microsoft.com/en-us/windows/msix/package/manual-packaging-root)