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
8 changes: 8 additions & 0 deletions doc/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ The WinGet MCP server's existing tools have been extended with new parameters to

The PowerShell module now automatically uses `GH_TOKEN` or `GITHUB_TOKEN` environment variables to authenticate GitHub API requests. This significantly increases the GitHub API rate limit, preventing failures in CI/CD pipelines. Use `-Verbose` to see which token is being used.

### Default priority of installer types

Installer type selection no longer depends on the order defined on the manifest. Instead, preference is given in this order:
- MSIX
- MSI / Wix / Burn
- Nullsoft / Inno / EXE
- Portable

## Bug Fixes

* Fixed the `useLatest` property in the DSC v3 `Microsoft.WinGet/Package` resource schema to emit a boolean default (`false`) instead of the incorrect string `"false"`.
Expand Down
31 changes: 28 additions & 3 deletions src/AppInstallerCLITests/ManifestComparator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,8 @@ TEST_CASE("ManifestComparator_InstalledTypeFilter", "[manifest_comparator]")
ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {}));
auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest);

// Only because it is first
RequireInstaller(result, msi);
// Msix is preferred over Msi by the default installer type precedence order
RequireInstaller(result, msix);
REQUIRE(inapplicabilities.size() == 0);
}
SECTION("MSI Installed")
Expand Down Expand Up @@ -238,7 +238,7 @@ TEST_CASE("ManifestComparator_InstalledTypeCompare", "[manifest_comparator]")
ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {}));
auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest);

// Only because it is first
// Burn is preferred over Exe by the default installer type precedence order
RequireInstaller(result, burn);
REQUIRE(inapplicabilities.size() == 0);
}
Expand Down Expand Up @@ -855,6 +855,31 @@ TEST_CASE("ManifestComparator_InstallerType", "[manifest_comparator]")
REQUIRE(!result);
RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType });
}
SECTION("No user preference - default order applies")
{
// No TestUserSettings means no user-configured preferences; the default order should be used.
// Default order: MSStore > Msix > Msi > Wix > Burn > Nullsoft > Inno > Exe > Portable
// Manifest has Msi, Exe, Msix — Msix should win.
ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {}));
auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest);

RequireInstaller(result, msix);
REQUIRE(inapplicabilities.size() == 0);
}
SECTION("No user preference - type not in default list does not win over default-ordered types")
{
// Zip is not in the default preference list; it should not be preferred over Msix/Msi/Exe.
Manifest localManifest;
ManifestInstaller zip = AddInstaller(localManifest, Architecture::Neutral, InstallerTypeEnum::Zip, ScopeEnum::User);
ManifestInstaller localExe = AddInstaller(localManifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::User);

ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {}));
auto [result, inapplicabilities] = mc.GetPreferredInstaller(localManifest);

// Exe is in the default list; Zip is not — Exe should be preferred.
RequireInstaller(result, localExe);
REQUIRE(inapplicabilities.size() == 0);
}
}

TEST_CASE("ManifestComparator_MachineArchitecture_Strong_Scope_Weak", "[manifest_comparator]")
Expand Down
16 changes: 16 additions & 0 deletions src/AppInstallerCommonCore/Manifest/ManifestComparator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,22 @@ namespace AppInstaller::Manifest
{
preference = Settings::User().Get<Settings::Setting::InstallerTypePreference>();
requirement = Settings::User().Get<Settings::Setting::InstallerTypeRequirement>();

// Apply default precedence order when the user has not configured any installer type preferences or requirements.
if (preference.empty() && requirement.empty())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Feels like this should be set any time preferences are not provided.

Of if requirements are provided but preferences are not, then we set preferences to requirements by default.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I thought it could be confusing if the requirements were copied into the preferences, unless that would be useful for dependency selection?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Alternatively, we implicitly "copy" the preferences by using the requirements in IsFirstBetter when the preferences are empty. That is probably cleaner than copying things in memory.

If preferences are empty, then you get manifest ordering (when other fields are equal of course) among the types in the requirements. I don't think we want a case where the manifest has two types and you get one or the other based on whether you have requirements set to include both (unless it is using requirement ordering).

I feel like implicitly using the requirement ordering is the better usability option, and it feels natural to me to list my requirements in the order that I would prefer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@denelon for an opinion

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

AH, I see now - Currently the requirements are just an applicability filter that work like "Any of these are valid" but the ordering is non-consequential. By copying the requirements to the preferences (or using them in IsFirstBetter) it would allow for the ordering that the preferences give.

That makes sense to me!!

{
preference = {
InstallerTypeEnum::MSStore,
InstallerTypeEnum::Msix,
InstallerTypeEnum::Msi,
InstallerTypeEnum::Wix,
InstallerTypeEnum::Burn,
InstallerTypeEnum::Nullsoft,
InstallerTypeEnum::Inno,
InstallerTypeEnum::Exe,
InstallerTypeEnum::Portable,
};
}
}

if (!preference.empty() || !requirement.empty())
Expand Down