Skip to content
Open
Show file tree
Hide file tree
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
3 changes: 3 additions & 0 deletions .github/actions/spelling/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ experimentalfeatures
XPRESS
fdw
fdwgp
FEATUREA
featureb
FECAFEB
fedorapeople
fileinuse
Expand Down Expand Up @@ -434,6 +436,7 @@ OUTOFMEMORY
Outptr
OVERLAPPEDWINDOW
OWC
packagededuplication
packageinuse
packageinusebyapplication
PACL
Expand Down
11 changes: 9 additions & 2 deletions doc/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@

## New Features

### Cross-source package deduplication (experimental)

Set `experimentalFeatures.packageDeduplication` to `true` in settings to automatically resolve equivalent installers across two or three sources for single-package `install`, `download`, and ordinary `show` commands. WinGet compares resolved versions, installer SHA256 hashes, and supported behavior checks after applying source priority. If all candidates pass, it selects the first participating source in the opened source order, reports that source, and reuses the checked installer.

This feature is disabled by default and independent of interactive package selection. It also works with redirected input or `--disable-interactivity`; unresolved matches retain the existing prompt/error behavior. Installed-package associations, local manifests, multi-package operations, and `show --versions` are excluded. Use `--source` to choose a source explicitly. See [settings](Settings.md#packagededuplication).

### Interactive package selection (experimental)

Set `experimentalFeatures.interactivePackageSelection` to `true` in settings to enable numbered choices when multiple packages match a single-package `install`, `show`, or `download` command in an interactive terminal. Enter a package number to continue or `0` to cancel.

This feature is disabled by default. Redirected and noninteractive callers retain the existing ambiguity error. Use `--id <ID> --exact --source <SOURCE>` to select a package explicitly, or `--disable-interactivity` to prevent prompts.
This feature is disabled by default. Matches not resolved by deduplication retain the existing ambiguity error for redirected and noninteractive callers. Use `--id <ID> --exact --source <SOURCE>` to select a package explicitly, or `--disable-interactivity` to prevent prompts.

### Source priority

Source priority is now available without enabling an experimental feature. Use `winget source add --priority <value>` or `winget source edit --name <source> --priority <value>` to configure it. Higher values take precedence; sources with equal priority still require disambiguation when multiple matches remain.
Source priority is now available without enabling an experimental feature. Use `winget source add --priority <value>` or `winget source edit --name <source> --priority <value>` to configure it. Higher values take precedence. Multiple matches at equal priority require disambiguation unless the experimental deduplication checks resolve them.

### `--output-locale` argument

Expand Down Expand Up @@ -46,6 +52,7 @@ This change resolves alias failures in non-symlinked scenarios, including cases
Because the alias is now created as an executable hardlink in the install location, command aliases remain available and consistent even when symlink creation is skipped.

### Minor Bug Fixes
* Preserved source-reported truncation in composite searches so incomplete results are not used for automatic or interactive package selection.
* Fixed REST search results bypassing locally verifiable package filters and selectors. Extra manifests are retrieved only for complete source result sets of three or fewer packages. Normalized name/publisher criteria remain unvalidated client-side.
* Fixed installed-package matching incorrectly combining names and publishers from different manifest entries.
* Prevented unrestricted REST searches when a source declares all requested selectors unsupported.
Expand Down
14 changes: 14 additions & 0 deletions doc/Settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,3 +465,17 @@ Enables numbered choices for ambiguous single-package `install`, `show`, and `do
```

`--disable-interactivity` and redirected input or output still prevent prompting.

### packageDeduplication

Automatically selects equivalent installers across two or three sources for single-package `install`, `download`, and ordinary `show` commands. Disabled by default and independent of `interactivePackageSelection`.

```json
"experimentalFeatures": {
"packageDeduplication": true
},
```

Source priority applies first. When the resolved versions, installer hashes, and supported behavior checks match, WinGet selects the first participating source in the opened source order and reports its name. This also works with redirected input or `--disable-interactivity`. Otherwise, normal package selection or ambiguity errors apply.

Installed packages, local manifests, multi-package operations, and `show --versions` are excluded. Use `--source` to select a source explicitly.
187 changes: 187 additions & 0 deletions doc/specs/#5345 - Cross-source package deduplication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
---
author: AmelBawa-msft, GitHub Copilot <Copilot>
created on: 2026-10-06
last updated: 2026-10-12
issue id: 5345
---

# Cross-source package deduplication

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

## Abstract

An experimental heuristic resolves ambiguous package matches across sources using versions and installer SHA256 hashes, with a few additional checks. It keeps the selected source's manifest intact; unresolved cases retain the existing prompt or error.

## Inspiration

Equivalent packages can have different identifiers, names, URLs, and metadata. Source priority and interactive selection help resolve ambiguity but do not identify equivalent installers automatically.

## Solution Design

### Settings and scope

`packageDeduplication` defaults to `false`, independently of `interactivePackageSelection`:

```json
{
"experimentalFeatures": {
"packageDeduplication": true
}
}
```

The existing experimental-features group policy applies; there is no new policy or CLI flag. Honor `--source`, search filters, and source priority before comparison.

Require two or three complete, failure-free candidates, exactly one per source, with source identities present in the opened source order. Otherwise perform no heuristic manifest evaluations. Never compare lower-priority candidates, partially remove matches, or merge search rows. Command exclusions are listed below.

### Comparison rules

Use existing command-specific version, channel, pin, and installer-applicability rules. Compare the preferred installer, not any installer with a matching hash. For install without an explicit version, evaluate only the first permitted version; an inapplicable installer or missing requested version leaves the set unresolved. Never probe older manifests.

Support only direct `exe`, `burn`, `inno`, `nullsoft`, `msi`, `wix`, and `msix` installers.

Normal applicability still respects architecture, scope, locale, installer-type requests, and operating-system support. Their raw metadata need not match across sources.

Every candidate must have:

1. Equal resolved versions under existing version comparison (`2.4` equals `2.4.0`) and case-insensitively equal channels. Reject empty, `Unknown`, `Latest`, or approximate versions. Unknown search metadata is acceptable if retrieval resolves a concrete version.
2. Identical 32-byte, nonzero installer SHA256 values. Names, IDs, URLs, product codes, and package-family names cannot substitute for these signals.

Apply only these additional checks:

| Check | Rule and reason |
| --- | --- |
| Execution family | Use existing installer compatibility: EXE/Burn/Inno/Nullsoft, MSI/Wix, or MSIX. Different families use different execution mechanisms. |
| Declared scope | For scope-sensitive types, reject any set containing both explicit user and machine scope, even with an unspecified third candidate. Missing scope alone is allowed; scope-agnostic types retain command-controlled behavior. |
| Operation restrictions | Reject download prohibition during download. For install, reject elevation prohibition conflicting with the current context or a supplied unsupported argument normally treated as an error. Unused or warning-only arguments do not block selection. |
| Automatic dependencies | If install/download would process package dependencies, any such dependency leaves ambiguity: identical IDs can resolve differently across sources. For install enabling Windows features, require equal case-insensitive feature-ID sets, ignoring order. Skip these checks for show or when dependency processing is disabled. Download does not enable features; reported-only Windows library/external dependencies do not block selection. |

Other metadata differences do not block selection once normal applicability passes. Do not compare switches, generated arguments, success/return-code tables, or authentication metadata for equality. The selected source's instructions remain authoritative, even when differences affect execution or exit-code interpretation. This is payload-based selection, not a guarantee of identical outcomes.

### Selection and failures

If every candidate passes, choose the participating source earliest in the existing opened source order, regardless of result-row order. Keep its identity and exact version, manifest, and installer without merging metadata.

Reuse that result for this invocation: if Alpha publishes 2.5 after comparing Alpha/Bravo 2.4, continue with the checked 2.4 result. Do not fetch another manifest or select "latest" again.

If a check fails or cannot be evaluated, preserve all candidates and existing command state. Expected manifest retrieval/parsing failures warn and log; cancellation stops; unexpected failures propagate. Authentication, agreement, or installer failures never trigger fallback to another source.

### Settings and behavior matrix

For supported commands when policy allows:

| Deduplication | Interactive selection | Behavior with multiple results |
| --- | --- | --- |
| Off | Off | Existing ambiguity error. |
| Off | On | Existing prompt/error rules. |
| On | Off | Resolve if checks pass; otherwise error. |
| On | On | Try deduplication before existing prompt/error rules. |

| Situation | Behavior |
| --- | --- |
| Single-package CLI install/download/ordinary show | Eligible. |
| Zero/one match, four or more, repeated source, truncation, source failure, or unknown source identity | No heuristic evaluations. |
| `show --versions` | Existing interactive selection only. |
| Other commands, local manifests, multi-package/dependency contexts | No deduplication. |
| Installed associations, install that may upgrade, or multiple source identities within a candidate | No deduplication. |
| COM, PowerShell cmdlets (including `-Force`), configuration/DSC, completion | Unchanged. |
| Disabled interactivity, redirected input/output, or unavailable prompt output | Passing candidates resolve without input; unresolved matches error. |
| `--no-vt` | Same behavior using plain text. |
| `--silent` | Controls installer UI, not selection prompts. |
| Experimental features disabled by policy | Neither experimental selection feature runs. |

### Schemas, authoring, and validation

Only the settings schema changes. Manifest schemas, COM IDL, PowerShell surfaces, validation pipelines, manifest authoring tools, and REST source reference tooling remain unchanged.

Alpha can publish this existing-format manifest:

```yaml
PackageIdentifier: Contoso.Editor
PackageVersion: 2.4.0
InstallerType: exe
Scope: user
Installers:
- Architecture: x64
InstallerUrl: https://alpha.example/editor.exe
InstallerSha256: A91F43BBD50C2A147216D7C2F7A06E0EBD7DF95A08C5CA6F0AB6BEE06C9E4C20
ManifestType: installer
ManifestVersion: 1.10.0
```

Bravo can use a different ID/URL with the same version/hash. These domains and hash are fictional.

### Acceptance cases

Assume eligible candidates with matching version/channel/hash and all other checks passing unless stated otherwise. "Unresolved" preserves the whole set.

| Case | Expected result |
| --- | --- |
| Different names/IDs/URLs, switches, return codes, or authentication metadata | Resolve; retain selected-source instructions. |
| Different version/channel/hash, invalid hash, or unresolved version | Unresolved. |
| EXE/Inno versus EXE/MSI | Resolve versus unresolved. |
| User/machine scope versus unspecified/user, both applicable | Unresolved versus resolve. |
| Download prohibited during download versus show | Unresolved versus resolve. |
| Package dependencies processed versus skipped | Unresolved versus resolve. |
| Windows feature sets differ versus only order/case differs | Unresolved versus resolve during install. |

## UI/UX Design

On selection, emit localized text, then continue normal command output:

```text
Equivalent installers found. Selected Contoso Editor [Contoso.Editor] from source Alpha.
```

For expected manifest retrieval/parsing failures:

```text
Could not compare packages across sources. Refine the search or choose a package.
```

Unresolved cases retain the existing prompt/error, candidate table, and refinement guidance:

```text
Specify a package with --id <ID> --exact --source <SOURCE>.
```

Ordinary mismatches produce only verbose diagnostics.

## Capabilities

### Accessibility

Use localized, line-oriented text without requiring color, cursor movement, or new input.

### Security

Hashes and source order do not establish trust. Preserve source boundaries, authentication, agreements, policy, and installer hash/signature checks.

### Reliability

Retain the checked result to prevent a race condition. Comparison must not download binaries, authenticate installers, accept agreements, resolve dependencies, or execute installers.

### Compatibility

Default-off and excluded-call behavior stays unchanged. `--disable-interactivity` prevents prompts, not deduplication; use `--source` or disable the setting to avoid automatic cross-source selection. No surfaces are deprecated.

### Performance, Power, and Efficiency

Evaluate at most one target manifest per candidate, at most three total, excluding preexisting search/correlation traffic. No historical probing or persistent cache.

## Potential Issues

Missing scope is not proof of identical scope. Package dependencies intentionally limit coverage. Propagating child truncation through both available-only and installed-aware searches can also suppress existing prompts that would otherwise show incomplete choices.

## Future Considerations

Cross-version matching, dependency equivalence, persistent correlation, search-result grouping, additional installer types, and API deduplication remain separate proposals.

## Resources

- [Deduplicating results from multiple sources](https://github.com/microsoft/winget-cli/issues/5345)
- [Matching and source-priority discussion](https://github.com/microsoft/winget-cli/issues/6435)
- [Existing multi-source matching and interactive selection](https://github.com/microsoft/winget-cli/pull/6577)
- [Interactive package selection](%235345%20-%20Interactive%20package%20selection.md)
- [Settings reference](../Settings.md)
11 changes: 7 additions & 4 deletions doc/specs/#5345 - Interactive package selection.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
author: AmelBawa-msft, GitHub Copilot <Copilot>
created on: 2026-09-28
last updated: 2026-10-01
last updated: 2026-10-09
issue id: 5345
---

Expand Down Expand Up @@ -31,6 +31,8 @@ This feature is disabled by default. Enable it in settings:

Apply existing search matching and source-priority rules first. If multiple candidates remain, eligible CLI call sites opt into selection. Shared workflows remain noninteractive by default.

The independent [package deduplication feature](%235345%20-%20Cross-source%20package%20deduplication.md) runs before the prompt for eligible commands. If it resolves equivalent installers, no selection prompt is needed. Otherwise, keep the candidate set and follow the rules below. `show --versions` remains prompt-only.

Display candidates in their existing order with stable, one-indexed numbers. A valid number selects the existing package object without searching again. Preserve command options and continue normal version selection, applicability checks, and agreement handling.

| Situation | Behavior |
Expand Down Expand Up @@ -99,15 +101,15 @@ Numeric, line-oriented input works without color, cursor navigation, or arrow ke

### Security

There is no default selection or inferred equivalence. Choosing a package does not accept agreements or bypass existing source, trust, or installer checks.
The prompt has no default choice and does not infer equivalence. Choosing a package does not accept agreements or bypass existing source, trust, or installer checks.

### Reliability

Selection uses the displayed candidate object rather than re-running a potentially different search. EOF fails explicitly; cancellation never starts installation or download.

### Compatibility

The feature is disabled by default. After opt-in, scripts can preserve ambiguity errors with `--disable-interactivity`, or avoid ambiguity with exact ID and source selectors.
The feature is disabled by default. `--disable-interactivity` suppresses prompts but not independently enabled package deduplication. Scripts can use exact ID and source selectors to choose explicitly, or disable `packageDeduplication` as well to preserve ambiguity errors.

### Performance, Power, and Efficiency

Expand All @@ -119,9 +121,10 @@ Long candidate lists require scrolling. Narrow terminals truncate table cells us

## Future Considerations

Cross-source equivalence heuristics, arrow-key navigation, and selection for installed-package operations are separate changes.
Arrow-key navigation and selection for installed-package operations remain separate changes.

## Resources

- [Package matching background](%23292%20-%20winget%20should%20install%20an%20app%20if%20there%20is%20an%20exact%20match.md)
- [Cross-source package deduplication](%235345%20-%20Cross-source%20package%20deduplication.md)
- [Settings reference](../Settings.md)
4 changes: 4 additions & 0 deletions doc/windows/package-manager/winget/source.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ winget source edit --name winget --priority 1

If multiple matches remain at the highest priority, refine the search or specify `--source`.

The optional [packageDeduplication setting](../../../Settings.md#packagededuplication) can resolve equivalent installers across two or three sources for single-package `install`, `download`, and ordinary `show`. It runs after source-priority filtering and chooses the first participating source in the opened source order only when all candidates pass the comparison checks. Lower-priority sources are not compared.

Deduplication is disabled by default and does not merge search results. When enabled, it can select a package without prompting even with `--disable-interactivity`. Use `--source` to choose a source explicitly; source trust, agreements, and installer verification still apply.

## list

the **list** sub-command enumerates the currently enabled sources. This sub-command also provides details on a specific source.
Expand Down
5 changes: 5 additions & 0 deletions schemas/JSON/settings/settings.schema.0.2.json
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,11 @@
"description": "Enable support for some commands to resume",
"type": "boolean",
"default": false
},
"packageDeduplication": {
"description": "Resolve equivalent installers across sources for single-package install, download, and show commands",
"type": "boolean",
"default": false
}
}
},
Expand Down
4 changes: 2 additions & 2 deletions src/AppInstallerCLICore/Commands/DownloadCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ namespace AppInstaller::CLI
Workflow::OpenSource() <<
Workflow::SearchSourceForSingle <<
Workflow::HandleSearchResultFailures <<
Workflow::EnsureOneMatchFromSearchResult(OperationType::Download, PackageSelectionBehavior::Prompt) <<
Workflow::GetManifestFromPackage(false);
Workflow::EnsureOneMatchFromSearchResult(OperationType::Download, PackageSelectionBehavior::Prompt | PackageSelectionBehavior::Deduplicate) <<
Workflow::GetManifestFromPackage(false, OperationType::Download);
}

context <<
Expand Down
Loading
Loading