Skip to content

Commit 419875b

Browse files
authored
Make Repair-WGPM a COM-aware cmdlet and rework version retrieval (#5842)
Fixes #5826 and #5827 ## Issue A previous change introduced a COM API to retrieve the WinGet version. The PowerShell methods to get the version were updated to try using it before invoking the existing method (run `winget --version`). This caused Repair-WGPM to use COM for the first time (IFF a version specifier was provided [which includes `-Latest`]). This caused the two linked issues: 1. #5826 :: In .NET Framework (Windows PowerShell), the .winmd file must be found in order to generate the COM type information at runtime. This is required when jit'ing the new version API, used only when a version specifier is provided. This doesn't affect .NET Core (PowerShell 7) because exceptions are swallowed to support backward compat and the types are all pre-generated by CsWinRT. Only commands deriving from a specific type were doing the initialization required. 2. #5827 :: Calling a COM API means that the server is active, making attempts to install the package fail due to an in-use error. This required `-Force` to be provided, again only if a version specifier was provided. ## Change The larger part of this change reworks the existing assert and repair state machine to better re-use the call to `winget --version` that is actually attempting to probe for WinGet CLI functionality. We keep that result around and use it when comparing to the supplied target version rather than attempting to retrieve the version again. If the version is not correct, we attach it to the exception that is thrown so that we can re-use it once again during the attempt to install the different version. Since the first attempt to call `winget --version` has to succeed in order to get to the code that would check the current version, we can successfully avoid the COM call in this path every time. Ultimately this means that if WinGet is already installed properly, attempting to change the version with Repair only gets the version once instead of the previous 3 times. The final portion of the change updates the base command for Repair and Assert to the one that provides the COM initialization. While this shouldn't be necessary with the other portion, it is preferable to supply `-Force` as a workaround than to simply wait for a resolution if the type cannot be loaded.
1 parent c49d442 commit 419875b

File tree

4 files changed

+46
-18
lines changed

4 files changed

+46
-18
lines changed

src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace Microsoft.WinGet.Client.Engine.Commands
2121
/// <summary>
2222
/// Used by Repair-WinGetPackageManager and Assert-WinGetPackageManager.
2323
/// </summary>
24-
public sealed class WinGetPackageManagerCommand : BaseCommand
24+
public sealed class WinGetPackageManagerCommand : ManagementDeploymentCommand
2525
{
2626
private const string EnvPath = "env:PATH";
2727

@@ -132,7 +132,7 @@ private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers
132132
switch (currentCategory)
133133
{
134134
case IntegrityCategory.UnexpectedVersion:
135-
await this.InstallDifferentVersionAsync(new WinGetVersion(expectedVersion), allUsers, force);
135+
await this.InstallDifferentVersionAsync(new WinGetVersion(expectedVersion), e.InstalledVersion, allUsers, force);
136136
break;
137137
case IntegrityCategory.NotInPath:
138138
this.RepairEnvPath();
@@ -167,9 +167,13 @@ private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers
167167
}
168168
}
169169

170-
private async Task InstallDifferentVersionAsync(WinGetVersion toInstallVersion, bool allUsers, bool force)
170+
private async Task InstallDifferentVersionAsync(WinGetVersion toInstallVersion, WinGetVersion? installedVersion, bool allUsers, bool force)
171171
{
172-
var installedVersion = WinGetVersion.InstalledWinGetVersion(this);
172+
if (installedVersion == null)
173+
{
174+
installedVersion = WinGetVersion.InstalledWinGetVersion(this);
175+
}
176+
173177
bool isDowngrade = installedVersion.CompareAsDeployment(toInstallVersion) > 0;
174178

175179
string message = $"Installed WinGet version '{installedVersion.TagVersion}' " +

src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,14 @@ public static void AssertWinGet(PowerShellCmdlet pwshCmdlet, string expectedVers
3939
return;
4040
}
4141

42+
WinGetCLICommandResult? versionResult = null;
43+
4244
try
4345
{
4446
// Start by calling winget without its WindowsApp PFN path.
4547
// If it succeeds and the exit code is 0 then we are good.
46-
var wingetCliWrapper = new WingetCLIWrapper(false);
47-
var result = wingetCliWrapper.RunCommand(pwshCmdlet, "--version");
48-
result.VerifyExitCode();
48+
versionResult = WinGetVersion.RunWinGetVersionFromCLI(pwshCmdlet, false);
49+
versionResult.VerifyExitCode();
4950
}
5051
catch (Win32Exception e)
5152
{
@@ -68,15 +69,16 @@ public static void AssertWinGet(PowerShellCmdlet pwshCmdlet, string expectedVers
6869
{
6970
// This assumes caller knows that the version exist.
7071
WinGetVersion expectedWinGetVersion = new WinGetVersion(expectedVersion);
71-
var installedVersion = WinGetVersion.InstalledWinGetVersion(pwshCmdlet);
72+
var installedVersion = WinGetVersion.InstalledWinGetVersion(pwshCmdlet, versionResult);
7273
if (expectedWinGetVersion.CompareTo(installedVersion) != 0)
7374
{
7475
throw new WinGetIntegrityException(
7576
IntegrityCategory.UnexpectedVersion,
7677
string.Format(
7778
Resources.IntegrityUnexpectedVersionMessage,
7879
installedVersion.TagVersion,
79-
expectedVersion));
80+
expectedVersion))
81+
{ InstalledVersion = installedVersion };
8082
}
8183
}
8284
}

src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetIntegrityException.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// -----------------------------------------------------------------------------
1+
// -----------------------------------------------------------------------------
22
// <copyright file="WinGetIntegrityException.cs" company="Microsoft Corporation">
33
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
44
// </copyright>
@@ -9,6 +9,7 @@ namespace Microsoft.WinGet.Client.Engine.Exceptions
99
using System;
1010
using System.Management.Automation;
1111
using Microsoft.WinGet.Client.Engine.Common;
12+
using Microsoft.WinGet.Client.Engine.Helpers;
1213
using Microsoft.WinGet.Resources;
1314

1415
/// <summary>
@@ -53,6 +54,11 @@ public WinGetIntegrityException(IntegrityCategory category, string message)
5354
/// </summary>
5455
public IntegrityCategory Category { get; }
5556

57+
/// <summary>
58+
/// Gets or sets the installed version.
59+
/// </summary>
60+
internal WinGetVersion? InstalledVersion { get; set; }
61+
5662
private static string GetMessage(IntegrityCategory category) => category switch
5763
{
5864
IntegrityCategory.Failure => Resources.IntegrityFailureMessage,

src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,39 @@ public WinGetVersion(string version)
6464
/// </summary>
6565
public bool IsPrerelease { get; }
6666

67+
/// <summary>
68+
/// Runs the winget version command.
69+
/// </summary>
70+
/// <param name="pwshCmdlet">PowerShell cmdlet.</param>
71+
/// <param name="fullPath">Use full path or not.</param>
72+
/// <returns>The command result.</returns>
73+
public static WinGetCLICommandResult RunWinGetVersionFromCLI(PowerShellCmdlet pwshCmdlet, bool fullPath = true)
74+
{
75+
var wingetCliWrapper = new WingetCLIWrapper(fullPath);
76+
return wingetCliWrapper.RunCommand(pwshCmdlet, "--version");
77+
}
78+
6779
/// <summary>
6880
/// Gets the version of the installed winget.
6981
/// </summary>
7082
/// <param name="pwshCmdlet">PowerShell cmdlet.</param>
83+
/// <param name="versionResult">A command result from running previously.</param>
7184
/// <returns>The WinGetVersion.</returns>
72-
public static WinGetVersion InstalledWinGetVersion(PowerShellCmdlet pwshCmdlet)
85+
public static WinGetVersion InstalledWinGetVersion(PowerShellCmdlet pwshCmdlet, WinGetCLICommandResult? versionResult = null)
7386
{
74-
// Try getting the version through COM if it is available (user might have an older build installed)
75-
string? comVersion = PackageManagerWrapper.Instance.GetVersion();
76-
if (comVersion != null)
87+
if (versionResult == null || versionResult.ExitCode != 0)
7788
{
78-
return new WinGetVersion(comVersion);
89+
// Try getting the version through COM if it is available (user might have an older build installed)
90+
string? comVersion = PackageManagerWrapper.Instance.GetVersion();
91+
if (comVersion != null)
92+
{
93+
return new WinGetVersion(comVersion);
94+
}
95+
96+
versionResult = RunWinGetVersionFromCLI(pwshCmdlet);
7997
}
8098

81-
var wingetCliWrapper = new WingetCLIWrapper();
82-
var result = wingetCliWrapper.RunCommand(pwshCmdlet, "--version");
83-
return new WinGetVersion(result.StdOut.Replace(Environment.NewLine, string.Empty));
99+
return new WinGetVersion(versionResult.StdOut.Replace(Environment.NewLine, string.Empty));
84100
}
85101

86102
/// <summary>

0 commit comments

Comments
 (0)