diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index 8ec1e210c6..0816a7db29 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -1,3 +1,6 @@ ## New in v1.28 + +## Bug Fixes +* The PowerShell module now checks for the executable to be in the path before calling it when using `Repair-WinGetPackageManager` diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs index 9669da8dbc..cf39ab2fd3 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs @@ -7,8 +7,10 @@ namespace Microsoft.WinGet.Client.Engine.Common { using System; + using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; + using System.Linq; using System.Management.Automation; using Microsoft.WinGet.Client.Engine.Exceptions; using Microsoft.WinGet.Client.Engine.Helpers; @@ -39,29 +41,55 @@ public static void AssertWinGet(PowerShellCmdlet pwshCmdlet, string expectedVers return; } - try - { - // Start by calling winget without its WindowsApp PFN path. - // If it succeeds and the exit code is 0 then we are good. - var wingetCliWrapper = new WingetCLIWrapper(false); - var result = wingetCliWrapper.RunCommand(pwshCmdlet, "--version"); - result.VerifyExitCode(); - } - catch (Win32Exception e) - { - pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' Win32Exception {e.Message}"); - throw new WinGetIntegrityException(GetReason(pwshCmdlet)); - } - catch (Exception e) when (e is WinGetCLIException || e is WinGetCLITimeoutException) - { - pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' WinGetCLIException {e.Message}"); - throw new WinGetIntegrityException(IntegrityCategory.Failure, e); - } - catch (Exception e) + pwshCmdlet.ExecuteInPowerShellThread(() => { - pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' Exception {e.Message}"); - throw new WinGetIntegrityException(IntegrityCategory.Unknown, e); - } + try + { + // First check if winget is registered in the current session using Get-Command. + var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); + { + // Ignore the error, we will check the results instead + ps.AddCommand("Get-Command").AddParameter("Name", "winget").AddParameter("ErrorAction", "Ignore"); + Collection results = ps.Invoke(); + ps.Dispose(); + if (results == null || + results.Count != 1 || + results.ElementAt(0).BaseObject is not CommandInfo) + { + // It's expected that the command is found, is the only command, and is a CommandInfo object. + // If it is not, then winget is not properly registered in the current session and we need to figure out why. + pwshCmdlet.Write(StreamType.Verbose, $"'winget' was not found using Get-Command"); + throw new WinGetIntegrityException(GetReason(pwshCmdlet, false)); + } + } + + // If the command is registered, try calling winget without its WindowsApp PFN path. + // If it succeeds and the exit code is 0 then we are good. + var wingetCliWrapper = new WingetCLIWrapper(false); + var result = wingetCliWrapper.RunCommand(pwshCmdlet, "--version"); + result.VerifyExitCode(); + } + catch (WinGetIntegrityException) + { + // We already know the reason why it failed. Just rethrow, but be sure to preserve the stack trace + throw; + } + catch (Win32Exception e) + { + pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' Win32Exception {e.Message}"); + throw new WinGetIntegrityException(GetReason(pwshCmdlet, true)); + } + catch (Exception e) when (e is WinGetCLIException || e is WinGetCLITimeoutException) + { + pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' WinGetCLIException {e.Message}"); + throw new WinGetIntegrityException(IntegrityCategory.Failure, e); + } + catch (Exception e) + { + pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' Exception {e.Message}"); + throw new WinGetIntegrityException(IntegrityCategory.Unknown, e); + } + }); // WinGet is installed. Verify version if needed. if (!string.IsNullOrEmpty(expectedVersion)) @@ -81,31 +109,39 @@ public static void AssertWinGet(PowerShellCmdlet pwshCmdlet, string expectedVers } } - private static IntegrityCategory GetReason(PowerShellCmdlet pwshCmdlet) + private static IntegrityCategory GetReason(PowerShellCmdlet pwshCmdlet, bool commandIsRegistered) { // Ok, so you are here because calling winget --version failed. Lets try to figure out why. var category = IntegrityCategory.Unknown; - pwshCmdlet.ExecuteInPowerShellThread(() => + + // If the command is registered, then we can try to call it + // Otherwise we will skip this step since it will always result in category remaining unknown + if (commandIsRegistered) { + pwshCmdlet.ExecuteInPowerShellThread(() => + { // When running winget.exe on PowerShell the message of the Win32Exception will distinguish between // 'The system cannot find the file specified' and 'No applicable app licenses found' but of course // the HRESULT is the same (E_FAIL). // To not compare strings let Powershell handle it. If calling winget throws an // ApplicationFailedException then is most likely that the license is not there. - try - { - var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); - ps.AddCommand("winget").Invoke(); - } - catch (ApplicationFailedException e) - { - pwshCmdlet.Write(StreamType.Verbose, e.Message); - category = IntegrityCategory.AppInstallerNoLicense; - } - catch (Exception) - { - } - }); + try + { + using (var ps = PowerShell.Create(RunspaceMode.CurrentRunspace)) + { + ps.AddCommand("winget").Invoke(); + } + } + catch (ApplicationFailedException e) + { + pwshCmdlet.Write(StreamType.Verbose, e.Message); + category = IntegrityCategory.AppInstallerNoLicense; + } + catch (Exception) + { + } + }); + } if (category != IntegrityCategory.Unknown) {