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
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Repairs the installation of the WinGet client on your computer.
### IntegrityVersionSet (Default)

```
Repair-WinGetPackageManager [-AllUsers] [-Force] [-Version <String>] [<CommonParameters>]
Repair-WinGetPackageManager [-AllUsers] [-Force] [-Version <String>] [-IncludePreRelease] [<CommonParameters>]
```

### IntegrityLatestSet
Expand Down Expand Up @@ -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.12.*" -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// <copyright file="RepairWinGetPackageManagerCmdlet.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
// </copyright>
Expand Down Expand Up @@ -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());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,40 @@ public void RepairUsingLatest(bool preRelease, bool allUsers, bool force)
/// <param name="expectedVersion">The expected version, if any.</param>
/// <param name="allUsers">Install for all users. Requires admin.</param>
/// <param name="force">Force application shutdown.</param>
public void Repair(string expectedVersion, bool allUsers, bool force)
/// <param name="includePrerelease">Include prerelease versions when matching version.</param>
public void Repair(string expectedVersion, bool allUsers, bool force, bool includePrerelease)
{
this.ValidateWhenAllUsers(allUsers);
var runningTask = this.RunOnMTA(
async () =>
{
if (!string.IsNullOrWhiteSpace(expectedVersion))
{
this.Write(StreamType.Verbose, $"Attempting to resolve version '{expectedVersion}'");
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
{
this.Write(StreamType.Verbose, "No version specified.");
}

await this.RepairStateMachineAsync(expectedVersion, allUsers, force);
return true;
});
Expand Down Expand Up @@ -138,7 +166,7 @@ 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:
Expand All @@ -156,6 +184,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:
Expand Down Expand Up @@ -197,10 +228,17 @@ 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 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);
appxModule.RegisterAppInstaller(toRegisterVersion);
await appxModule.RegisterAppInstallerAsync(toRegisterVersion, allUsers);
}

private void RepairEnvPath()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// <copyright file="IntegrityCategory.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
// </copyright>
Expand Down Expand Up @@ -65,5 +65,10 @@ public enum IntegrityCategory
/// No applicable license found.
/// </summary>
AppInstallerNoLicense,

/// <summary>
/// WinGet source is not installed.
/// </summary>
WinGetSourceNotInstalled,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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";
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a COM API for fetching source information? Since the winget source is a well-known source (can be disabled, but not removed), would it be a possible consideration to dynamically fetch the source URL through the API? That way, in the event a source3.msix ever becomes the default, this portion of the cmdlet would just work?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was able to find the CDN URL https://cdn.winget.microsoft.com/cache but not the source2.msix file as part of the COM API. @JohnMcPMS, is there a recommended/forward compatible method to obtain the source2.msix URL instead of hardcoding it in the powershell module? Or specifying it here is okay?

Copy link
Member

Choose a reason for hiding this comment

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

It is an internal implementation detail, so there isn't a way to get the file name. I don't see why we need to specify it here twice though.

Copy link
Member

Choose a reason for hiding this comment

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

Can we not have the file name twice? Use string concatenation or formatting or whatever to only have source2.msix the one time.


private readonly PowerShellCmdlet pwshCmdlet;
private readonly HttpClientHelper httpClientHelper;
private Lazy<HashSet<Architecture>> frameworkArchitectures;
Expand All @@ -112,21 +117,32 @@ public AppxModuleHelper(PowerShellCmdlet pwshCmdlet)
/// <summary>
/// Calls Get-AppxPackage Microsoft.DesktopAppInstaller.
/// </summary>
/// <param name="allUsers">Whether to get for all users.</param>
/// <returns>Result of Get-AppxPackage.</returns>
public PSObject? GetAppInstallerObject(bool allUsers = false)
{
return this.GetAppxObject(AppInstallerName, allUsers);
}

/// <summary>
/// Calls Get-AppxPackage Microsoft.Winget.Source.
/// </summary>
/// <returns>Result of Get-AppxPackage.</returns>
public PSObject? GetAppInstallerObject()
public PSObject? GetWinGetSourceObject()
{
return this.GetAppxObject(AppInstallerName);
return this.GetAppxObject(WinGetSourceName);
}

/// <summary>
/// Gets the string value a property from the Get-AppxPackage object of AppInstaller.
/// </summary>
/// <param name="propertyName">Property name.</param>
/// <param name="allUsers">Whether to get for all users.</param>
/// <returns>Value, null if doesn't exist.</returns>
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();
Expand All @@ -139,15 +155,26 @@ public AppxModuleHelper(PowerShellCmdlet pwshCmdlet)
return result;
}

/// <summary>
/// Checks if winget source is installed.
/// </summary>
/// <returns>True if installed.</returns>
public bool IsWinGetSourceInstalled()
{
return this.GetWinGetSourceObject() is not null;
}

/// <summary>
/// Calls Add-AppxPackage to register with AppInstaller's AppxManifest.xml.
/// </summary>
/// <param name="releaseTag">Release tag of GitHub release.</param>
public void RegisterAppInstaller(string releaseTag)
/// <param name="allUsers">Whether to register for all users.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
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)
{
Expand All @@ -157,43 +184,21 @@ 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}";
}
}

// Ensure that all dependencies are present when attempting to register.
// 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<string, object>
{
{ Path, appxManifestPath },
},
new List<string>
{
Register,
DisableDevelopmentMode,
});
this.RegisterAppInstallerInternal(allUsers);
}

/// <summary>
Expand Down Expand Up @@ -231,6 +236,15 @@ public async Task InstallFromGitHubReleaseAsync(string releaseTag, bool allUsers
}
}

/// <summary>
/// Installs the WinGet source by downloading and adding package.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task InstallWinGetSourceAsync()
{
await this.DownloadPackageAndAddAsync(WinGetSourceUrl, WinGetSourceMsixName, options: null);
}

/// <summary>
/// Gets the Xaml dependency package name and release tag based on the provided WinGet release tag.
/// </summary>
Expand Down Expand Up @@ -278,6 +292,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)
{
Expand Down Expand Up @@ -320,14 +338,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<string>();
if (allUsers)
{
options.Add(AllUsers);
}

return this.ExecuteAppxCmdlet(
GetAppxPackage,
new Dictionary<string, object>
{
{ Name, packageName },
})
},
options)
.FirstOrDefault();
}

Expand Down Expand Up @@ -808,5 +833,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<string, object>
{
{ Path, appxManifestPath },
},
new List<string>
{
Register,
DisableDevelopmentMode,
});
}
}
}
Loading
Loading