From 059a28395aa6c368ce08a1454ef2af6057972bbd Mon Sep 17 00:00:00 2001
From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com>
Date: Fri, 5 Sep 2025 14:14:52 -0700
Subject: [PATCH 1/8] Register
---
.../Commands/WinGetPackageManagerCommand.cs | 8 +-
.../Helpers/AppxModuleHelper.cs | 90 +++++++++++--------
.../Microsoft.WinGet.Client.Engine.csproj | 2 +-
...crosoft.WinGet.Configuration.Engine.csproj | 2 +-
4 files changed, 62 insertions(+), 40 deletions(-)
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
index cdf5f16d51..e10ae952bb 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
@@ -138,11 +138,13 @@ private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers
this.RepairEnvPath();
break;
case IntegrityCategory.AppInstallerNotRegistered:
- this.Register(expectedVersion);
+ await this.RegisterAsync(expectedVersion, allUsers);
break;
case IntegrityCategory.AppInstallerNotInstalled:
case IntegrityCategory.AppInstallerNotSupported:
case IntegrityCategory.Failure:
+ System.Diagnostics.Debugger.Launch();
+ System.Diagnostics.Debugger.Break();
await this.InstallAsync(expectedVersion, allUsers, force);
break;
case IntegrityCategory.AppInstallerNoLicense:
@@ -197,10 +199,10 @@ private async Task InstallAsync(string toInstallVersion, bool allUsers, bool for
await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion, allUsers, false, force);
}
- private void Register(string toRegisterVersion)
+ private async Task RegisterAsync(string toRegisterVersion, bool allUsers)
{
var appxModule = new AppxModuleHelper(this);
- appxModule.RegisterAppInstaller(toRegisterVersion);
+ await appxModule.RegisterAppInstallerAsync(toRegisterVersion, allUsers);
}
private void RepairEnvPath()
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs
index e8b8d1ad09..5845be1ba2 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs
@@ -16,7 +16,6 @@ namespace Microsoft.WinGet.Client.Engine.Helpers
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.WinGet.Client.Engine.Common;
- using Microsoft.WinGet.Client.Engine.Exceptions;
using Microsoft.WinGet.Client.Engine.Extensions;
using Microsoft.WinGet.Common.Command;
using Newtonsoft.Json;
@@ -61,6 +60,7 @@ internal class AppxModuleHelper
private const string Register = "Register";
private const string DisableDevelopmentMode = "DisableDevelopmentMode";
private const string ForceTargetApplicationShutdown = "ForceTargetApplicationShutdown";
+ private const string AllUsers = "AllUsers";
private const string AppInstallerName = "Microsoft.DesktopAppInstaller";
private const string AppxManifest = "AppxManifest.xml";
@@ -112,21 +112,23 @@ public AppxModuleHelper(PowerShellCmdlet pwshCmdlet)
///
/// Calls Get-AppxPackage Microsoft.DesktopAppInstaller.
///
+ /// Whether to get for all users.
/// Result of Get-AppxPackage.
- public PSObject? GetAppInstallerObject()
+ public PSObject? GetAppInstallerObject(bool allUsers = false)
{
- return this.GetAppxObject(AppInstallerName);
+ return this.GetAppxObject(AppInstallerName, allUsers);
}
///
/// Gets the string value a property from the Get-AppxPackage object of AppInstaller.
///
/// Property name.
+ /// Whether to get for all users.
/// Value, null if doesn't exist.
- public string? GetAppInstallerPropertyValue(string propertyName)
+ public string? GetAppInstallerPropertyValue(string propertyName, bool allUsers = false)
{
string? result = null;
- var packageObj = this.GetAppInstallerObject();
+ var packageObj = this.GetAppInstallerObject(allUsers);
if (packageObj is not null)
{
var property = packageObj.Properties.Where(p => p.Name == propertyName).FirstOrDefault();
@@ -143,11 +145,13 @@ public AppxModuleHelper(PowerShellCmdlet pwshCmdlet)
/// Calls Add-AppxPackage to register with AppInstaller's AppxManifest.xml.
///
/// Release tag of GitHub release.
- public void RegisterAppInstaller(string releaseTag)
+ /// Whether to register for all users.
+ /// A representing the asynchronous operation.
+ public async Task RegisterAppInstallerAsync(string releaseTag, bool allUsers)
{
if (string.IsNullOrEmpty(releaseTag))
{
- string? versionFromLocalPackage = this.GetAppInstallerPropertyValue(Version);
+ string? versionFromLocalPackage = this.GetAppInstallerPropertyValue(Version, allUsers);
if (versionFromLocalPackage == null)
{
@@ -157,11 +161,11 @@ public void RegisterAppInstaller(string releaseTag)
var packageVersion = new Version(versionFromLocalPackage);
if (packageVersion.Major == 1 && packageVersion.Minor > 15)
{
- releaseTag = $"1.{packageVersion.Minor - 15}.{packageVersion.Build}";
+ releaseTag = $"v1.{packageVersion.Minor - 15}.{packageVersion.Build}";
}
else
{
- releaseTag = $"{packageVersion.Major}.{packageVersion.Minor}.{packageVersion.Build}";
+ releaseTag = $"v{packageVersion.Major}.{packageVersion.Minor}.{packageVersion.Build}";
}
}
@@ -169,31 +173,9 @@ public void RegisterAppInstaller(string releaseTag)
// If dependencies are missing, a provisioned package can appear to only need registration,
// but will fail to register. `InstallDependenciesAsync` checks for the packages before
// acting, so it should be mostly a no-op if they are already available.
- this.InstallDependenciesAsync(releaseTag).Wait();
-
- string? packageFullName = this.GetAppInstallerPropertyValue(PackageFullName);
-
- if (packageFullName == null)
- {
- throw new ArgumentNullException(PackageFullName);
- }
-
- string appxManifestPath = System.IO.Path.Combine(
- Utilities.ProgramFilesWindowsAppPath,
- packageFullName,
- AppxManifest);
+ await this.InstallDependenciesAsync(releaseTag);
- _ = this.ExecuteAppxCmdlet(
- AddAppxPackage,
- new Dictionary
- {
- { Path, appxManifestPath },
- },
- new List
- {
- Register,
- DisableDevelopmentMode,
- });
+ this.RegisterAppInstallerInternal(allUsers);
}
///
@@ -278,6 +260,10 @@ await this.httpClientHelper.DownloadUrlWithProgressAsync(
.AddParameter(ErrorAction, Stop)
.Invoke();
});
+
+ // Register the package after provisioning so that it is
+ // available immediately.
+ this.RegisterAppInstallerInternal(allUsers: true);
}
catch (RuntimeException e)
{
@@ -320,14 +306,21 @@ private async Task AddAppInstallerBundleAsync(string releaseTag, bool downgrade,
}
}
- private PSObject? GetAppxObject(string packageName)
+ private PSObject? GetAppxObject(string packageName, bool allUsers = false)
{
+ var options = new List();
+ if (allUsers)
+ {
+ options.Add(AllUsers);
+ }
+
return this.ExecuteAppxCmdlet(
GetAppxPackage,
new Dictionary
{
{ Name, packageName },
- })
+ },
+ options)
.FirstOrDefault();
}
@@ -808,5 +801,32 @@ private bool IsStubPackageOptionPresent()
return result;
}
+
+ private void RegisterAppInstallerInternal(bool allUsers = false)
+ {
+ string? packageFullName = this.GetAppInstallerPropertyValue(PackageFullName, allUsers);
+
+ if (packageFullName == null)
+ {
+ throw new ArgumentNullException(PackageFullName);
+ }
+
+ string appxManifestPath = System.IO.Path.Combine(
+ Utilities.ProgramFilesWindowsAppPath,
+ packageFullName,
+ AppxManifest);
+
+ _ = this.ExecuteAppxCmdlet(
+ AddAppxPackage,
+ new Dictionary
+ {
+ { Path, appxManifestPath },
+ },
+ new List
+ {
+ Register,
+ DisableDevelopmentMode,
+ });
+ }
}
}
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj b/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj
index 110fe45120..e54da55956 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj
@@ -21,7 +21,7 @@
enable
-
+
$(DefineConstants);USE_PROD_CLSIDS
diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj
index 70f53cc531..83c29cfe17 100644
--- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj
+++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj
@@ -12,7 +12,7 @@
Debug;Release;ReleaseStatic
-
+
$(DefineConstants);USE_PROD_CLSIDS
From 00efc143cc0f4fc6f64505ed313171b8a7081516 Mon Sep 17 00:00:00 2001
From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com>
Date: Fri, 5 Sep 2025 15:19:13 -0700
Subject: [PATCH 2/8] Install winget source
---
.../Commands/WinGetPackageManagerCommand.cs | 11 ++++++-
.../Common/IntegrityCategory.cs | 7 +++-
.../Common/WinGetIntegrity.cs | 7 ++++
.../Helpers/AppxModuleHelper.cs | 33 +++++++++++++++++++
4 files changed, 56 insertions(+), 2 deletions(-)
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
index e10ae952bb..a711e97b60 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
@@ -144,7 +144,6 @@ private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers
case IntegrityCategory.AppInstallerNotSupported:
case IntegrityCategory.Failure:
System.Diagnostics.Debugger.Launch();
- System.Diagnostics.Debugger.Break();
await this.InstallAsync(expectedVersion, allUsers, force);
break;
case IntegrityCategory.AppInstallerNoLicense:
@@ -158,6 +157,9 @@ private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers
throw new WinGetRepairException(e);
}
+ break;
+ case IntegrityCategory.WinGetSourceNotInstalled:
+ await this.InstallWinGetSourceAsync();
break;
case IntegrityCategory.AppExecutionAliasDisabled:
case IntegrityCategory.Unknown:
@@ -199,6 +201,13 @@ private async Task InstallAsync(string toInstallVersion, bool allUsers, bool for
await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion, allUsers, false, force);
}
+ private async Task InstallWinGetSourceAsync()
+ {
+ this.Write(StreamType.Verbose, "Installing winget source");
+ var appxModule = new AppxModuleHelper(this);
+ await appxModule.InstallWinGetSourceAsync();
+ }
+
private async Task RegisterAsync(string toRegisterVersion, bool allUsers)
{
var appxModule = new AppxModuleHelper(this);
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/IntegrityCategory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/IntegrityCategory.cs
index b026070109..22fdebb85f 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/IntegrityCategory.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/IntegrityCategory.cs
@@ -1,4 +1,4 @@
-// -----------------------------------------------------------------------------
+// -----------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
//
@@ -65,5 +65,10 @@ public enum IntegrityCategory
/// No applicable license found.
///
AppInstallerNoLicense,
+
+ ///
+ /// WinGet source is not installed.
+ ///
+ WinGetSourceNotInstalled,
}
}
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs
index 9669da8dbc..7139d2314a 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs
@@ -79,6 +79,13 @@ public static void AssertWinGet(PowerShellCmdlet pwshCmdlet, string expectedVers
expectedVersion));
}
}
+
+ // Verify that the winget source is installed.
+ var appxModule = new AppxModuleHelper(pwshCmdlet);
+ if (!appxModule.IsWinGetSourceInstalled())
+ {
+ throw new WinGetIntegrityException(IntegrityCategory.WinGetSourceNotInstalled);
+ }
}
private static IntegrityCategory GetReason(PowerShellCmdlet pwshCmdlet)
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs
index 5845be1ba2..e9558d75d4 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs
@@ -94,6 +94,11 @@ internal class AppxModuleHelper
private const string XamlPackage27 = "Microsoft.UI.Xaml.2.7";
private const string XamlReleaseTag273 = "v2.7.3";
+ // WinGet Source
+ private const string WinGetSourceName = "Microsoft.Winget.Source";
+ private const string WinGetSourceMsixName = "source2.msix";
+ private const string WinGetSourceUrl = "https://cdn.winget.microsoft.com/cache/source2.msix";
+
private readonly PowerShellCmdlet pwshCmdlet;
private readonly HttpClientHelper httpClientHelper;
private Lazy> frameworkArchitectures;
@@ -119,6 +124,16 @@ public AppxModuleHelper(PowerShellCmdlet pwshCmdlet)
return this.GetAppxObject(AppInstallerName, allUsers);
}
+ ///
+ /// Calls Get-AppxPackage Microsoft.Winget.Source.
+ ///
+ /// Whether to get for all users.
+ /// Result of Get-AppxPackage.
+ public PSObject? GetWinGetSourceObject(bool allUsers = false)
+ {
+ return this.GetAppxObject(WinGetSourceName, allUsers);
+ }
+
///
/// Gets the string value a property from the Get-AppxPackage object of AppInstaller.
///
@@ -141,6 +156,15 @@ public AppxModuleHelper(PowerShellCmdlet pwshCmdlet)
return result;
}
+ ///
+ /// Checks if winget source is installed.
+ ///
+ /// True if installed.
+ public bool IsWinGetSourceInstalled()
+ {
+ return this.GetWinGetSourceObject() is not null;
+ }
+
///
/// Calls Add-AppxPackage to register with AppInstaller's AppxManifest.xml.
///
@@ -213,6 +237,15 @@ public async Task InstallFromGitHubReleaseAsync(string releaseTag, bool allUsers
}
}
+ ///
+ /// Installs the WinGet source by downloading and adding package.
+ ///
+ /// A representing the asynchronous operation.
+ public async Task InstallWinGetSourceAsync()
+ {
+ await this.DownloadPackageAndAddAsync(WinGetSourceUrl, WinGetSourceMsixName, null);
+ }
+
///
/// Gets the Xaml dependency package name and release tag based on the provided WinGet release tag.
///
From 04ba820a4bf72f1108d0eff251166d423ab2d022 Mon Sep 17 00:00:00 2001
From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com>
Date: Mon, 8 Sep 2025 11:14:26 -0700
Subject: [PATCH 3/8] Version pattern
---
.../Common/WinGetPackageManagerCmdlet.cs | 3 +
.../RepairWinGetPackageManagerCmdlet.cs | 4 +-
.../Commands/WinGetPackageManagerCommand.cs | 67 ++++++++++++++++++-
.../Helpers/GitHubClient.cs | 12 +++-
.../Helpers/WinGetVersion.cs | 8 ++-
5 files changed, 87 insertions(+), 7 deletions(-)
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs
index 3d2f4d16d7..daddb3c243 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs
@@ -36,6 +36,9 @@ public abstract class WinGetPackageManagerCmdlet : PSCmdlet
[Parameter(
ParameterSetName = Constants.IntegrityLatestSet,
ValueFromPipelineByPropertyName = true)]
+ [Parameter(
+ ParameterSetName = Constants.IntegrityVersionSet,
+ ValueFromPipelineByPropertyName = true)]
public SwitchParameter IncludePrerelease { get; set; }
}
}
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs
index b3e6ae4df2..d60bc088e9 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs
@@ -1,4 +1,4 @@
-// -----------------------------------------------------------------------------
+// -----------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
//
@@ -49,7 +49,7 @@ protected override void ProcessRecord()
}
else
{
- this.command.Repair(this.Version, this.AllUsers.ToBool(), this.Force.ToBool());
+ this.command.Repair(this.Version, this.AllUsers.ToBool(), this.Force.ToBool(), this.IncludePrerelease.ToBool());
}
}
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
index a711e97b60..8e54a38d71 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
@@ -8,6 +8,7 @@ namespace Microsoft.WinGet.Client.Engine.Commands
{
using System;
using System.Collections.Generic;
+ using System.Linq;
using System.Management.Automation;
using System.Threading.Tasks;
using Microsoft.WinGet.Client.Engine.Commands.Common;
@@ -88,18 +89,81 @@ public void RepairUsingLatest(bool preRelease, bool allUsers, bool force)
/// The expected version, if any.
/// Install for all users. Requires admin.
/// Force application shutdown.
- public void Repair(string expectedVersion, bool allUsers, bool force)
+ /// Include prerelease versions when matching version.
+ public void Repair(string expectedVersion, bool allUsers, bool force, bool includePrerelease)
{
this.ValidateWhenAllUsers(allUsers);
var runningTask = this.RunOnMTA(
async () =>
{
+ if (!string.IsNullOrWhiteSpace(expectedVersion))
+ {
+ var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli);
+ var allReleases = await gitHubClient.GetAllReleasesAsync();
+ var allWinGetReleases = allReleases.Select(r => new WinGetVersion(r.TagName));
+ var latestVersion = GetLatestMatchingVersion(allWinGetReleases, expectedVersion, includePrerelease);
+ if (latestVersion == null)
+ {
+ this.Write(StreamType.Warning, $"No matching version found for {expectedVersion}");
+ }
+ else
+ {
+ expectedVersion = latestVersion.TagVersion;
+ this.Write(StreamType.Verbose, $"Matching version found: {expectedVersion}");
+ }
+ }
+ else
+ {
+ this.Write(StreamType.Verbose, "No version specified.");
+ }
+
await this.RepairStateMachineAsync(expectedVersion, allUsers, force);
return true;
});
this.Wait(runningTask);
}
+ private static WinGetVersion GetLatestMatchingVersion(IEnumerable versions, string pattern, bool includePrerelease)
+ {
+ pattern = string.IsNullOrWhiteSpace(pattern) ? "*" : pattern;
+
+ var parts = pattern.Split('.');
+ string? major = parts[0];
+ string? minor = parts.Length > 1 ? parts[1] : null;
+ string? build = parts.Length > 2 ? parts[2] : null;
+ string? revision = parts.Length > 3 ? parts[3] : null;
+
+ if (!includePrerelease)
+ {
+ versions = versions.Where(v => !v.IsPrerelease);
+ }
+
+ versions = versions
+ .Where(v =>
+ VersionPartMatch(major, v.Version.Major) &&
+ VersionPartMatch(minor, v.Version.Minor) &&
+ VersionPartMatch(build, v.Version.Build) &&
+ VersionPartMatch(revision, v.Version.Revision))
+ .OrderBy(f => f.Version);
+
+ return versions.Count() == 0 ? null : versions.Last();
+ }
+
+ private static bool VersionPartMatch(string? partPattern, int partValue)
+ {
+ if (string.IsNullOrWhiteSpace(partPattern))
+ {
+ return true;
+ }
+
+ if (partPattern.EndsWith("*"))
+ {
+ return partValue.ToString().StartsWith(partPattern.TrimEnd('*'));
+ }
+
+ return partPattern == partValue.ToString();
+ }
+
private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers, bool force)
{
var seenCategories = new HashSet();
@@ -143,7 +207,6 @@ private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers
case IntegrityCategory.AppInstallerNotInstalled:
case IntegrityCategory.AppInstallerNotSupported:
case IntegrityCategory.Failure:
- System.Diagnostics.Debugger.Launch();
await this.InstallAsync(expectedVersion, allUsers, force);
break;
case IntegrityCategory.AppInstallerNoLicense:
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs
index be2eddd2ec..e47c8de390 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs
@@ -6,6 +6,7 @@
namespace Microsoft.WinGet.Client.Engine.Helpers
{
+ using System.Collections.Generic;
using System.Threading.Tasks;
using Octokit;
@@ -63,7 +64,7 @@ public async Task GetLatestReleaseAsync(bool includePrerelease)
if (includePrerelease)
{
// GetAll orders by newest and includes pre releases.
- release = (await this.gitHubClient.Repository.Release.GetAll(this.owner, this.repo))[0];
+ release = (await this.GetAllReleasesAsync())[0];
}
else
{
@@ -72,5 +73,14 @@ public async Task GetLatestReleaseAsync(bool includePrerelease)
return release;
}
+
+ ///
+ /// Gets all releases.
+ ///
+ /// All releases.
+ public async Task> GetAllReleasesAsync()
+ {
+ return await this.gitHubClient.Repository.Release.GetAll(this.owner, this.repo);
+ }
}
}
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs
index 19d4704dd5..35c32bdb17 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs
@@ -20,9 +20,9 @@ internal class WinGetVersion
/// String Version.
public WinGetVersion(string version)
{
- if (string.IsNullOrEmpty(version))
+ if (string.IsNullOrWhiteSpace(version))
{
- throw new ArgumentNullException();
+ throw new ArgumentNullException(nameof(version));
}
string toParseVersion = version;
@@ -32,6 +32,10 @@ public WinGetVersion(string version)
{
this.TagVersion = version;
toParseVersion = toParseVersion.Substring(1);
+ if (toParseVersion.Length > 0 && toParseVersion[0] == '-')
+ {
+ toParseVersion = toParseVersion.Substring(1);
+ }
}
else
{
From cf58d52071af8fb6020b840033fcfe8aa80f748f Mon Sep 17 00:00:00 2001
From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com>
Date: Mon, 8 Sep 2025 12:48:10 -0700
Subject: [PATCH 4/8] CE
---
.../Commands/WinGetPackageManagerCommand.cs | 41 +++++++++++++------
1 file changed, 28 insertions(+), 13 deletions(-)
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
index 8e54a38d71..0965fe2427 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
@@ -98,19 +98,8 @@ public void Repair(string expectedVersion, bool allUsers, bool force, bool inclu
{
if (!string.IsNullOrWhiteSpace(expectedVersion))
{
- var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli);
- var allReleases = await gitHubClient.GetAllReleasesAsync();
- var allWinGetReleases = allReleases.Select(r => new WinGetVersion(r.TagName));
- var latestVersion = GetLatestMatchingVersion(allWinGetReleases, expectedVersion, includePrerelease);
- if (latestVersion == null)
- {
- this.Write(StreamType.Warning, $"No matching version found for {expectedVersion}");
- }
- else
- {
- expectedVersion = latestVersion.TagVersion;
- this.Write(StreamType.Verbose, $"Matching version found: {expectedVersion}");
- }
+ this.Write(StreamType.Verbose, $"Attempting to resolve version '{expectedVersion}'");
+ expectedVersion = await this.ResolveVersionAsync(expectedVersion, includePrerelease);
}
else
{
@@ -164,6 +153,32 @@ private static bool VersionPartMatch(string? partPattern, int partValue)
return partPattern == partValue.ToString();
}
+ private async Task ResolveVersionAsync(string version, bool includePrerelease)
+ {
+ try
+ {
+ var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli);
+ var allReleases = await gitHubClient.GetAllReleasesAsync();
+ var allWinGetReleases = allReleases.Select(r => new WinGetVersion(r.TagName));
+ var latestVersion = GetLatestMatchingVersion(allWinGetReleases, version, includePrerelease);
+ if (latestVersion == null)
+ {
+ this.Write(StreamType.Warning, $"No matching version found for {version}");
+ return version;
+ }
+ else
+ {
+ this.Write(StreamType.Verbose, $"Matching version found: {latestVersion.TagVersion}");
+ return latestVersion.TagVersion;
+ }
+ }
+ catch (Exception e)
+ {
+ this.Write(StreamType.Warning, $"Could not resolve version '{version}': {e.Message}");
+ return version;
+ }
+ }
+
private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers, bool force)
{
var seenCategories = new HashSet();
From 60ae0d8f3d98e9b09a9ad78d1df18c6f05521f5c Mon Sep 17 00:00:00 2001
From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com>
Date: Mon, 8 Sep 2025 13:01:25 -0700
Subject: [PATCH 5/8] CE
---
.../Helpers/AppxModuleHelper.cs | 2 +-
.../Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs | 2 ++
.../Microsoft.WinGet.Client.Engine.csproj | 4 ++--
.../Microsoft.WinGet.Configuration.Engine.csproj | 2 +-
4 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs
index e9558d75d4..40750a6c1e 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs
@@ -243,7 +243,7 @@ public async Task InstallFromGitHubReleaseAsync(string releaseTag, bool allUsers
/// A representing the asynchronous operation.
public async Task InstallWinGetSourceAsync()
{
- await this.DownloadPackageAndAddAsync(WinGetSourceUrl, WinGetSourceMsixName, null);
+ await this.DownloadPackageAndAddAsync(WinGetSourceUrl, WinGetSourceMsixName, options: null);
}
///
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs
index 35c32bdb17..e9fece7ea9 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs
@@ -32,6 +32,8 @@ public WinGetVersion(string version)
{
this.TagVersion = version;
toParseVersion = toParseVersion.Substring(1);
+
+ // Handle v-0.2*, v-0.3*, v-0.4*
if (toParseVersion.Length > 0 && toParseVersion[0] == '-')
{
toParseVersion = toParseVersion.Substring(1);
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj b/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj
index e54da55956..31048e86c0 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Microsoft.WinGet.Client.Engine.csproj
@@ -1,4 +1,4 @@
-
+
@@ -21,7 +21,7 @@
enable
-
+
$(DefineConstants);USE_PROD_CLSIDS
diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj
index 83c29cfe17..70f53cc531 100644
--- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj
+++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj
@@ -12,7 +12,7 @@
Debug;Release;ReleaseStatic
-
+
$(DefineConstants);USE_PROD_CLSIDS
From 654be227943b346dec0a4ca964c0b5b8e9c81e88 Mon Sep 17 00:00:00 2001
From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com>
Date: Mon, 8 Sep 2025 15:08:12 -0700
Subject: [PATCH 6/8] Fix build and add docs
---
.../Repair-WinGetPackageManager.md | 15 +++++--
.../Commands/WinGetPackageManagerCommand.cs | 45 +++++++++++++------
2 files changed, 43 insertions(+), 17 deletions(-)
diff --git a/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackageManager.md b/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackageManager.md
index b0f6e28a69..51d79e214f 100644
--- a/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackageManager.md
+++ b/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackageManager.md
@@ -17,7 +17,7 @@ Repairs the installation of the WinGet client on your computer.
### IntegrityVersionSet (Default)
```
-Repair-WinGetPackageManager [-AllUsers] [-Force] [-Version ] []
+Repair-WinGetPackageManager [-AllUsers] [-Force] [-Version ] [] [-IncludePreRelease]
```
### IntegrityLatestSet
@@ -54,6 +54,16 @@ This example shows how to repair they WinGet client by installing the latest ver
it functions properly. The **Force** parameter shuts down the version that is currently running so
that it can update the application files.
+### Example 3: Install a version with wildcards
+
+```powershell
+Repair-WinGetPackageManager -Version "1.*.1*" -Force
+```
+
+This example shows how to repair the WinGet client by installing a version that matches the
+specified version pattern. The **Force** parameter shuts down the version that is currently running
+so that it can update the application files.
+
## PARAMETERS
### -AllUsers
@@ -123,8 +133,7 @@ Accept wildcard characters: False
```
### -Version
-
-Use this parameter to specify the specific version of the WinGet client to install.
+Specifies the version of the WinGet client to install or repair. You can provide an exact version number or use wildcard characters (for example, `"1.*.1*"`) to match and install the latest version that fits the pattern.
```yaml
Type: System.String
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
index 0965fe2427..ef6525184c 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
@@ -112,15 +112,28 @@ public void Repair(string expectedVersion, bool allUsers, bool force, bool inclu
this.Wait(runningTask);
}
- private static WinGetVersion GetLatestMatchingVersion(IEnumerable versions, string pattern, bool includePrerelease)
+ ///
+ /// Tries to get the latest version matching the pattern.
+ ///
+ ///
+ /// Pattern only supports trailing wildcards.
+ /// - For example, the pattern can be: 1.11.*, 1.11.3*
+ /// - But it cannot be: 1.*1.1 or 1.*1*.1.
+ ///
+ /// List of versions to match against.
+ /// Pattern to match.
+ /// Include prerelease versions.
+ /// The resulting version.
+ /// True if a matching version was found.
+ private static bool TryGetLatestMatchingVersion(IEnumerable versions, string pattern, bool includePrerelease, out WinGetVersion result)
{
pattern = string.IsNullOrWhiteSpace(pattern) ? "*" : pattern;
var parts = pattern.Split('.');
- string? major = parts[0];
- string? minor = parts.Length > 1 ? parts[1] : null;
- string? build = parts.Length > 2 ? parts[2] : null;
- string? revision = parts.Length > 3 ? parts[3] : null;
+ string major = parts[0];
+ string minor = parts.Length > 1 ? parts[1] : string.Empty;
+ string build = parts.Length > 2 ? parts[2] : string.Empty;
+ string revision = parts.Length > 3 ? parts[3] : string.Empty;
if (!includePrerelease)
{
@@ -135,10 +148,17 @@ private static WinGetVersion GetLatestMatchingVersion(IEnumerable
VersionPartMatch(revision, v.Version.Revision))
.OrderBy(f => f.Version);
- return versions.Count() == 0 ? null : versions.Last();
+ if (!versions.Any())
+ {
+ result = null!;
+ return false;
+ }
+
+ result = versions.Last();
+ return true;
}
- private static bool VersionPartMatch(string? partPattern, int partValue)
+ private static bool VersionPartMatch(string partPattern, int partValue)
{
if (string.IsNullOrWhiteSpace(partPattern))
{
@@ -160,17 +180,14 @@ private async Task ResolveVersionAsync(string version, bool includePrere
var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli);
var allReleases = await gitHubClient.GetAllReleasesAsync();
var allWinGetReleases = allReleases.Select(r => new WinGetVersion(r.TagName));
- var latestVersion = GetLatestMatchingVersion(allWinGetReleases, version, includePrerelease);
- if (latestVersion == null)
- {
- this.Write(StreamType.Warning, $"No matching version found for {version}");
- return version;
- }
- else
+ if (TryGetLatestMatchingVersion(allWinGetReleases, version, includePrerelease, out var latestVersion))
{
this.Write(StreamType.Verbose, $"Matching version found: {latestVersion.TagVersion}");
return latestVersion.TagVersion;
}
+
+ this.Write(StreamType.Warning, $"No matching version found for {version}");
+ return version;
}
catch (Exception e)
{
From 0defac8f2b7ac8eac7568271bd96c580381106e9 Mon Sep 17 00:00:00 2001
From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com>
Date: Tue, 9 Sep 2025 10:24:44 -0700
Subject: [PATCH 7/8] Addressing comments
---
.../Repair-WinGetPackageManager.md | 4 ++--
.../Commands/WinGetPackageManagerCommand.cs | 23 +++++++++++--------
2 files changed, 16 insertions(+), 11 deletions(-)
diff --git a/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackageManager.md b/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackageManager.md
index 51d79e214f..24a82423f9 100644
--- a/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackageManager.md
+++ b/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackageManager.md
@@ -17,7 +17,7 @@ Repairs the installation of the WinGet client on your computer.
### IntegrityVersionSet (Default)
```
-Repair-WinGetPackageManager [-AllUsers] [-Force] [-Version ] [] [-IncludePreRelease]
+Repair-WinGetPackageManager [-AllUsers] [-Force] [-Version ] [-IncludePreRelease] []
```
### IntegrityLatestSet
@@ -57,7 +57,7 @@ that it can update the application files.
### Example 3: Install a version with wildcards
```powershell
-Repair-WinGetPackageManager -Version "1.*.1*" -Force
+Repair-WinGetPackageManager -Version "1.12.*" -Force
```
This example shows how to repair the WinGet client by installing a version that matches the
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
index ef6525184c..6d1dbd7283 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
@@ -116,9 +116,9 @@ public void Repair(string expectedVersion, bool allUsers, bool force, bool inclu
/// Tries to get the latest version matching the pattern.
///
///
- /// Pattern only supports trailing wildcards.
- /// - For example, the pattern can be: 1.11.*, 1.11.3*
- /// - But it cannot be: 1.*1.1 or 1.*1*.1.
+ /// Pattern only supports leading and trailing wildcards.
+ /// - For example, the pattern can be: 1.11.*, 1.11.3*, 1.11.*3
+ /// - But it cannot be: 1.*1*.1 or 1.1*1.1.
///
/// List of versions to match against.
/// Pattern to match.
@@ -130,10 +130,10 @@ private static bool TryGetLatestMatchingVersion(IEnumerable versi
pattern = string.IsNullOrWhiteSpace(pattern) ? "*" : pattern;
var parts = pattern.Split('.');
- string major = parts[0];
- string minor = parts.Length > 1 ? parts[1] : string.Empty;
- string build = parts.Length > 2 ? parts[2] : string.Empty;
- string revision = parts.Length > 3 ? parts[3] : string.Empty;
+ var major = parts.ElementAtOrDefault(0);
+ var minor = parts.ElementAtOrDefault(1);
+ var build = parts.ElementAtOrDefault(2);
+ var revision = parts.ElementAtOrDefault(3);
if (!includePrerelease)
{
@@ -158,14 +158,19 @@ private static bool TryGetLatestMatchingVersion(IEnumerable versi
return true;
}
- private static bool VersionPartMatch(string partPattern, int partValue)
+ private static bool VersionPartMatch(string? partPattern, int partValue)
{
if (string.IsNullOrWhiteSpace(partPattern))
{
return true;
}
- if (partPattern.EndsWith("*"))
+ if (partPattern!.StartsWith("*"))
+ {
+ return partValue.ToString().EndsWith(partPattern.TrimStart('*'));
+ }
+
+ if (partPattern!.EndsWith("*"))
{
return partValue.ToString().StartsWith(partPattern.TrimEnd('*'));
}
From af4e87406e9548c9c5d61fbda467fe8d57411577 Mon Sep 17 00:00:00 2001
From: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com>
Date: Mon, 29 Sep 2025 12:39:45 -0700
Subject: [PATCH 8/8] Addressing comments
---
.../Commands/WinGetPackageManagerCommand.cs | 109 +++---------------
.../Helpers/AppxModuleHelper.cs | 5 +-
.../Helpers/GitHubClient.cs | 94 +++++++++++++++
3 files changed, 114 insertions(+), 94 deletions(-)
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
index 6d1dbd7283..f6ca0465df 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs
@@ -8,7 +8,6 @@ namespace Microsoft.WinGet.Client.Engine.Commands
{
using System;
using System.Collections.Generic;
- using System.Linq;
using System.Management.Automation;
using System.Threading.Tasks;
using Microsoft.WinGet.Client.Engine.Commands.Common;
@@ -99,7 +98,24 @@ public void Repair(string expectedVersion, bool allUsers, bool force, bool inclu
if (!string.IsNullOrWhiteSpace(expectedVersion))
{
this.Write(StreamType.Verbose, $"Attempting to resolve version '{expectedVersion}'");
- expectedVersion = await this.ResolveVersionAsync(expectedVersion, includePrerelease);
+ var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli);
+ try
+ {
+ var resolvedVersion = await gitHubClient.ResolveVersionAsync(expectedVersion, includePrerelease);
+ if (!string.IsNullOrEmpty(resolvedVersion))
+ {
+ this.Write(StreamType.Verbose, $"Matching version found: {resolvedVersion}");
+ expectedVersion = resolvedVersion!;
+ }
+ else
+ {
+ this.Write(StreamType.Warning, $"No matching version found for {expectedVersion}");
+ }
+ }
+ catch (Exception ex)
+ {
+ this.Write(StreamType.Warning, $"Could not resolve version '{expectedVersion}': {ex.Message}");
+ }
}
else
{
@@ -112,95 +128,6 @@ public void Repair(string expectedVersion, bool allUsers, bool force, bool inclu
this.Wait(runningTask);
}
- ///
- /// Tries to get the latest version matching the pattern.
- ///
- ///
- /// Pattern only supports leading and trailing wildcards.
- /// - For example, the pattern can be: 1.11.*, 1.11.3*, 1.11.*3
- /// - But it cannot be: 1.*1*.1 or 1.1*1.1.
- ///
- /// List of versions to match against.
- /// Pattern to match.
- /// Include prerelease versions.
- /// The resulting version.
- /// True if a matching version was found.
- private static bool TryGetLatestMatchingVersion(IEnumerable versions, string pattern, bool includePrerelease, out WinGetVersion result)
- {
- pattern = string.IsNullOrWhiteSpace(pattern) ? "*" : pattern;
-
- var parts = pattern.Split('.');
- var major = parts.ElementAtOrDefault(0);
- var minor = parts.ElementAtOrDefault(1);
- var build = parts.ElementAtOrDefault(2);
- var revision = parts.ElementAtOrDefault(3);
-
- if (!includePrerelease)
- {
- versions = versions.Where(v => !v.IsPrerelease);
- }
-
- versions = versions
- .Where(v =>
- VersionPartMatch(major, v.Version.Major) &&
- VersionPartMatch(minor, v.Version.Minor) &&
- VersionPartMatch(build, v.Version.Build) &&
- VersionPartMatch(revision, v.Version.Revision))
- .OrderBy(f => f.Version);
-
- if (!versions.Any())
- {
- result = null!;
- return false;
- }
-
- result = versions.Last();
- return true;
- }
-
- private static bool VersionPartMatch(string? partPattern, int partValue)
- {
- if (string.IsNullOrWhiteSpace(partPattern))
- {
- return true;
- }
-
- if (partPattern!.StartsWith("*"))
- {
- return partValue.ToString().EndsWith(partPattern.TrimStart('*'));
- }
-
- if (partPattern!.EndsWith("*"))
- {
- return partValue.ToString().StartsWith(partPattern.TrimEnd('*'));
- }
-
- return partPattern == partValue.ToString();
- }
-
- private async Task ResolveVersionAsync(string version, bool includePrerelease)
- {
- try
- {
- var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli);
- var allReleases = await gitHubClient.GetAllReleasesAsync();
- var allWinGetReleases = allReleases.Select(r => new WinGetVersion(r.TagName));
- if (TryGetLatestMatchingVersion(allWinGetReleases, version, includePrerelease, out var latestVersion))
- {
- this.Write(StreamType.Verbose, $"Matching version found: {latestVersion.TagVersion}");
- return latestVersion.TagVersion;
- }
-
- this.Write(StreamType.Warning, $"No matching version found for {version}");
- return version;
- }
- catch (Exception e)
- {
- this.Write(StreamType.Warning, $"Could not resolve version '{version}': {e.Message}");
- return version;
- }
- }
-
private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers, bool force)
{
var seenCategories = new HashSet();
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs
index 40750a6c1e..0e85774bcd 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs
@@ -127,11 +127,10 @@ public AppxModuleHelper(PowerShellCmdlet pwshCmdlet)
///
/// Calls Get-AppxPackage Microsoft.Winget.Source.
///
- /// Whether to get for all users.
/// Result of Get-AppxPackage.
- public PSObject? GetWinGetSourceObject(bool allUsers = false)
+ public PSObject? GetWinGetSourceObject()
{
- return this.GetAppxObject(WinGetSourceName, allUsers);
+ return this.GetAppxObject(WinGetSourceName);
}
///
diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs
index e47c8de390..0d73a50736 100644
--- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs
+++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs
@@ -6,9 +6,13 @@
namespace Microsoft.WinGet.Client.Engine.Helpers
{
+ using System;
using System.Collections.Generic;
+ using System.Linq;
using System.Threading.Tasks;
+ using Microsoft.WinGet.Common.Command;
using Octokit;
+ using static Microsoft.WinGet.Client.Engine.Common.Constants;
///
/// Handles GitHub interactions.
@@ -82,5 +86,95 @@ public async Task> GetAllReleasesAsync()
{
return await this.gitHubClient.Repository.Release.GetAll(this.owner, this.repo);
}
+
+ ///
+ /// Resolve a version string to the latest matching version from GitHub releases.
+ ///
+ /// Version string to resolve. Can include wildcards (*).
+ /// Whether to include prerelease versions in the search.
+ /// Resolved version string or null if no match found.
+ public async Task ResolveVersionAsync(string version, bool includePrerelease)
+ {
+ var allReleases = await this.GetAllReleasesAsync();
+ var allWinGetReleases = allReleases.Select(r => new WinGetVersion(r.TagName));
+ if (TryGetLatestMatchingVersion(allWinGetReleases, version, includePrerelease, out var latestVersion))
+ {
+ return latestVersion.TagVersion;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Tries to get the latest version matching the pattern.
+ ///
+ ///
+ /// Pattern only supports leading and trailing wildcards.
+ /// - For example, the pattern can be: 1.11.*, 1.11.3*, 1.11.*3
+ /// - But it cannot be: 1.*1*.1 or 1.1*1.1.
+ ///
+ /// List of versions to match against.
+ /// Pattern to match.
+ /// Include prerelease versions.
+ /// The resulting version.
+ /// True if a matching version was found.
+ private static bool TryGetLatestMatchingVersion(IEnumerable versions, string pattern, bool includePrerelease, out WinGetVersion result)
+ {
+ pattern = string.IsNullOrWhiteSpace(pattern) ? "*" : pattern;
+
+ var parts = pattern.Split('.');
+ var major = parts.ElementAtOrDefault(0);
+ var minor = parts.ElementAtOrDefault(1);
+ var build = parts.ElementAtOrDefault(2);
+ var revision = parts.ElementAtOrDefault(3);
+
+ if (!includePrerelease)
+ {
+ versions = versions.Where(v => !v.IsPrerelease);
+ }
+
+ versions = versions
+ .Where(v =>
+ VersionPartMatch(major, v.Version.Major) &&
+ VersionPartMatch(minor, v.Version.Minor) &&
+ VersionPartMatch(build, v.Version.Build) &&
+ VersionPartMatch(revision, v.Version.Revision))
+ .OrderBy(f => f.Version);
+
+ if (!versions.Any())
+ {
+ result = null!;
+ return false;
+ }
+
+ result = versions.Last();
+ return true;
+ }
+
+ ///
+ /// Checks if a version part matches a pattern.
+ ///
+ /// Version part pattern.
+ /// Version part value.
+ /// True if the part matches the pattern.
+ private static bool VersionPartMatch(string? partPattern, int partValue)
+ {
+ if (string.IsNullOrWhiteSpace(partPattern))
+ {
+ return true;
+ }
+
+ if (partPattern!.StartsWith("*"))
+ {
+ return partValue.ToString().EndsWith(partPattern.TrimStart('*'));
+ }
+
+ if (partPattern!.EndsWith("*"))
+ {
+ return partValue.ToString().StartsWith(partPattern.TrimEnd('*'));
+ }
+
+ return partPattern == partValue.ToString();
+ }
}
}