From 9adeb54f60795c34e7c892346903318001f7609a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sun, 7 Jul 2024 16:43:48 +0200 Subject: [PATCH 1/6] Update NuGet Modules --- .../UniGetUI.Core.Classes.Tests.csproj | 9 ++++++--- .../UniGetUI.Core.Data.Tests.csproj | 9 ++++++--- .../UniGetUI.Core.IconEngine.Tests.csproj | 9 ++++++--- .../UniGetUI.Core.LanguageEngine.Tests.csproj | 9 ++++++--- .../UniGetUI.Core.Logging.Tests.csproj | 9 ++++++--- .../UniGetUI.Core.Settings.Tests.csproj | 9 ++++++--- .../UniGetUI.Core.Tools.Tests.csproj | 9 ++++++--- ...xternalLibraries.WindowsPackageManager.Interop.csproj | 4 ++-- 8 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/UniGetUI.Core.Classes.Tests/UniGetUI.Core.Classes.Tests.csproj b/src/UniGetUI.Core.Classes.Tests/UniGetUI.Core.Classes.Tests.csproj index 2112c9731..8c72c09d0 100644 --- a/src/UniGetUI.Core.Classes.Tests/UniGetUI.Core.Classes.Tests.csproj +++ b/src/UniGetUI.Core.Classes.Tests/UniGetUI.Core.Classes.Tests.csproj @@ -32,11 +32,14 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/UniGetUI.Core.Data.Tests/UniGetUI.Core.Data.Tests.csproj b/src/UniGetUI.Core.Data.Tests/UniGetUI.Core.Data.Tests.csproj index 40e7ba506..a3c44ed60 100644 --- a/src/UniGetUI.Core.Data.Tests/UniGetUI.Core.Data.Tests.csproj +++ b/src/UniGetUI.Core.Data.Tests/UniGetUI.Core.Data.Tests.csproj @@ -33,10 +33,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/UniGetUI.Core.IconEngine.Tests/UniGetUI.Core.IconEngine.Tests.csproj b/src/UniGetUI.Core.IconEngine.Tests/UniGetUI.Core.IconEngine.Tests.csproj index c57c5ebbb..a1b45886e 100644 --- a/src/UniGetUI.Core.IconEngine.Tests/UniGetUI.Core.IconEngine.Tests.csproj +++ b/src/UniGetUI.Core.IconEngine.Tests/UniGetUI.Core.IconEngine.Tests.csproj @@ -33,10 +33,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/UniGetUI.Core.Language.Tests/UniGetUI.Core.LanguageEngine.Tests.csproj b/src/UniGetUI.Core.Language.Tests/UniGetUI.Core.LanguageEngine.Tests.csproj index 73cfcf9a7..b2cfe2ccd 100644 --- a/src/UniGetUI.Core.Language.Tests/UniGetUI.Core.LanguageEngine.Tests.csproj +++ b/src/UniGetUI.Core.Language.Tests/UniGetUI.Core.LanguageEngine.Tests.csproj @@ -33,10 +33,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/UniGetUI.Core.Logging.Tests/UniGetUI.Core.Logging.Tests.csproj b/src/UniGetUI.Core.Logging.Tests/UniGetUI.Core.Logging.Tests.csproj index 42e4db33e..627a1a4b8 100644 --- a/src/UniGetUI.Core.Logging.Tests/UniGetUI.Core.Logging.Tests.csproj +++ b/src/UniGetUI.Core.Logging.Tests/UniGetUI.Core.Logging.Tests.csproj @@ -33,10 +33,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/UniGetUI.Core.Settings.Tests/UniGetUI.Core.Settings.Tests.csproj b/src/UniGetUI.Core.Settings.Tests/UniGetUI.Core.Settings.Tests.csproj index e2ad5a4d3..6a8eae4e3 100644 --- a/src/UniGetUI.Core.Settings.Tests/UniGetUI.Core.Settings.Tests.csproj +++ b/src/UniGetUI.Core.Settings.Tests/UniGetUI.Core.Settings.Tests.csproj @@ -30,9 +30,12 @@ - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/UniGetUI.Core.Tools.Tests/UniGetUI.Core.Tools.Tests.csproj b/src/UniGetUI.Core.Tools.Tests/UniGetUI.Core.Tools.Tests.csproj index 3ab53cd19..3de765a84 100644 --- a/src/UniGetUI.Core.Tools.Tests/UniGetUI.Core.Tools.Tests.csproj +++ b/src/UniGetUI.Core.Tools.Tests/UniGetUI.Core.Tools.Tests.csproj @@ -30,9 +30,12 @@ - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/WindowsPackageManager.Interop/ExternalLibraries.WindowsPackageManager.Interop.csproj b/src/WindowsPackageManager.Interop/ExternalLibraries.WindowsPackageManager.Interop.csproj index 622f013d9..fa7567e3b 100644 --- a/src/WindowsPackageManager.Interop/ExternalLibraries.WindowsPackageManager.Interop.csproj +++ b/src/WindowsPackageManager.Interop/ExternalLibraries.WindowsPackageManager.Interop.csproj @@ -45,14 +45,14 @@ - Feed the $(TargetDir)\WINMD path to CsWinRT in order to generate the projected classes NOTE: Suppressing the warning only is not enough as this will cause CoreClrInitFailure (0x80008089) error. --> - + NU1701 true none false - + NU1701 true none From 20e13895084e33139868e0e15c99ff159a3f8b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sun, 7 Jul 2024 17:18:30 +0200 Subject: [PATCH 2/6] Add WinGet support for listing installed packages --- .../WinGet.cs | 194 +--- .../WinGetHelpers.cs | 846 ------------------ .../WinGetHelpers/BundledWinGetHelper.cs | 651 ++++++++++++++ .../WinGetHelpers/IWinGetManagerHelpers.cs | 48 + .../WinGetHelpers/NativeWinGetHelper.cs | 454 ++++++++++ 5 files changed, 1160 insertions(+), 1033 deletions(-) delete mode 100644 src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers.cs create mode 100644 src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/BundledWinGetHelper.cs create mode 100644 src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/IWinGetManagerHelpers.cs create mode 100644 src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs index 660e0501c..827fe7e66 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs @@ -115,199 +115,18 @@ protected override async Task FindPackages_UnSafe(string query) { return await WinGetHelper.Instance.FindPackages_UnSafe(this, query); } - + protected override async Task GetAvailableUpdates_UnSafe() { - if (Settings.Get("ForceLegacyBundledWinGet")) - return await BundledWinGetLegacyMethods.GetAvailableUpdates_UnSafe(this); - - List Packages = []; - - Process p = new() - { - StartInfo = new ProcessStartInfo() - { - FileName = "cmd.exe", - Arguments = "/C " + PowerShellPath + " " + PowerShellPromptArgs, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - CreateNoWindow = true, - StandardOutputEncoding = CodePagesEncodingProvider.Instance.GetEncoding(CoreData.CODE_PAGE), - StandardInputEncoding = new UTF8Encoding(false), - StandardErrorEncoding = CodePagesEncodingProvider.Instance.GetEncoding(CoreData.CODE_PAGE), - } - }; - - ManagerClasses.Classes.ProcessTaskLogger logger = TaskLogger.CreateNew(LoggableTaskType.ListUpdates, p); - - p.Start(); - - string command = """ - Write-Output (Get-Module -Name Microsoft.WinGet.Client).Version - Import-Module Microsoft.WinGet.Client - function Print-WinGetPackage { - param ( - [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $Name, - [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $Id, - [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $InstalledVersion, - [Parameter(ValueFromPipelineByPropertyName)] [string[]] $AvailableVersions, - [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [bool] $IsUpdateAvailable, - [Parameter(ValueFromPipelineByPropertyName)] [string] $Source - ) - process { - if($IsUpdateAvailable) - { - Write-Output("#" + $Name + "`t" + $Id + "`t" + $InstalledVersion + "`t" + $AvailableVersions[0] + "`t" + $Source) - } - } - } - - Get-WinGetPackage | Print-WinGetPackage - - exit - - """; - - await p.StandardInput.WriteAsync(command); - p.StandardInput.Close(); - logger.AddToStdIn(command); - - string? line; - while ((line = await p.StandardOutput.ReadLineAsync()) != null) - { - logger.AddToStdOut(line); - if (!line.StartsWith("#")) - { - continue; // The PowerShell script appends a '#' to the beginning of each line to identify the output - } - - string[] elements = line.Split('\t'); - if (elements.Length < 5) - { - continue; - } - - ManagerSource source = GetSourceOrDefault(elements[4]); - - Packages.Add(new Package(elements[0][1..], elements[1], elements[2], elements[3], source, this)); - } - - logger.AddToStdErr(await p.StandardError.ReadToEndAsync()); - await p.WaitForExitAsync(); - logger.Close(p.ExitCode); - - if (Packages.Count() > 0) - { - return Packages.ToArray(); - } - else - { - Logger.Warn("WinGet updates returned zero packages, attempting legacy..."); - return await BundledWinGetLegacyMethods.GetAvailableUpdates_UnSafe(this); - } + return await WinGetHelper.Instance.GetAvailableUpdates_UnSafe(this); } - + protected override async Task GetInstalledPackages_UnSafe() { - if (Settings.Get("ForceLegacyBundledWinGet")) - return await BundledWinGetLegacyMethods.GetInstalledPackages_UnSafe(this); - - List Packages = []; - Process p = new() - { - StartInfo = new ProcessStartInfo() - { - FileName = "cmd.exe", - Arguments = "/C " + PowerShellPath + " " + PowerShellPromptArgs, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - CreateNoWindow = true, - StandardOutputEncoding = CodePagesEncodingProvider.Instance.GetEncoding(CoreData.CODE_PAGE), - StandardInputEncoding = new UTF8Encoding(false), - StandardErrorEncoding = CodePagesEncodingProvider.Instance.GetEncoding(CoreData.CODE_PAGE), - } - }; - - ManagerClasses.Classes.ProcessTaskLogger logger = TaskLogger.CreateNew(LoggableTaskType.ListInstalledPackages, p); - p.Start(); - - string command = """ - Write-Output (Get-Module -Name Microsoft.WinGet.Client).Version - Import-Module Microsoft.WinGet.Client - function Print-WinGetPackage { - param ( - [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $Name, - [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $Id, - [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $InstalledVersion, - [Parameter(ValueFromPipelineByPropertyName)] [string[]] $AvailableVersions, - [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [bool] $IsUpdateAvailable, - [Parameter(ValueFromPipelineByPropertyName)] [string] $Source - ) - process { - Write-Output("#" + $Name + "`t" + $Id + "`t" + $InstalledVersion + "`t" + $Source) - } - } - - Get-WinGetPackage | Print-WinGetPackage - - - exit - - """; - - await p.StandardInput.WriteAsync(command); - p.StandardInput.Close(); - logger.AddToStdIn(command); - - - string? line; - while ((line = await p.StandardOutput.ReadLineAsync()) != null) - { - logger.AddToStdOut(line); - if (!line.StartsWith("#")) - { - continue; // The PowerShell script appends a '#' to the beginning of each line to identify the output - } - - string[] elements = line.Split('\t'); - if (elements.Length < 4) - { - continue; - } - - ManagerSource source; - if (elements[3] != "") - { - source = GetSourceOrDefault(elements[3]); - } - else - { - source = GetLocalSource(elements[1]); - } - - Packages.Add(new Package(elements[0][1..], elements[1], elements[2], source, this)); - } - - logger.AddToStdErr(await p.StandardError.ReadToEndAsync()); - await p.WaitForExitAsync(); - logger.Close(p.ExitCode); - - if (Packages.Count() > 0) - { - return Packages.ToArray(); - } - else - { - Logger.Warn("WinGet installed packages returned zero packages, attempting legacy..."); - return await BundledWinGetLegacyMethods.GetInstalledPackages_UnSafe(this); - } + return await WinGetHelper.Instance.GetInstalledPackages_UnSafe(this); } - - private ManagerSource GetLocalSource(string id) + + public ManagerSource GetLocalSource(string id) { try { @@ -361,6 +180,7 @@ private ManagerSource GetLocalSource(string id) return LocalPcSource; } } + public override string[] GetInstallParameters(Package package, InstallationOptions options) { diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers.cs deleted file mode 100644 index fe6cad8bd..000000000 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers.cs +++ /dev/null @@ -1,846 +0,0 @@ -using Microsoft.Management.Deployment; -using System.Diagnostics; -using System.Text; -using System.Text.RegularExpressions; -using UniGetUI.Core.Data; -using UniGetUI.Core.Logging; -using UniGetUI.Core.SettingsEngine; -using UniGetUI.Core.Tools; -using UniGetUI.PackageEngine.Classes.Manager.ManagerHelpers; -using UniGetUI.PackageEngine.Enums; -using UniGetUI.PackageEngine.PackageClasses; -using WindowsPackageManager.Interop; - -namespace UniGetUI.PackageEngine.Managers.WingetManager -{ - internal static class WinGetHelper - { - private static IWinGetPackageHelper? __helper; - public static IWinGetPackageHelper Instance - { - get - { - if (__helper == null) - { - __helper = new BundledWinGetHelper(); - } - return __helper; - } - - set - { - __helper = value; - } - } - } - - internal interface IWinGetPackageHelper - { - - public Task FindPackages_UnSafe(WinGet ManagerInstance, string query); - public Task GetSources_UnSafe(WinGet ManagerInstance); - public Task GetPackageVersions_Unsafe(WinGet ManagerInstance, Package package); - public Task GetPackageDetails_UnSafe(WinGet ManagerInstance, PackageDetails details); - - } - - internal class NativeWinGetHelper : IWinGetPackageHelper - { - public WindowsPackageManagerStandardFactory Factory; - public PackageManager WinGetManager; - - public NativeWinGetHelper() - { - if (CoreTools.IsAdministrator()) - { - Logger.Info("Running elevated, WinGet class registration is likely to fail"); - } - - Factory = new WindowsPackageManagerStandardFactory(); - WinGetManager = Factory.CreatePackageManager(); - } - - - public async Task FindPackages_UnSafe(WinGet ManagerInstance, string query) - { - List Packages = []; - ManagerClasses.Classes.NativeTaskLogger logger = ManagerInstance.TaskLogger.CreateNew(LoggableTaskType.FindPackages); - foreach (string query_part in query.Replace(".", " ").Split(" ")) - { - FindPackagesOptions PackageFilters = Factory.CreateFindPackagesOptions(); - - logger.Log("Generating filters..."); - // Name filter - PackageMatchFilter FilterName = Factory.CreatePackageMatchFilter(); - FilterName.Field = PackageMatchField.Name; - FilterName.Value = query_part; - FilterName.Option = PackageFieldMatchOption.ContainsCaseInsensitive; - PackageFilters.Filters.Add(FilterName); - - // Id filter - PackageMatchFilter FilterId = Factory.CreatePackageMatchFilter(); - FilterId.Field = PackageMatchField.Id; - FilterId.Value = query_part; - FilterId.Option = PackageFieldMatchOption.ContainsCaseInsensitive; - PackageFilters.Filters.Add(FilterId); - - // Load catalogs - logger.Log("Loading available catalogs..."); - IReadOnlyList AvailableCatalogs = WinGetManager.GetPackageCatalogs(); - Dictionary> FindPackageTasks = []; - - // Spawn Tasks to find packages on catalogs - logger.Log("Spawning catalog fetching tasks..."); - foreach (PackageCatalogReference CatalogReference in AvailableCatalogs.ToArray()) - { - logger.Log($"Begin search on catalog {CatalogReference.Info.Name}"); - // Connect to catalog - CatalogReference.AcceptSourceAgreements = true; - ConnectResult result = await CatalogReference.ConnectAsync(); - if (result.Status == ConnectResultStatus.Ok) - { - try - { - // Create task and spawn it - Task task = new(() => result.PackageCatalog.FindPackages(PackageFilters)); - task.Start(); - - // Add task to list - FindPackageTasks.Add( - CatalogReference, - task - ); - } - catch (Exception e) - { - logger.Error("WinGet: Catalog " + CatalogReference.Info.Name + " failed to spawn FindPackages task."); - logger.Error(e); - } - } - else - { - logger.Error("WinGet: Catalog " + CatalogReference.Info.Name + " failed to connect."); - } - } - - // Wait for tasks completion - await Task.WhenAll(FindPackageTasks.Values.ToArray()); - logger.Log($"All catalogs fetched. Fetching results for query piece {query_part}"); - - foreach (KeyValuePair> CatalogTaskPair in FindPackageTasks) - { - try - { - // Get the source for the catalog - ManagerSource source = ManagerInstance.GetSourceOrDefault(CatalogTaskPair.Key.Info.Name); - - FindPackagesResult FoundPackages = CatalogTaskPair.Value.Result; - foreach (MatchResult package in FoundPackages.Matches.ToArray()) - { - CatalogPackage catPkg = package.CatalogPackage; - // Create the Package item and add it to the list - logger.Log($"Found package: {catPkg.Name}|{catPkg.Id}|{catPkg.DefaultInstallVersion.Version} on catalog {source.Name}"); - Packages.Add(new Package( - catPkg.Name, - catPkg.Id, - catPkg.DefaultInstallVersion.Version, - source, - ManagerInstance - )); - } - } - catch (Exception e) - { - logger.Error("WinGet: Catalog " + CatalogTaskPair.Key.Info.Name + " failed to get available packages."); - logger.Error(e); - } - } - } - logger.Close(0); - return Packages.ToArray(); - } - - public async Task GetSources_UnSafe(WinGet ManagerInstance) - { - List sources = []; - ManagerClasses.Classes.NativeTaskLogger logger = ManagerInstance.TaskLogger.CreateNew(LoggableTaskType.ListSources); - - foreach (PackageCatalogReference catalog in await Task.Run(() => WinGetManager.GetPackageCatalogs().ToArray())) - { - try - { - logger.Log($"Found source {catalog.Info.Name} with argument {catalog.Info.Argument}"); - sources.Add(new ManagerSource(ManagerInstance, catalog.Info.Name, new Uri(catalog.Info.Argument), updateDate: catalog.Info.LastUpdateTime.ToString())); - } - catch (Exception e) - { - logger.Error(e); - } - } - - logger.Close(0); - return sources.ToArray(); - } - - public async Task GetPackageVersions_Unsafe(WinGet ManagerInstance, Package package) - { - ManagerClasses.Classes.NativeTaskLogger logger = ManagerInstance.TaskLogger.CreateNew(LoggableTaskType.LoadPackageVersions); - - // Find the native package for the given Package object - PackageCatalogReference Catalog = WinGetManager.GetPackageCatalogByName(package.Source.Name); - if (Catalog == null) - { - logger.Error("Failed to get catalog " + package.Source.Name + ". Is the package local?"); - logger.Close(1); - return []; - } - - // Connect to catalog - Catalog.AcceptSourceAgreements = true; - ConnectResult ConnectResult = await Task.Run(() => Catalog.Connect()); - if (ConnectResult.Status != ConnectResultStatus.Ok) - { - logger.Error("Failed to connect to catalog " + package.Source.Name); - logger.Close(1); - return []; - } - - // Match only the exact same Id - FindPackagesOptions packageMatchFilter = Factory.CreateFindPackagesOptions(); - PackageMatchFilter filters = Factory.CreatePackageMatchFilter(); - filters.Field = PackageMatchField.Id; - filters.Value = package.Id; - filters.Option = PackageFieldMatchOption.Equals; - packageMatchFilter.Filters.Add(filters); - packageMatchFilter.ResultLimit = 1; - Task SearchResult = Task.Run(() => ConnectResult.PackageCatalog.FindPackages(packageMatchFilter)); - - if (SearchResult.Result == null || SearchResult.Result.Matches == null || SearchResult.Result.Matches.Count() == 0) - { - logger.Error("Failed to find package " + package.Id + " in catalog " + package.Source.Name); - logger.Close(1); - return []; - } - - // Get the Native Package - CatalogPackage NativePackage = SearchResult.Result.Matches.First().CatalogPackage; - string[] versions = NativePackage.AvailableVersions.Select(x => x.Version).ToArray(); - foreach (string? version in versions) - { - logger.Log(version); - } - - logger.Close(0); - return versions ?? []; - } - - public async Task GetPackageDetails_UnSafe(WinGet ManagerInstance, PackageDetails details) - { - ManagerClasses.Classes.NativeTaskLogger logger = ManagerInstance.TaskLogger.CreateNew(LoggableTaskType.LoadPackageDetails); - - if (details.Package.Source.Name == "winget") - { - details.ManifestUrl = new Uri("https://github.com/microsoft/winget-pkgs/tree/master/manifests/" - + details.Package.Id[0].ToString().ToLower() + "/" - + details.Package.Id.Split('.')[0] + "/" - + String.Join("/", details.Package.Id.Contains('.') ? details.Package.Id.Split('.')[1..] : details.Package.Id.Split('.')) - ); - } - else if (details.Package.Source.Name == "msstore") - { - details.ManifestUrl = new Uri("https://apps.microsoft.com/detail/" + details.Package.Id); - } - - // Find the native package for the given Package object - PackageCatalogReference Catalog = WinGetManager.GetPackageCatalogByName(details.Package.Source.Name); - if (Catalog == null) - { - logger.Error("Failed to get catalog " + details.Package.Source.Name + ". Is the package local?"); - logger.Close(1); - return; - } - - // Connect to catalog - Catalog.AcceptSourceAgreements = true; - ConnectResult ConnectResult = await Task.Run(() => Catalog.Connect()); - if (ConnectResult.Status != ConnectResultStatus.Ok) - { - logger.Error("Failed to connect to catalog " + details.Package.Source.Name); - logger.Close(1); - return; - } - - // Match only the exact same Id - FindPackagesOptions packageMatchFilter = Factory.CreateFindPackagesOptions(); - PackageMatchFilter filters = Factory.CreatePackageMatchFilter(); - filters.Field = PackageMatchField.Id; - filters.Value = details.Package.Id; - filters.Option = PackageFieldMatchOption.Equals; - packageMatchFilter.Filters.Add(filters); - packageMatchFilter.ResultLimit = 1; - Task SearchResult = Task.Run(() => ConnectResult.PackageCatalog.FindPackages(packageMatchFilter)); - - if (SearchResult.Result == null || SearchResult.Result.Matches == null || SearchResult.Result.Matches.Count() == 0) - { - logger.Error("WinGet: Failed to find package " + details.Package.Id + " in catalog " + details.Package.Source.Name); - logger.Close(1); - return; - } - - // Get the Native Package - CatalogPackage NativePackage = SearchResult.Result.Matches.First().CatalogPackage; - - // Extract data from NativeDetails - CatalogPackageMetadata NativeDetails = NativePackage.DefaultInstallVersion.GetCatalogPackageMetadata(Windows.System.UserProfile.GlobalizationPreferences.Languages[0]); - - if (NativeDetails.Author != "") - { - details.Author = NativeDetails.Author; - } - - if (NativeDetails.Description != "") - { - details.Description = NativeDetails.Description; - } - - if (NativeDetails.PackageUrl != "") - { - details.HomepageUrl = new Uri(NativeDetails.PackageUrl); - } - - if (NativeDetails.License != "") - { - details.License = NativeDetails.License; - } - - if (NativeDetails.LicenseUrl != "") - { - details.LicenseUrl = new Uri(NativeDetails.LicenseUrl); - } - - if (NativeDetails.Publisher != "") - { - details.Publisher = NativeDetails.Publisher; - } - - if (NativeDetails.ReleaseNotes != "") - { - details.ReleaseNotes = NativeDetails.ReleaseNotes; - } - - if (NativeDetails.ReleaseNotesUrl != "") - { - details.ReleaseNotesUrl = new Uri(NativeDetails.ReleaseNotesUrl); - } - - if (NativeDetails.Tags != null) - { - details.Tags = NativeDetails.Tags.ToArray(); - } - - - // There is no way yet to retrieve installer URLs right now so this part will be console-parsed. - // TODO: Replace this code with native code when available on the COM api - Process process = new(); - List output = []; - ProcessStartInfo startInfo = new() - { - FileName = ManagerInstance.WinGetBundledPath, - Arguments = ManagerInstance.Properties.ExecutableCallArgs + " show --id " + details.Package.Id + " --exact --disable-interactivity --accept-source-agreements --source " + details.Package.Source.Name, - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - UseShellExecute = false, - CreateNoWindow = true, - StandardOutputEncoding = System.Text.Encoding.UTF8 - }; - process.StartInfo = startInfo; - process.Start(); - - logger.Log("Begin loading installers:"); - logger.Log(" Executable: " + startInfo.FileName); - logger.Log(" Arguments: " + startInfo.Arguments); - - // Retrieve the output - string? _line; - while ((_line = await process.StandardOutput.ReadLineAsync()) != null) - { - if (_line.Trim() != "") - { - logger.Log(_line); - output.Add(_line); - } - } - - logger.Error(await process.StandardError.ReadToEndAsync()); - - // Parse the output - foreach (string __line in output) - { - try - { - string line = __line.Trim(); - if (line.Contains("Installer SHA256:")) - { - details.InstallerHash = line.Split(":")[1].Trim(); - } - else if (line.Contains("Installer Url:")) - { - details.InstallerUrl = new Uri(line.Replace("Installer Url:", "").Trim()); - details.InstallerSize = await CoreTools.GetFileSizeAsync(details.InstallerUrl); - } - else if (line.Contains("Release Date:")) - { - details.UpdateDate = line.Split(":")[1].Trim(); - } - else if (line.Contains("Installer Type:")) - { - details.InstallerType = line.Split(":")[1].Trim(); - } - } - catch (Exception e) - { - Logger.Warn("Error occurred while parsing line value=\"" + __line + "\""); - Logger.Warn(e.Message); - } - } - logger.Close(0); - return; - } - } - - - internal class BundledWinGetHelper : IWinGetPackageHelper - { - - public BundledWinGetHelper() - { - - } - - public async Task FindPackages_UnSafe(WinGet ManagerInstance, string query) - { - if (Settings.Get("ForceLegacyBundledWinGet")) - return await BundledWinGetLegacyMethods.FindPackages_UnSafe(ManagerInstance, query); - - List Packages = []; - - Process p = new() - { - StartInfo = new ProcessStartInfo() - { - FileName = "cmd.exe", - Arguments = "/C " + ManagerInstance.PowerShellPath + " " + ManagerInstance.PowerShellPromptArgs, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - CreateNoWindow = true, - StandardOutputEncoding = CodePagesEncodingProvider.Instance.GetEncoding(CoreData.CODE_PAGE), - StandardInputEncoding = new UTF8Encoding(false), - StandardErrorEncoding = CodePagesEncodingProvider.Instance.GetEncoding(CoreData.CODE_PAGE), - } - }; - - p.Start(); - - ManagerClasses.Classes.ProcessTaskLogger logger = ManagerInstance.TaskLogger.CreateNew(LoggableTaskType.FindPackages, p); - - string command = """ - Write-Output (Get-Module -Name Microsoft.WinGet.Client).Version - Import-Module Microsoft.WinGet.Client - function Print-WinGetPackage { - param ( - [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $Name, - [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $Id, - [Parameter(ValueFromPipelineByPropertyName)] [string[]] $AvailableVersions, - [Parameter(ValueFromPipelineByPropertyName)] [string] $Source - ) - process { - Write-Output(""#"" + $Name + ""`t"" + $Id + ""`t"" + $AvailableVersions[0] + ""`t"" + $Source) - } - } - - Find-WinGetPackage -Query {query} | Print-WinGetPackage - - exit - - - """; - - await p.StandardInput.WriteAsync(command); - p.StandardInput.Close(); - logger.AddToStdIn(command); - - string? line; - while ((line = await p.StandardOutput.ReadLineAsync()) != null) - { - logger.AddToStdOut(line); - if (!line.StartsWith("#")) - { - continue; // The PowerShell script appends a '#' to the beginning of each line to identify the output - } - - string[] elements = line.Split('\t'); - if (elements.Length < 4) - { - continue; - } - - ManagerSource source = ManagerInstance.GetSourceOrDefault(elements[3]); - - Packages.Add(new Package(elements[0][1..], elements[1], elements[2], source, ManagerInstance)); - } - - logger.AddToStdErr(await p.StandardError.ReadToEndAsync()); - await p.WaitForExitAsync(); - logger.Close(p.ExitCode); - - if (Packages.Count() > 0) - { - return Packages.ToArray(); - } - else - { - Logger.Warn("WinGet package fetching returned zero packages, attempting legacy..."); - return await BundledWinGetLegacyMethods.FindPackages_UnSafe(ManagerInstance, query); - } - - } - - public async Task GetPackageDetails_UnSafe(WinGet ManagerInstance, PackageDetails details) - { - if (details.Package.Source.Name == "winget") - { - details.ManifestUrl = new Uri("https://github.com/microsoft/winget-pkgs/tree/master/manifests/" - + details.Package.Id[0].ToString().ToLower() + "/" - + details.Package.Id.Split('.')[0] + "/" - + String.Join("/", details.Package.Id.Contains('.') ? details.Package.Id.Split('.')[1..] : details.Package.Id.Split('.')) - ); - } - else if (details.Package.Source.Name == "msstore") - { - details.ManifestUrl = new Uri("https://apps.microsoft.com/detail/" + details.Package.Id); - } - - // Get the output for the best matching locale - Process process = new(); - string packageIdentifier = "--id " + details.Package.Id + " --exact"; - - List output = []; - bool LocaleFound = true; - ProcessStartInfo startInfo = new() - { - FileName = ManagerInstance.WinGetBundledPath, - Arguments = ManagerInstance.Properties.ExecutableCallArgs + " show " + packageIdentifier + " --disable-interactivity --accept-source-agreements --locale " + System.Globalization.CultureInfo.CurrentCulture.ToString(), - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - UseShellExecute = false, - CreateNoWindow = true, - StandardOutputEncoding = System.Text.Encoding.UTF8 - }; - process.StartInfo = startInfo; - process.Start(); - - string? _line; - while ((_line = await process.StandardOutput.ReadLineAsync()) != null) - { - if (_line.Trim() != "") - { - output.Add(_line); - if (_line.Contains("The value provided for the `locale` argument is invalid") || _line.Contains("No applicable installer found; see Logger.Logs for more details.")) - { - LocaleFound = false; - break; - } - } - } - - // Load fallback english locale - if (!LocaleFound) - { - output.Clear(); - Logger.Info("Winget could not found culture data for package Id=" + details.Package.Id + " and Culture=" + System.Globalization.CultureInfo.CurrentCulture.ToString() + ". Trying to get data for en-US"); - process = new Process(); - LocaleFound = true; - startInfo = new() - { - FileName = ManagerInstance.WinGetBundledPath, - Arguments = ManagerInstance.Properties.ExecutableCallArgs + " show " + packageIdentifier + " --disable-interactivity --accept-source-agreements --locale en-US", - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - UseShellExecute = false, - CreateNoWindow = true, - StandardOutputEncoding = System.Text.Encoding.UTF8 - }; - process.StartInfo = startInfo; - process.Start(); - - while ((_line = await process.StandardOutput.ReadLineAsync()) != null) - { - if (_line.Trim() != "") - { - output.Add(_line); - if (_line.Contains("The value provided for the `locale` argument is invalid") || _line.Contains("No applicable installer found; see Logger.Logs for more details.")) - { - LocaleFound = false; - break; - } - } - } - } - - // Load default locale - if (!LocaleFound) - { - output.Clear(); - Logger.Info("Winget could not found culture data for package Id=" + details.Package.Id + " and Culture=en-US. Loading default"); - LocaleFound = true; - process = new Process(); - startInfo = new() - { - FileName = ManagerInstance.WinGetBundledPath, - Arguments = ManagerInstance.Properties.ExecutableCallArgs + " show " + packageIdentifier + " --disable-interactivity --accept-source-agreements", - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - UseShellExecute = false, - CreateNoWindow = true, - StandardOutputEncoding = System.Text.Encoding.UTF8 - }; - process.StartInfo = startInfo; - process.Start(); - - while ((_line = await process.StandardOutput.ReadLineAsync()) != null) - { - if (_line.Trim() != "") - { - output.Add(_line); - } - } - } - - // Parse the output - bool IsLoadingDescription = false; - bool IsLoadingReleaseNotes = false; - bool IsLoadingTags = false; - foreach (string __line in output) - { - try - { - string line = __line.TrimEnd(); - if (line == "") - { - continue; - } - - // Check if a multiline field is being loaded - if (line.StartsWith(" ") && IsLoadingDescription) - { - details.Description += "\n" + line.Trim(); - } - else if (line.StartsWith(" ") && IsLoadingReleaseNotes) - { - details.ReleaseNotes += "\n" + line.Trim(); - } - else if (line.StartsWith(" ") && IsLoadingTags) - { - details.Tags = details.Tags.Append(line.Trim()).ToArray(); - } - - // Stop loading multiline fields - else if (IsLoadingDescription) - { - IsLoadingDescription = false; - } - else if (IsLoadingReleaseNotes) - { - IsLoadingReleaseNotes = false; - } - else if (IsLoadingTags) - { - IsLoadingTags = false; - } - - // Check for singleline fields - if (line.Contains("Publisher:")) - { - details.Publisher = line.Split(":")[1].Trim(); - } - else if (line.Contains("Author:")) - { - details.Author = line.Split(":")[1].Trim(); - } - else if (line.Contains("Homepage:")) - { - details.HomepageUrl = new Uri(line.Replace("Homepage:", "").Trim()); - } - else if (line.Contains("License:")) - { - details.License = line.Split(":")[1].Trim(); - } - else if (line.Contains("License Url:")) - { - details.LicenseUrl = new Uri(line.Replace("License Url:", "").Trim()); - } - else if (line.Contains("Installer SHA256:")) - { - details.InstallerHash = line.Split(":")[1].Trim(); - } - else if (line.Contains("Installer Url:")) - { - details.InstallerUrl = new Uri(line.Replace("Installer Url:", "").Trim()); - details.InstallerSize = await CoreTools.GetFileSizeAsync(details.InstallerUrl); - } - else if (line.Contains("Release Date:")) - { - details.UpdateDate = line.Split(":")[1].Trim(); - } - else if (line.Contains("Release Notes Url:")) - { - details.ReleaseNotesUrl = new Uri(line.Replace("Release Notes Url:", "").Trim()); - } - else if (line.Contains("Installer Type:")) - { - details.InstallerType = line.Split(":")[1].Trim(); - } - else if (line.Contains("Description:")) - { - details.Description = line.Split(":")[1].Trim(); - IsLoadingDescription = true; - } - else if (line.Contains("ReleaseNotes")) - { - details.ReleaseNotes = line.Split(":")[1].Trim(); - IsLoadingReleaseNotes = true; - } - else if (line.Contains("Tags")) - { - details.Tags = new string[0]; - IsLoadingTags = true; - } - } - catch (Exception e) - { - Logger.Warn("Error occurred while parsing line value=\"" + _line + "\""); - Logger.Warn(e.Message); - } - } - - return; - } - - public async Task GetPackageVersions_Unsafe(WinGet ManagerInstance, Package package) - { - Process p = new() - { - StartInfo = new ProcessStartInfo() - { - FileName = ManagerInstance.WinGetBundledPath, - Arguments = ManagerInstance.Properties.ExecutableCallArgs + " show --id " + package.Id + " --exact --versions --accept-source-agreements", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - CreateNoWindow = true, - StandardOutputEncoding = System.Text.Encoding.UTF8 - } - }; - - ManagerClasses.Classes.ProcessTaskLogger logger = ManagerInstance.TaskLogger.CreateNew(LoggableTaskType.LoadPackageVersions, p); - - p.Start(); - - string? line; - List versions = []; - bool DashesPassed = false; - while ((line = await p.StandardOutput.ReadLineAsync()) != null) - { - logger.AddToStdOut(line); - if (!DashesPassed) - { - if (line.Contains("---")) - { - DashesPassed = true; - } - } - else - { - versions.Add(line.Trim()); - } - } - logger.AddToStdErr(await p.StandardError.ReadToEndAsync()); - await p.WaitForExitAsync(); - logger.Close(p.ExitCode); - return versions.ToArray(); - } - - public async Task GetSources_UnSafe(WinGet ManagerInstance) - { - List sources = []; - - Process p = new() - { - StartInfo = new() - { - FileName = ManagerInstance.Status.ExecutablePath, - Arguments = ManagerInstance.Properties.ExecutableCallArgs + " source list", - RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - UseShellExecute = false, - CreateNoWindow = true, - StandardOutputEncoding = System.Text.Encoding.UTF8 - } - }; - - p.Start(); - - ManagerClasses.Classes.ProcessTaskLogger logger = ManagerInstance.TaskLogger.CreateNew(LoggableTaskType.FindPackages, p); - - bool dashesPassed = false; - string? line; - while ((line = await p.StandardOutput.ReadLineAsync()) != null) - { - logger.AddToStdOut(line); - try - { - if (string.IsNullOrEmpty(line)) - { - continue; - } - - if (!dashesPassed) - { - if (line.Contains("---")) - { - dashesPassed = true; - } - } - else - { - string[] parts = Regex.Replace(line.Trim(), " {2,}", " ").Split(' '); - if (parts.Length > 1) - { - sources.Add(new ManagerSource(ManagerInstance, parts[0].Trim(), new Uri(parts[1].Trim()))); - } - } - } - catch (Exception e) - { - Logger.Warn(e); - } - } - - logger.AddToStdErr(await p.StandardError.ReadToEndAsync()); - await p.WaitForExitAsync(); - logger.Close(p.ExitCode); - return sources.ToArray(); - - } - } -} - - diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/BundledWinGetHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/BundledWinGetHelper.cs new file mode 100644 index 000000000..8ef3624e6 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/BundledWinGetHelper.cs @@ -0,0 +1,651 @@ +using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; +using UniGetUI.Core.Data; +using UniGetUI.Core.Logging; +using UniGetUI.Core.SettingsEngine; +using UniGetUI.Core.Tools; +using UniGetUI.PackageEngine.Classes.Manager.ManagerHelpers; +using UniGetUI.PackageEngine.Enums; +using UniGetUI.PackageEngine.PackageClasses; + +namespace UniGetUI.PackageEngine.Managers.WingetManager; + +internal class BundledWinGetHelper : IWinGetManagerHelper +{ + public BundledWinGetHelper() + { + } + + public async Task GetAvailableUpdates_UnSafe(WinGet Manager) + { + if (Settings.Get("ForceLegacyBundledWinGet")) + return await BundledWinGetLegacyMethods.GetAvailableUpdates_UnSafe(Manager); + + List Packages = []; + + Process p = new() + { + StartInfo = new ProcessStartInfo() + { + FileName = "cmd.exe", + Arguments = "/C " + Manager.PowerShellPath + " " + Manager.PowerShellPromptArgs, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + CreateNoWindow = true, + StandardOutputEncoding = CodePagesEncodingProvider.Instance.GetEncoding(CoreData.CODE_PAGE), + StandardInputEncoding = new UTF8Encoding(false), + StandardErrorEncoding = CodePagesEncodingProvider.Instance.GetEncoding(CoreData.CODE_PAGE), + } + }; + + ManagerClasses.Classes.ProcessTaskLogger logger = Manager.TaskLogger.CreateNew(LoggableTaskType.ListUpdates, p); + + p.Start(); + + string command = """ + Write-Output (Get-Module -Name Microsoft.WinGet.Client).Version + Import-Module Microsoft.WinGet.Client + function Print-WinGetPackage { + param ( + [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $Name, + [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $Id, + [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $InstalledVersion, + [Parameter(ValueFromPipelineByPropertyName)] [string[]] $AvailableVersions, + [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [bool] $IsUpdateAvailable, + [Parameter(ValueFromPipelineByPropertyName)] [string] $Source + ) + process { + if($IsUpdateAvailable) + { + Write-Output("#" + $Name + "`t" + $Id + "`t" + $InstalledVersion + "`t" + $AvailableVersions[0] + "`t" + $Source) + } + } + } + + Get-WinGetPackage | Print-WinGetPackage + + exit + + """; + + await p.StandardInput.WriteAsync(command); + p.StandardInput.Close(); + logger.AddToStdIn(command); + + string? line; + while ((line = await p.StandardOutput.ReadLineAsync()) != null) + { + logger.AddToStdOut(line); + if (!line.StartsWith("#")) + { + continue; // The PowerShell script appends a '#' to the beginning of each line to identify the output + } + + string[] elements = line.Split('\t'); + if (elements.Length < 5) + { + continue; + } + + ManagerSource source = Manager.GetSourceOrDefault(elements[4]); + + Packages.Add(new Package(elements[0][1..], elements[1], elements[2], elements[3], source, Manager)); + } + + logger.AddToStdErr(await p.StandardError.ReadToEndAsync()); + await p.WaitForExitAsync(); + logger.Close(p.ExitCode); + + if (Packages.Count() > 0) + { + return Packages.ToArray(); + } + else + { + Logger.Warn("WinGet updates returned zero packages, attempting legacy..."); + return await BundledWinGetLegacyMethods.GetAvailableUpdates_UnSafe(Manager); + } + } + + public async Task GetInstalledPackages_UnSafe(WinGet Manager) + { + if (Settings.Get("ForceLegacyBundledWinGet")) + return await BundledWinGetLegacyMethods.GetInstalledPackages_UnSafe(Manager); + + List Packages = []; + Process p = new() + { + StartInfo = new ProcessStartInfo() + { + FileName = "cmd.exe", + Arguments = "/C " + Manager.PowerShellPath + " " + Manager.PowerShellPromptArgs, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + CreateNoWindow = true, + StandardOutputEncoding = CodePagesEncodingProvider.Instance.GetEncoding(CoreData.CODE_PAGE), + StandardInputEncoding = new UTF8Encoding(false), + StandardErrorEncoding = CodePagesEncodingProvider.Instance.GetEncoding(CoreData.CODE_PAGE), + } + }; + + ManagerClasses.Classes.ProcessTaskLogger logger = + Manager.TaskLogger.CreateNew(LoggableTaskType.ListInstalledPackages, p); + p.Start(); + + string command = """ + Write-Output (Get-Module -Name Microsoft.WinGet.Client).Version + Import-Module Microsoft.WinGet.Client + function Print-WinGetPackage { + param ( + [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $Name, + [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $Id, + [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $InstalledVersion, + [Parameter(ValueFromPipelineByPropertyName)] [string[]] $AvailableVersions, + [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [bool] $IsUpdateAvailable, + [Parameter(ValueFromPipelineByPropertyName)] [string] $Source + ) + process { + Write-Output("#" + $Name + "`t" + $Id + "`t" + $InstalledVersion + "`t" + $Source) + } + } + + Get-WinGetPackage | Print-WinGetPackage + + + exit + + """; + + await p.StandardInput.WriteAsync(command); + p.StandardInput.Close(); + logger.AddToStdIn(command); + + + string? line; + while ((line = await p.StandardOutput.ReadLineAsync()) != null) + { + logger.AddToStdOut(line); + if (!line.StartsWith("#")) + { + continue; // The PowerShell script appends a '#' to the beginning of each line to identify the output + } + + string[] elements = line.Split('\t'); + if (elements.Length < 4) + { + continue; + } + + ManagerSource source; + if (elements[3] != "") + { + source = Manager.GetSourceOrDefault(elements[3]); + } + else + { + source = Manager.GetLocalSource(elements[1]); + } + + Packages.Add(new Package(elements[0][1..], elements[1], elements[2], source, Manager)); + } + + logger.AddToStdErr(await p.StandardError.ReadToEndAsync()); + await p.WaitForExitAsync(); + logger.Close(p.ExitCode); + + if (Packages.Count() > 0) + { + return Packages.ToArray(); + } + else + { + Logger.Warn("WinGet installed packages returned zero packages, attempting legacy..."); + return await BundledWinGetLegacyMethods.GetInstalledPackages_UnSafe(Manager); + } + } + + + public async Task FindPackages_UnSafe(WinGet Manager, string query) + { + if (Settings.Get("ForceLegacyBundledWinGet")) + return await BundledWinGetLegacyMethods.FindPackages_UnSafe(Manager, query); + + List Packages = []; + + Process p = new() + { + StartInfo = new ProcessStartInfo() + { + FileName = "cmd.exe", + Arguments = "/C " + Manager.PowerShellPath + " " + Manager.PowerShellPromptArgs, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + CreateNoWindow = true, + StandardOutputEncoding = CodePagesEncodingProvider.Instance.GetEncoding(CoreData.CODE_PAGE), + StandardInputEncoding = new UTF8Encoding(false), + StandardErrorEncoding = CodePagesEncodingProvider.Instance.GetEncoding(CoreData.CODE_PAGE), + } + }; + + p.Start(); + + ManagerClasses.Classes.ProcessTaskLogger + logger = Manager.TaskLogger.CreateNew(LoggableTaskType.FindPackages, p); + + string command = """ + Write-Output (Get-Module -Name Microsoft.WinGet.Client).Version + Import-Module Microsoft.WinGet.Client + function Print-WinGetPackage { + param ( + [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $Name, + [Parameter(Mandatory,ValueFromPipelineByPropertyName)] [string] $Id, + [Parameter(ValueFromPipelineByPropertyName)] [string[]] $AvailableVersions, + [Parameter(ValueFromPipelineByPropertyName)] [string] $Source + ) + process { + Write-Output(""#"" + $Name + ""`t"" + $Id + ""`t"" + $AvailableVersions[0] + ""`t"" + $Source) + } + } + + Find-WinGetPackage -Query {query} | Print-WinGetPackage + + exit + + + """; + + await p.StandardInput.WriteAsync(command); + p.StandardInput.Close(); + logger.AddToStdIn(command); + + string? line; + while ((line = await p.StandardOutput.ReadLineAsync()) != null) + { + logger.AddToStdOut(line); + if (!line.StartsWith("#")) + { + continue; // The PowerShell script appends a '#' to the beginning of each line to identify the output + } + + string[] elements = line.Split('\t'); + if (elements.Length < 4) + { + continue; + } + + ManagerSource source = Manager.GetSourceOrDefault(elements[3]); + + Packages.Add(new Package(elements[0][1..], elements[1], elements[2], source, Manager)); + } + + logger.AddToStdErr(await p.StandardError.ReadToEndAsync()); + await p.WaitForExitAsync(); + logger.Close(p.ExitCode); + + if (Packages.Count() > 0) + { + return Packages.ToArray(); + } + else + { + Logger.Warn("WinGet package fetching returned zero packages, attempting legacy..."); + return await BundledWinGetLegacyMethods.FindPackages_UnSafe(Manager, query); + } + + } + + public async Task GetPackageDetails_UnSafe(WinGet Manager, PackageDetails details) + { + if (details.Package.Source.Name == "winget") + { + details.ManifestUrl = new Uri("https://github.com/microsoft/winget-pkgs/tree/master/manifests/" + + details.Package.Id[0].ToString().ToLower() + "/" + + details.Package.Id.Split('.')[0] + "/" + + String.Join("/", + details.Package.Id.Contains('.') + ? details.Package.Id.Split('.')[1..] + : details.Package.Id.Split('.')) + ); + } + else if (details.Package.Source.Name == "msstore") + { + details.ManifestUrl = new Uri("https://apps.microsoft.com/detail/" + details.Package.Id); + } + + // Get the output for the best matching locale + Process process = new(); + string packageIdentifier = "--id " + details.Package.Id + " --exact"; + + List output = []; + bool LocaleFound = true; + ProcessStartInfo startInfo = new() + { + FileName = Manager.WinGetBundledPath, + Arguments = Manager.Properties.ExecutableCallArgs + " show " + packageIdentifier + + " --disable-interactivity --accept-source-agreements --locale " + + System.Globalization.CultureInfo.CurrentCulture.ToString(), + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = true, + StandardOutputEncoding = System.Text.Encoding.UTF8 + }; + process.StartInfo = startInfo; + process.Start(); + + string? _line; + while ((_line = await process.StandardOutput.ReadLineAsync()) != null) + { + if (_line.Trim() != "") + { + output.Add(_line); + if (_line.Contains("The value provided for the `locale` argument is invalid") || + _line.Contains("No applicable installer found; see Logger.Logs for more details.")) + { + LocaleFound = false; + break; + } + } + } + + // Load fallback english locale + if (!LocaleFound) + { + output.Clear(); + Logger.Info("Winget could not found culture data for package Id=" + details.Package.Id + " and Culture=" + + System.Globalization.CultureInfo.CurrentCulture.ToString() + ". Trying to get data for en-US"); + process = new Process(); + LocaleFound = true; + startInfo = new() + { + FileName = Manager.WinGetBundledPath, + Arguments = Manager.Properties.ExecutableCallArgs + " show " + packageIdentifier + + " --disable-interactivity --accept-source-agreements --locale en-US", + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = true, + StandardOutputEncoding = System.Text.Encoding.UTF8 + }; + process.StartInfo = startInfo; + process.Start(); + + while ((_line = await process.StandardOutput.ReadLineAsync()) != null) + { + if (_line.Trim() != "") + { + output.Add(_line); + if (_line.Contains("The value provided for the `locale` argument is invalid") || + _line.Contains("No applicable installer found; see Logger.Logs for more details.")) + { + LocaleFound = false; + break; + } + } + } + } + + // Load default locale + if (!LocaleFound) + { + output.Clear(); + Logger.Info("Winget could not found culture data for package Id=" + details.Package.Id + + " and Culture=en-US. Loading default"); + LocaleFound = true; + process = new Process(); + startInfo = new() + { + FileName = Manager.WinGetBundledPath, + Arguments = Manager.Properties.ExecutableCallArgs + " show " + packageIdentifier + + " --disable-interactivity --accept-source-agreements", + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = true, + StandardOutputEncoding = System.Text.Encoding.UTF8 + }; + process.StartInfo = startInfo; + process.Start(); + + while ((_line = await process.StandardOutput.ReadLineAsync()) != null) + { + if (_line.Trim() != "") + { + output.Add(_line); + } + } + } + + // Parse the output + bool IsLoadingDescription = false; + bool IsLoadingReleaseNotes = false; + bool IsLoadingTags = false; + foreach (string __line in output) + { + try + { + string line = __line.TrimEnd(); + if (line == "") + { + continue; + } + + // Check if a multiline field is being loaded + if (line.StartsWith(" ") && IsLoadingDescription) + { + details.Description += "\n" + line.Trim(); + } + else if (line.StartsWith(" ") && IsLoadingReleaseNotes) + { + details.ReleaseNotes += "\n" + line.Trim(); + } + else if (line.StartsWith(" ") && IsLoadingTags) + { + details.Tags = details.Tags.Append(line.Trim()).ToArray(); + } + + // Stop loading multiline fields + else if (IsLoadingDescription) + { + IsLoadingDescription = false; + } + else if (IsLoadingReleaseNotes) + { + IsLoadingReleaseNotes = false; + } + else if (IsLoadingTags) + { + IsLoadingTags = false; + } + + // Check for singleline fields + if (line.Contains("Publisher:")) + { + details.Publisher = line.Split(":")[1].Trim(); + } + else if (line.Contains("Author:")) + { + details.Author = line.Split(":")[1].Trim(); + } + else if (line.Contains("Homepage:")) + { + details.HomepageUrl = new Uri(line.Replace("Homepage:", "").Trim()); + } + else if (line.Contains("License:")) + { + details.License = line.Split(":")[1].Trim(); + } + else if (line.Contains("License Url:")) + { + details.LicenseUrl = new Uri(line.Replace("License Url:", "").Trim()); + } + else if (line.Contains("Installer SHA256:")) + { + details.InstallerHash = line.Split(":")[1].Trim(); + } + else if (line.Contains("Installer Url:")) + { + details.InstallerUrl = new Uri(line.Replace("Installer Url:", "").Trim()); + details.InstallerSize = await CoreTools.GetFileSizeAsync(details.InstallerUrl); + } + else if (line.Contains("Release Date:")) + { + details.UpdateDate = line.Split(":")[1].Trim(); + } + else if (line.Contains("Release Notes Url:")) + { + details.ReleaseNotesUrl = new Uri(line.Replace("Release Notes Url:", "").Trim()); + } + else if (line.Contains("Installer Type:")) + { + details.InstallerType = line.Split(":")[1].Trim(); + } + else if (line.Contains("Description:")) + { + details.Description = line.Split(":")[1].Trim(); + IsLoadingDescription = true; + } + else if (line.Contains("ReleaseNotes")) + { + details.ReleaseNotes = line.Split(":")[1].Trim(); + IsLoadingReleaseNotes = true; + } + else if (line.Contains("Tags")) + { + details.Tags = new string[0]; + IsLoadingTags = true; + } + } + catch (Exception e) + { + Logger.Warn("Error occurred while parsing line value=\"" + _line + "\""); + Logger.Warn(e.Message); + } + } + + return; + } + + public async Task GetPackageVersions_Unsafe(WinGet Manager, Package package) + { + Process p = new() + { + StartInfo = new ProcessStartInfo() + { + FileName = Manager.WinGetBundledPath, + Arguments = Manager.Properties.ExecutableCallArgs + " show --id " + package.Id + + " --exact --versions --accept-source-agreements", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + CreateNoWindow = true, + StandardOutputEncoding = System.Text.Encoding.UTF8 + } + }; + + ManagerClasses.Classes.ProcessTaskLogger logger = + Manager.TaskLogger.CreateNew(LoggableTaskType.LoadPackageVersions, p); + + p.Start(); + + string? line; + List versions = []; + bool DashesPassed = false; + while ((line = await p.StandardOutput.ReadLineAsync()) != null) + { + logger.AddToStdOut(line); + if (!DashesPassed) + { + if (line.Contains("---")) + { + DashesPassed = true; + } + } + else + { + versions.Add(line.Trim()); + } + } + + logger.AddToStdErr(await p.StandardError.ReadToEndAsync()); + await p.WaitForExitAsync(); + logger.Close(p.ExitCode); + return versions.ToArray(); + } + + public async Task GetSources_UnSafe(WinGet Manager) + { + List sources = []; + + Process p = new() + { + StartInfo = new() + { + FileName = Manager.Status.ExecutablePath, + Arguments = Manager.Properties.ExecutableCallArgs + " source list", + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = true, + StandardOutputEncoding = System.Text.Encoding.UTF8 + } + }; + + p.Start(); + + ManagerClasses.Classes.ProcessTaskLogger + logger = Manager.TaskLogger.CreateNew(LoggableTaskType.FindPackages, p); + + bool dashesPassed = false; + string? line; + while ((line = await p.StandardOutput.ReadLineAsync()) != null) + { + logger.AddToStdOut(line); + try + { + if (string.IsNullOrEmpty(line)) + { + continue; + } + + if (!dashesPassed) + { + if (line.Contains("---")) + { + dashesPassed = true; + } + } + else + { + string[] parts = Regex.Replace(line.Trim(), " {2,}", " ").Split(' '); + if (parts.Length > 1) + { + sources.Add(new ManagerSource(Manager, parts[0].Trim(), new Uri(parts[1].Trim()))); + } + } + } + catch (Exception e) + { + Logger.Warn(e); + } + } + + logger.AddToStdErr(await p.StandardError.ReadToEndAsync()); + await p.WaitForExitAsync(); + logger.Close(p.ExitCode); + return sources.ToArray(); + + } +} \ No newline at end of file diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/IWinGetManagerHelpers.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/IWinGetManagerHelpers.cs new file mode 100644 index 000000000..f70b56521 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/IWinGetManagerHelpers.cs @@ -0,0 +1,48 @@ +using Microsoft.Management.Deployment; +using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; +using UniGetUI.Core.Data; +using UniGetUI.Core.Logging; +using UniGetUI.Core.SettingsEngine; +using UniGetUI.Core.Tools; +using UniGetUI.PackageEngine.Classes.Manager.ManagerHelpers; +using UniGetUI.PackageEngine.Enums; +using UniGetUI.PackageEngine.PackageClasses; +using WindowsPackageManager.Interop; + +namespace UniGetUI.PackageEngine.Managers.WingetManager +{ + internal static class WinGetHelper + { + private static IWinGetManagerHelper? __helper; + public static IWinGetManagerHelper Instance + { + get + { + if (__helper == null) + { + __helper = new BundledWinGetHelper(); + } + return __helper; + } + + set + { + __helper = value; + } + } + } + + internal interface IWinGetManagerHelper + { + public Task GetAvailableUpdates_UnSafe(WinGet Manager); + public Task GetInstalledPackages_UnSafe(WinGet Manager); + public Task FindPackages_UnSafe(WinGet Manager, string query); + public Task GetSources_UnSafe(WinGet Manager); + public Task GetPackageVersions_Unsafe(WinGet Manager, Package package); + public Task GetPackageDetails_UnSafe(WinGet Manager, PackageDetails details); + } +} + + diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs new file mode 100644 index 000000000..13e4fb094 --- /dev/null +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs @@ -0,0 +1,454 @@ +using System.Diagnostics; +using Microsoft.Management.Deployment; +using UniGetUI.Core.Logging; +using UniGetUI.Core.Tools; +using UniGetUI.PackageEngine.Classes.Manager.ManagerHelpers; +using UniGetUI.PackageEngine.Enums; +using UniGetUI.PackageEngine.PackageClasses; +using WindowsPackageManager.Interop; + +namespace UniGetUI.PackageEngine.Managers.WingetManager; + +internal class NativeWinGetHelper : IWinGetManagerHelper +{ + public WindowsPackageManagerStandardFactory Factory; + public PackageManager WinGetManager; + + public NativeWinGetHelper() + { + if (CoreTools.IsAdministrator()) + { + Logger.Info("Running elevated, WinGet class registration is likely to fail"); + } + + Factory = new WindowsPackageManagerStandardFactory(); + WinGetManager = Factory.CreatePackageManager(); + } + + + public async Task FindPackages_UnSafe(WinGet Manager, string query) + { + List Packages = []; + ManagerClasses.Classes.NativeTaskLogger logger = Manager.TaskLogger.CreateNew(LoggableTaskType.FindPackages); + foreach (string query_part in query.Replace(".", " ").Split(" ")) + { + FindPackagesOptions PackageFilters = Factory.CreateFindPackagesOptions(); + + logger.Log("Generating filters..."); + // Name filter + PackageMatchFilter FilterName = Factory.CreatePackageMatchFilter(); + FilterName.Field = PackageMatchField.Name; + FilterName.Value = query_part; + FilterName.Option = PackageFieldMatchOption.ContainsCaseInsensitive; + PackageFilters.Filters.Add(FilterName); + + // Id filter + PackageMatchFilter FilterId = Factory.CreatePackageMatchFilter(); + FilterId.Field = PackageMatchField.Id; + FilterId.Value = query_part; + FilterId.Option = PackageFieldMatchOption.ContainsCaseInsensitive; + PackageFilters.Filters.Add(FilterId); + + // Load catalogs + logger.Log("Loading available catalogs..."); + IReadOnlyList AvailableCatalogs = WinGetManager.GetPackageCatalogs(); + Dictionary> FindPackageTasks = []; + + // Spawn Tasks to find packages on catalogs + logger.Log("Spawning catalog fetching tasks..."); + foreach (PackageCatalogReference CatalogReference in AvailableCatalogs.ToArray()) + { + logger.Log($"Begin search on catalog {CatalogReference.Info.Name}"); + // Connect to catalog + CatalogReference.AcceptSourceAgreements = true; + ConnectResult result = await CatalogReference.ConnectAsync(); + if (result.Status == ConnectResultStatus.Ok) + { + try + { + // Create task and spawn it + Task task = new(() => result.PackageCatalog.FindPackages(PackageFilters)); + task.Start(); + + // Add task to list + FindPackageTasks.Add( + CatalogReference, + task + ); + } + catch (Exception e) + { + logger.Error("WinGet: Catalog " + CatalogReference.Info.Name + + " failed to spawn FindPackages task."); + logger.Error(e); + } + } + else + { + logger.Error("WinGet: Catalog " + CatalogReference.Info.Name + " failed to connect."); + } + } + + // Wait for tasks completion + await Task.WhenAll(FindPackageTasks.Values.ToArray()); + logger.Log($"All catalogs fetched. Fetching results for query piece {query_part}"); + + foreach (KeyValuePair> CatalogTaskPair in + FindPackageTasks) + { + try + { + // Get the source for the catalog + ManagerSource source = Manager.GetSourceOrDefault(CatalogTaskPair.Key.Info.Name); + + FindPackagesResult FoundPackages = CatalogTaskPair.Value.Result; + foreach (MatchResult package in FoundPackages.Matches.ToArray()) + { + CatalogPackage catPkg = package.CatalogPackage; + // Create the Package item and add it to the list + logger.Log( + $"Found package: {catPkg.Name}|{catPkg.Id}|{catPkg.DefaultInstallVersion.Version} on catalog {source.Name}"); + Packages.Add(new Package( + catPkg.Name, + catPkg.Id, + catPkg.DefaultInstallVersion.Version, + source, + Manager + )); + } + } + catch (Exception e) + { + logger.Error("WinGet: Catalog " + CatalogTaskPair.Key.Info.Name + + " failed to get available packages."); + logger.Error(e); + } + } + } + + logger.Close(0); + return Packages.ToArray(); + } + + + public async Task GetAvailableUpdates_UnSafe(WinGet Manager) + { + return []; + } + + public async Task GetInstalledPackages_UnSafe(WinGet Manager) + { + List packages = new(); + foreach (var match in await GetLocalWinGetPackages()) + { + var nativePackage = match.CatalogPackage; + + ManagerSource source; + if (nativePackage.DefaultInstallVersion != null) + { + source = Manager.GetSourceOrDefault(nativePackage.DefaultInstallVersion.PackageCatalog.Info.Name); + } + else + { + source = Manager.GetLocalSource(nativePackage.Id); + } + packages.Add(new Package(nativePackage.Name, nativePackage.Id, nativePackage.InstalledVersion.Version, source, Manager)); + } + + return packages.ToArray(); + } + + private async Task> GetLocalWinGetPackages() + { + PackageCatalogReference installedSearchCatalogRef; + CreateCompositePackageCatalogOptions createCompositePackageCatalogOptions = Factory.CreateCreateCompositePackageCatalogOptions(); + foreach(var catalogRef in WinGetManager.GetPackageCatalogs().ToArray()) + { + Console.WriteLine($"Searching on package catalog {catalogRef.Info.Name} "); + createCompositePackageCatalogOptions.Catalogs.Add(catalogRef); + } + createCompositePackageCatalogOptions.CompositeSearchBehavior = CompositeSearchBehavior.LocalCatalogs; + installedSearchCatalogRef = WinGetManager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions); + + var ConnectResult = await Task.Run(() => installedSearchCatalogRef.Connect()); + if (ConnectResult.Status != ConnectResultStatus.Ok) + { + throw new Exception("WinGet: Failed to connect to local catalog."); + } + + FindPackagesOptions findPackagesOptions = Factory.CreateFindPackagesOptions(); + PackageMatchFilter filter = Factory.CreatePackageMatchFilter(); + filter.Field = PackageMatchField.Id; + filter.Option = PackageFieldMatchOption.StartsWithCaseInsensitive; + filter.Value = ""; + findPackagesOptions.Filters.Add(filter); + + var TaskResult = await Task.Run(() => ConnectResult.PackageCatalog.FindPackages(findPackagesOptions)); + + return TaskResult.Matches.ToArray(); + } + + public async Task GetSources_UnSafe(WinGet Manager) + { + List sources = []; + ManagerClasses.Classes.NativeTaskLogger logger = Manager.TaskLogger.CreateNew(LoggableTaskType.ListSources); + + foreach (PackageCatalogReference catalog in await Task.Run(() => WinGetManager.GetPackageCatalogs().ToArray())) + { + try + { + logger.Log($"Found source {catalog.Info.Name} with argument {catalog.Info.Argument}"); + sources.Add(new ManagerSource(Manager, catalog.Info.Name, new Uri(catalog.Info.Argument), + updateDate: catalog.Info.LastUpdateTime.ToString())); + } + catch (Exception e) + { + logger.Error(e); + } + } + + logger.Close(0); + return sources.ToArray(); + } + + public async Task GetPackageVersions_Unsafe(WinGet Manager, Package package) + { + ManagerClasses.Classes.NativeTaskLogger logger = + Manager.TaskLogger.CreateNew(LoggableTaskType.LoadPackageVersions); + + // Find the native package for the given Package object + PackageCatalogReference Catalog = WinGetManager.GetPackageCatalogByName(package.Source.Name); + if (Catalog == null) + { + logger.Error("Failed to get catalog " + package.Source.Name + ". Is the package local?"); + logger.Close(1); + return []; + } + + // Connect to catalog + Catalog.AcceptSourceAgreements = true; + ConnectResult ConnectResult = await Task.Run(() => Catalog.Connect()); + if (ConnectResult.Status != ConnectResultStatus.Ok) + { + logger.Error("Failed to connect to catalog " + package.Source.Name); + logger.Close(1); + return []; + } + + // Match only the exact same Id + FindPackagesOptions packageMatchFilter = Factory.CreateFindPackagesOptions(); + PackageMatchFilter filters = Factory.CreatePackageMatchFilter(); + filters.Field = PackageMatchField.Id; + filters.Value = package.Id; + filters.Option = PackageFieldMatchOption.Equals; + packageMatchFilter.Filters.Add(filters); + packageMatchFilter.ResultLimit = 1; + Task SearchResult = + Task.Run(() => ConnectResult.PackageCatalog.FindPackages(packageMatchFilter)); + + if (SearchResult.Result == null || SearchResult.Result.Matches == null || + SearchResult.Result.Matches.Count() == 0) + { + logger.Error("Failed to find package " + package.Id + " in catalog " + package.Source.Name); + logger.Close(1); + return []; + } + + // Get the Native Package + CatalogPackage NativePackage = SearchResult.Result.Matches.First().CatalogPackage; + string[] versions = NativePackage.AvailableVersions.Select(x => x.Version).ToArray(); + foreach (string? version in versions) + { + logger.Log(version); + } + + logger.Close(0); + return versions ?? []; + } + + public async Task GetPackageDetails_UnSafe(WinGet Manager, PackageDetails details) + { + ManagerClasses.Classes.NativeTaskLogger logger = + Manager.TaskLogger.CreateNew(LoggableTaskType.LoadPackageDetails); + + if (details.Package.Source.Name == "winget") + { + details.ManifestUrl = new Uri("https://github.com/microsoft/winget-pkgs/tree/master/manifests/" + + details.Package.Id[0].ToString().ToLower() + "/" + + details.Package.Id.Split('.')[0] + "/" + + String.Join("/", + details.Package.Id.Contains('.') + ? details.Package.Id.Split('.')[1..] + : details.Package.Id.Split('.')) + ); + } + else if (details.Package.Source.Name == "msstore") + { + details.ManifestUrl = new Uri("https://apps.microsoft.com/detail/" + details.Package.Id); + } + + // Find the native package for the given Package object + PackageCatalogReference Catalog = WinGetManager.GetPackageCatalogByName(details.Package.Source.Name); + if (Catalog == null) + { + logger.Error("Failed to get catalog " + details.Package.Source.Name + ". Is the package local?"); + logger.Close(1); + return; + } + + // Connect to catalog + Catalog.AcceptSourceAgreements = true; + ConnectResult ConnectResult = await Task.Run(() => Catalog.Connect()); + if (ConnectResult.Status != ConnectResultStatus.Ok) + { + logger.Error("Failed to connect to catalog " + details.Package.Source.Name); + logger.Close(1); + return; + } + + // Match only the exact same Id + FindPackagesOptions packageMatchFilter = Factory.CreateFindPackagesOptions(); + PackageMatchFilter filters = Factory.CreatePackageMatchFilter(); + filters.Field = PackageMatchField.Id; + filters.Value = details.Package.Id; + filters.Option = PackageFieldMatchOption.Equals; + packageMatchFilter.Filters.Add(filters); + packageMatchFilter.ResultLimit = 1; + Task SearchResult = + Task.Run(() => ConnectResult.PackageCatalog.FindPackages(packageMatchFilter)); + + if (SearchResult.Result == null || SearchResult.Result.Matches == null || + SearchResult.Result.Matches.Count() == 0) + { + logger.Error("WinGet: Failed to find package " + details.Package.Id + " in catalog " + + details.Package.Source.Name); + logger.Close(1); + return; + } + + // Get the Native Package + CatalogPackage NativePackage = SearchResult.Result.Matches.First().CatalogPackage; + + // Extract data from NativeDetails + CatalogPackageMetadata NativeDetails = + NativePackage.DefaultInstallVersion.GetCatalogPackageMetadata(Windows.System.UserProfile + .GlobalizationPreferences.Languages[0]); + + if (NativeDetails.Author != "") + { + details.Author = NativeDetails.Author; + } + + if (NativeDetails.Description != "") + { + details.Description = NativeDetails.Description; + } + + if (NativeDetails.PackageUrl != "") + { + details.HomepageUrl = new Uri(NativeDetails.PackageUrl); + } + + if (NativeDetails.License != "") + { + details.License = NativeDetails.License; + } + + if (NativeDetails.LicenseUrl != "") + { + details.LicenseUrl = new Uri(NativeDetails.LicenseUrl); + } + + if (NativeDetails.Publisher != "") + { + details.Publisher = NativeDetails.Publisher; + } + + if (NativeDetails.ReleaseNotes != "") + { + details.ReleaseNotes = NativeDetails.ReleaseNotes; + } + + if (NativeDetails.ReleaseNotesUrl != "") + { + details.ReleaseNotesUrl = new Uri(NativeDetails.ReleaseNotesUrl); + } + + if (NativeDetails.Tags != null) + { + details.Tags = NativeDetails.Tags.ToArray(); + } + + + // There is no way yet to retrieve installer URLs right now so this part will be console-parsed. + // TODO: Replace this code with native code when available on the COM api + Process process = new(); + List output = []; + ProcessStartInfo startInfo = new() + { + FileName = Manager.WinGetBundledPath, + Arguments = Manager.Properties.ExecutableCallArgs + " show --id " + details.Package.Id + + " --exact --disable-interactivity --accept-source-agreements --source " + + details.Package.Source.Name, + RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = true, + StandardOutputEncoding = System.Text.Encoding.UTF8 + }; + process.StartInfo = startInfo; + process.Start(); + + logger.Log("Begin loading installers:"); + logger.Log(" Executable: " + startInfo.FileName); + logger.Log(" Arguments: " + startInfo.Arguments); + + // Retrieve the output + string? _line; + while ((_line = await process.StandardOutput.ReadLineAsync()) != null) + { + if (_line.Trim() != "") + { + logger.Log(_line); + output.Add(_line); + } + } + + logger.Error(await process.StandardError.ReadToEndAsync()); + + // Parse the output + foreach (string __line in output) + { + try + { + string line = __line.Trim(); + if (line.Contains("Installer SHA256:")) + { + details.InstallerHash = line.Split(":")[1].Trim(); + } + else if (line.Contains("Installer Url:")) + { + details.InstallerUrl = new Uri(line.Replace("Installer Url:", "").Trim()); + details.InstallerSize = await CoreTools.GetFileSizeAsync(details.InstallerUrl); + } + else if (line.Contains("Release Date:")) + { + details.UpdateDate = line.Split(":")[1].Trim(); + } + else if (line.Contains("Installer Type:")) + { + details.InstallerType = line.Split(":")[1].Trim(); + } + } + catch (Exception e) + { + Logger.Warn("Error occurred while parsing line value=\"" + __line + "\""); + Logger.Warn(e.Message); + } + } + + logger.Close(0); + return; + } +} \ No newline at end of file From 9763be6b8472ac8f8e0fa1fa4efc123241a478ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sun, 7 Jul 2024 17:33:05 +0200 Subject: [PATCH 3/6] Add the ability to load updates through the WinGet API --- .../WinGetHelpers/NativeWinGetHelper.cs | 33 +++++++++++++++---- .../AbstractPackagesPage.xaml.cs | 2 +- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs index 13e4fb094..27ba43068 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs @@ -4,6 +4,7 @@ using UniGetUI.Core.Tools; using UniGetUI.PackageEngine.Classes.Manager.ManagerHelpers; using UniGetUI.PackageEngine.Enums; +using UniGetUI.PackageEngine.ManagerClasses.Classes; using UniGetUI.PackageEngine.PackageClasses; using WindowsPackageManager.Interop; @@ -133,13 +134,29 @@ public async Task FindPackages_UnSafe(WinGet Manager, string query) public async Task GetAvailableUpdates_UnSafe(WinGet Manager) { - return []; + var logger = Manager.TaskLogger.CreateNew(LoggableTaskType.ListInstalledPackages); + List packages = new(); + foreach (var match in await GetLocalWinGetPackages(logger)) + { + var nativePackage = match.CatalogPackage; + if (nativePackage.IsUpdateAvailable) + { + ManagerSource source; + source = Manager.GetSourceOrDefault(nativePackage.DefaultInstallVersion.PackageCatalog.Info.Name); + packages.Add(new Package(nativePackage.Name, nativePackage.Id, nativePackage.InstalledVersion.Version, nativePackage.DefaultInstallVersion.Version, source, Manager)); + logger.Log($"Found package {nativePackage.Name} {nativePackage.Id} on source {source.Name}, from version {nativePackage.InstalledVersion.Version} to version {nativePackage.DefaultInstallVersion.Version}"); + } + } + + return packages.ToArray(); + } public async Task GetInstalledPackages_UnSafe(WinGet Manager) { + var logger = Manager.TaskLogger.CreateNew(LoggableTaskType.ListInstalledPackages); List packages = new(); - foreach (var match in await GetLocalWinGetPackages()) + foreach (var match in await GetLocalWinGetPackages(logger)) { var nativePackage = match.CatalogPackage; @@ -152,19 +169,20 @@ public async Task GetInstalledPackages_UnSafe(WinGet Manager) { source = Manager.GetLocalSource(nativePackage.Id); } + logger.Log($"Found package {nativePackage.Name} {nativePackage.Id} on source {source.Name}"); packages.Add(new Package(nativePackage.Name, nativePackage.Id, nativePackage.InstalledVersion.Version, source, Manager)); } return packages.ToArray(); } - private async Task> GetLocalWinGetPackages() + private async Task> GetLocalWinGetPackages(NativeTaskLogger logger) { PackageCatalogReference installedSearchCatalogRef; CreateCompositePackageCatalogOptions createCompositePackageCatalogOptions = Factory.CreateCreateCompositePackageCatalogOptions(); foreach(var catalogRef in WinGetManager.GetPackageCatalogs().ToArray()) { - Console.WriteLine($"Searching on package catalog {catalogRef.Info.Name} "); + Logger.Info($"Adding catalog {catalogRef.Info.Name} to composite catalog"); createCompositePackageCatalogOptions.Catalogs.Add(catalogRef); } createCompositePackageCatalogOptions.CompositeSearchBehavior = CompositeSearchBehavior.LocalCatalogs; @@ -173,7 +191,9 @@ private async Task> GetLocalWinGetPackages() var ConnectResult = await Task.Run(() => installedSearchCatalogRef.Connect()); if (ConnectResult.Status != ConnectResultStatus.Ok) { - throw new Exception("WinGet: Failed to connect to local catalog."); + logger.Error("Failed to connect to installedSearchCatalogRef. Aborting."); + logger.Close(1); + throw new Exception("WinGet: Failed to connect to composite catalog."); } FindPackagesOptions findPackagesOptions = Factory.CreateFindPackagesOptions(); @@ -213,8 +233,7 @@ public async Task GetSources_UnSafe(WinGet Manager) public async Task GetPackageVersions_Unsafe(WinGet Manager, Package package) { - ManagerClasses.Classes.NativeTaskLogger logger = - Manager.TaskLogger.CreateNew(LoggableTaskType.LoadPackageVersions); + NativeTaskLogger logger = Manager.TaskLogger.CreateNew(LoggableTaskType.LoadPackageVersions); // Find the native package for the given Package object PackageCatalogReference Catalog = WinGetManager.GetPackageCatalogByName(package.Source.Name); diff --git a/src/UniGetUI/Interface/SoftwarePages/AbstractPackagesPage.xaml.cs b/src/UniGetUI/Interface/SoftwarePages/AbstractPackagesPage.xaml.cs index 50b519149..9a5a52961 100644 --- a/src/UniGetUI/Interface/SoftwarePages/AbstractPackagesPage.xaml.cs +++ b/src/UniGetUI/Interface/SoftwarePages/AbstractPackagesPage.xaml.cs @@ -566,7 +566,7 @@ public void FilterPackages() FilteredPackages.BlockSorting = true; foreach (Package match in MatchingList) { - if (VisibleManagers.Contains(match.Manager) || VisibleSources.Contains(match.Source)) + if (VisibleSources.Contains(match.Source) || (!match.Manager.Capabilities.SupportsCustomSources && VisibleManagers.Contains(match.Manager))) { FilteredPackages.Add(match); } From 7f58141a14231ecafee38ac4eadffe87043c1ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sun, 7 Jul 2024 17:53:19 +0200 Subject: [PATCH 4/6] Fix small freeze when loading WinGet package lists --- .../WinGet.cs | 6 ++--- .../WinGetHelpers/NativeWinGetHelper.cs | 24 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs index 827fe7e66..48fa01c8b 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGet.cs @@ -113,17 +113,17 @@ public WinGet() : base() protected override async Task FindPackages_UnSafe(string query) { - return await WinGetHelper.Instance.FindPackages_UnSafe(this, query); + return await Task.Run(() => WinGetHelper.Instance.FindPackages_UnSafe(this, query).GetAwaiter().GetResult()); } protected override async Task GetAvailableUpdates_UnSafe() { - return await WinGetHelper.Instance.GetAvailableUpdates_UnSafe(this); + return await Task.Run(() => WinGetHelper.Instance.GetAvailableUpdates_UnSafe(this).GetAwaiter().GetResult()); } protected override async Task GetInstalledPackages_UnSafe() { - return await WinGetHelper.Instance.GetInstalledPackages_UnSafe(this); + return await Task.Run(() => WinGetHelper.Instance.GetInstalledPackages_UnSafe(this).GetAwaiter().GetResult()); } public ManagerSource GetLocalSource(string id) diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs index 27ba43068..29b835e13 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs @@ -136,9 +136,8 @@ public async Task GetAvailableUpdates_UnSafe(WinGet Manager) { var logger = Manager.TaskLogger.CreateNew(LoggableTaskType.ListInstalledPackages); List packages = new(); - foreach (var match in await GetLocalWinGetPackages(logger)) + foreach (var nativePackage in await Task.Run(() => GetLocalWinGetPackages(logger))) { - var nativePackage = match.CatalogPackage; if (nativePackage.IsUpdateAvailable) { ManagerSource source; @@ -148,6 +147,7 @@ public async Task GetAvailableUpdates_UnSafe(WinGet Manager) } } + logger.Close(0); return packages.ToArray(); } @@ -156,10 +156,8 @@ public async Task GetInstalledPackages_UnSafe(WinGet Manager) { var logger = Manager.TaskLogger.CreateNew(LoggableTaskType.ListInstalledPackages); List packages = new(); - foreach (var match in await GetLocalWinGetPackages(logger)) + foreach (var nativePackage in await Task.Run(() => GetLocalWinGetPackages(logger))) { - var nativePackage = match.CatalogPackage; - ManagerSource source; if (nativePackage.DefaultInstallVersion != null) { @@ -172,23 +170,23 @@ public async Task GetInstalledPackages_UnSafe(WinGet Manager) logger.Log($"Found package {nativePackage.Name} {nativePackage.Id} on source {source.Name}"); packages.Add(new Package(nativePackage.Name, nativePackage.Id, nativePackage.InstalledVersion.Version, source, Manager)); } - + logger.Close(0); return packages.ToArray(); } - private async Task> GetLocalWinGetPackages(NativeTaskLogger logger) + private IEnumerable GetLocalWinGetPackages(NativeTaskLogger logger) { PackageCatalogReference installedSearchCatalogRef; CreateCompositePackageCatalogOptions createCompositePackageCatalogOptions = Factory.CreateCreateCompositePackageCatalogOptions(); foreach(var catalogRef in WinGetManager.GetPackageCatalogs().ToArray()) { - Logger.Info($"Adding catalog {catalogRef.Info.Name} to composite catalog"); + logger.Log($"Adding catalog {catalogRef.Info.Name} to composite catalog"); createCompositePackageCatalogOptions.Catalogs.Add(catalogRef); } createCompositePackageCatalogOptions.CompositeSearchBehavior = CompositeSearchBehavior.LocalCatalogs; installedSearchCatalogRef = WinGetManager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions); - var ConnectResult = await Task.Run(() => installedSearchCatalogRef.Connect()); + var ConnectResult = installedSearchCatalogRef.Connect(); if (ConnectResult.Status != ConnectResultStatus.Ok) { logger.Error("Failed to connect to installedSearchCatalogRef. Aborting."); @@ -203,9 +201,11 @@ private async Task> GetLocalWinGetPackages(NativeTaskLo filter.Value = ""; findPackagesOptions.Filters.Add(filter); - var TaskResult = await Task.Run(() => ConnectResult.PackageCatalog.FindPackages(findPackagesOptions)); - - return TaskResult.Matches.ToArray(); + var TaskResult = ConnectResult.PackageCatalog.FindPackages(findPackagesOptions); + List foundPackages = new(); + foreach(var match in TaskResult.Matches.ToArray()) + foundPackages.Add(match.CatalogPackage); + return foundPackages; } public async Task GetSources_UnSafe(WinGet Manager) From a26233697e7250412ef48438733b77041dbf2659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sun, 7 Jul 2024 17:56:57 +0200 Subject: [PATCH 5/6] Changes --- .../WinGetHelpers/NativeWinGetHelper.cs | 2 +- .../WinGetPackageDetailsProvider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs index 29b835e13..45a8ff868 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs @@ -12,7 +12,7 @@ namespace UniGetUI.PackageEngine.Managers.WingetManager; internal class NativeWinGetHelper : IWinGetManagerHelper { - public WindowsPackageManagerStandardFactory Factory; + public WindowsPackageManagerFactory Factory; public PackageManager WinGetManager; public NativeWinGetHelper() diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetPackageDetailsProvider.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetPackageDetailsProvider.cs index 96a3f9ef8..8d9419133 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetPackageDetailsProvider.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetPackageDetailsProvider.cs @@ -202,7 +202,7 @@ protected override async Task GetPackageScreenshots_Unsafe(Package packag } Microsoft.Management.Deployment.PackageManager WinGetManager = ((NativeWinGetHelper)WinGetHelper.Instance).WinGetManager; - WindowsPackageManager.Interop.WindowsPackageManagerStandardFactory Factory = ((NativeWinGetHelper)WinGetHelper.Instance).Factory; + WindowsPackageManager.Interop.WindowsPackageManagerFactory Factory = ((NativeWinGetHelper)WinGetHelper.Instance).Factory; // Find the native package for the given Package object PackageCatalogReference Catalog = WinGetManager.GetPackageCatalogByName(package.Source.Name); From 28f6ce2ebc74cd10c548b37d2a0163b44e4f1711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Mon, 8 Jul 2024 00:10:21 +0200 Subject: [PATCH 6/6] Add a setting to Toggle between COM, PowerShell and RAW CLI Parsing for WinGet --- .../WinGetHelpers/NativeWinGetHelper.cs | 4 ++++ .../PEInterface.cs | 3 +++ src/UniGetUI/Interface/Pages/SettingsPage.xaml.cs | 12 ++++++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs index 45a8ff868..c7854c05c 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/WinGetHelpers/NativeWinGetHelper.cs @@ -1,6 +1,8 @@ using System.Diagnostics; +using System.Diagnostics.Tracing; using Microsoft.Management.Deployment; using UniGetUI.Core.Logging; +using UniGetUI.Core.SettingsEngine; using UniGetUI.Core.Tools; using UniGetUI.PackageEngine.Classes.Manager.ManagerHelpers; using UniGetUI.PackageEngine.Enums; @@ -17,6 +19,8 @@ internal class NativeWinGetHelper : IWinGetManagerHelper public NativeWinGetHelper() { + if (Settings.Get("DisableWinGetCOMApi")) + throw new Exception("User requested to disable the WinGet COM API, crashing..."); if (CoreTools.IsAdministrator()) { Logger.Info("Running elevated, WinGet class registration is likely to fail"); diff --git a/src/UniGetUI.PackageEngine.PackageEngine/PEInterface.cs b/src/UniGetUI.PackageEngine.PackageEngine/PEInterface.cs index dd61773d9..5c16b2f5c 100644 --- a/src/UniGetUI.PackageEngine.PackageEngine/PEInterface.cs +++ b/src/UniGetUI.PackageEngine.PackageEngine/PEInterface.cs @@ -11,6 +11,9 @@ namespace UniGetUI.PackageEngine { + /// + /// The interface/entry point for the UniGetUI Package Engine + /// public static class PEInterface { private const int ManagerLoadTimeout = 10000; // 10 seconds timeout for Package Manager initialization diff --git a/src/UniGetUI/Interface/Pages/SettingsPage.xaml.cs b/src/UniGetUI/Interface/Pages/SettingsPage.xaml.cs index 2d4a5ec3e..6c05091c0 100644 --- a/src/UniGetUI/Interface/Pages/SettingsPage.xaml.cs +++ b/src/UniGetUI/Interface/Pages/SettingsPage.xaml.cs @@ -122,14 +122,22 @@ public SettingsInterface() CoreTools.LaunchBatchFile(Path.Join(CoreData.UniGetUIExecutableDirectory, "Assets", "Utilities", "reset_winget_sources.cmd"), CoreTools.Translate("Resetting Winget sources - WingetUI"), RunAsAdmin: true); }; + CheckboxCard Winget_DisableCOM = new() + { + Text = CoreTools.Translate("Use the WinGet COM API to fetch packages"), + SettingName = "DisableWinGetCOMApi", + }; + Winget_DisableCOM.StateChanged += (s, e) => PackageManagerExpanders[PEInterface.WinGet].ShowRestartRequiredBanner(); + CheckboxCard Winget_UseBundled = new() { - Text = CoreTools.Translate("Use legacy bundled WinGet instead of PowerShell CMDlets"), + Text = CoreTools.Translate("Use bundled WinGet instead of PowerShell CMDlets"), SettingName = "ForceLegacyBundledWinGet" }; - ExtraSettingsCards[PEInterface.WinGet].Add(Winget_ResetSources); + ExtraSettingsCards[PEInterface.WinGet].Add(Winget_DisableCOM); ExtraSettingsCards[PEInterface.WinGet].Add(Winget_UseBundled); + ExtraSettingsCards[PEInterface.WinGet].Add(Winget_ResetSources); ButtonCard Scoop_Install = new() { Text = CoreTools.AutoTranslated("Install Scoop"), ButtonText = CoreTools.AutoTranslated("Install") }; Scoop_Install.Click += (s, e) =>