FinderCommand.cs (8385B)
1 // ----------------------------------------------------------------------------- 2 // <copyright file="FinderCommand.cs" company="Microsoft Corporation"> 3 // Copyright (c) Microsoft Corporation. Licensed under the MIT License. 4 // </copyright> 5 // ----------------------------------------------------------------------------- 6 7 namespace Microsoft.WinGet.Client.Engine.Commands.Common 8 { 9 using System; 10 using System.Collections.Generic; 11 using System.Linq; 12 using System.Management.Automation; 13 using System.Reflection; 14 using Microsoft.Management.Deployment; 15 using Microsoft.WinGet.Client.Engine.Attributes; 16 using Microsoft.WinGet.Client.Engine.Exceptions; 17 using Microsoft.WinGet.Client.Engine.Helpers; 18 19 /// <summary> 20 /// This is the base class for all commands that might need to search for a package. It contains an initial 21 /// set of parameters that corresponds to the intersection of i.e., the "install" and "search" commands. 22 /// </summary> 23 public abstract class FinderCommand : ManagementDeploymentCommand 24 { 25 /// <summary> 26 /// Initializes a new instance of the <see cref="FinderCommand"/> class. 27 /// </summary> 28 /// <param name="psCmdlet">PSCmdlet.</param> 29 internal FinderCommand(PSCmdlet psCmdlet) 30 : base(psCmdlet) 31 { 32 } 33 34 /// <summary> 35 /// Gets or sets the field that is matched against the identifier of a package. 36 /// </summary> 37 [Filter(Field = PackageMatchField.Id)] 38 protected string? Id { get; set; } 39 40 /// <summary> 41 /// Gets or sets the field that is matched against the name of a package. 42 /// </summary> 43 [Filter(Field = PackageMatchField.Name)] 44 protected string? Name { get; set; } 45 46 /// <summary> 47 /// Gets or sets the field that is matched against the moniker of a package. 48 /// </summary> 49 [Filter(Field = PackageMatchField.Moniker)] 50 protected string? Moniker { get; set; } 51 52 /// <summary> 53 /// Gets or sets the name of the source to search for packages. If null, then all sources are searched. 54 /// </summary> 55 protected string? Source { get; set; } 56 57 /// <summary> 58 /// Gets or sets how to match against package fields. 59 /// </summary> 60 #pragma warning disable SA1011 // Closing square brackets should be spaced correctly 61 protected string[]? Query { get; set; } 62 #pragma warning restore SA1011 // Closing square brackets should be spaced correctly 63 64 private string? QueryAsJoinedString 65 { 66 get 67 { 68 return this.Query is null 69 ? null 70 : string.Join(" ", this.Query); 71 } 72 } 73 74 /// <summary> 75 /// Searches for packages based on the configured parameters. 76 /// </summary> 77 /// <param name="behavior">The <see cref="CompositeSearchBehavior" /> value.</param> 78 /// <param name="limit">The limit on the number of matches returned.</param> 79 /// <param name="match">The match option.</param> 80 /// <returns>A list of <see cref="MatchResult" /> objects.</returns> 81 internal IReadOnlyList<MatchResult> FindPackages( 82 CompositeSearchBehavior behavior, 83 uint limit, 84 PackageFieldMatchOption match) 85 { 86 PackageCatalog catalog = this.GetPackageCatalog(behavior); 87 FindPackagesOptions options = this.GetFindPackagesOptions(limit, match); 88 return this.GetMatchResults(catalog, options); 89 } 90 91 /// <summary> 92 /// Sets the find package options for a query input. 93 /// DO NOT pass PackageFieldMatchOption WinRT enum type in this method. 94 /// That will cause the type to attempt to be loaded in the construction 95 /// of this method and throw a different exception for Windows PowerShell. 96 /// </summary> 97 /// <param name="options">The options object.</param> 98 /// <param name="match">The match type as string.</param> 99 /// <param name="value">The query value.</param> 100 internal virtual void SetQueryInFindPackagesOptions( 101 ref FindPackagesOptions options, 102 string match, 103 string? value) 104 { 105 var selector = ManagementDeploymentFactory.Instance.CreatePackageMatchFilter(); 106 selector.Field = PackageMatchField.CatalogDefault; 107 selector.Value = value ?? string.Empty; 108 selector.Option = PSEnumHelpers.ToPackageFieldMatchOption(match); 109 options.Selectors.Add(selector); 110 } 111 112 private void AddFilterToFindPackagesOptionsIfNotNull( 113 ref FindPackagesOptions options, 114 PackageMatchField field, 115 PackageFieldMatchOption match, 116 string? value) 117 { 118 if (value != null) 119 { 120 var filter = ManagementDeploymentFactory.Instance.CreatePackageMatchFilter(); 121 filter.Field = field; 122 filter.Value = value; 123 filter.Option = match; 124 options.Filters.Add(filter); 125 } 126 } 127 128 private IReadOnlyList<MatchResult> GetMatchResults( 129 PackageCatalog catalog, 130 FindPackagesOptions options) 131 { 132 FindPackagesResult result = catalog.FindPackages(options); 133 if (result.Status == FindPackagesResultStatus.Ok) 134 { 135 return result.Matches; 136 } 137 else 138 { 139 throw new FindPackagesException(result.Status); 140 } 141 } 142 143 private PackageCatalog GetPackageCatalog(CompositeSearchBehavior behavior) 144 { 145 PackageCatalogReference reference = this.GetPackageCatalogReference(behavior); 146 ConnectResult result = reference.Connect(); 147 if (result.Status == ConnectResultStatus.Ok) 148 { 149 return result.PackageCatalog; 150 } 151 else 152 { 153 throw new CatalogConnectException(result.ExtendedErrorCode); 154 } 155 } 156 157 private PackageCatalogReference GetPackageCatalogReference(CompositeSearchBehavior behavior) 158 { 159 CreateCompositePackageCatalogOptions options = ManagementDeploymentFactory.Instance.CreateCreateCompositePackageCatalogOptions(); 160 IReadOnlyList<PackageCatalogReference> references = this.GetPackageCatalogReferences(this.Source); 161 for (var i = 0; i < references.Count; i++) 162 { 163 options.Catalogs.Add(references[i]); 164 } 165 166 options.CompositeSearchBehavior = behavior; 167 return PackageManagerWrapper.Instance.CreateCompositePackageCatalog(options); 168 } 169 170 private FindPackagesOptions GetFindPackagesOptions(uint limit, PackageFieldMatchOption match) 171 { 172 var options = ManagementDeploymentFactory.Instance.CreateFindPackagesOptions(); 173 this.SetQueryInFindPackagesOptions(ref options, match.ToString(), this.QueryAsJoinedString); 174 this.AddAttributedFiltersToFindPackagesOptions(ref options, match); 175 options.ResultLimit = limit; 176 return options; 177 } 178 179 private void AddAttributedFiltersToFindPackagesOptions( 180 ref FindPackagesOptions options, 181 PackageFieldMatchOption match) 182 { 183 IEnumerable<PropertyInfo> properties = this.GetType() 184 .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance) 185 .Where(property => Attribute.IsDefined(property, typeof(FilterAttribute))); 186 187 foreach (PropertyInfo info in properties) 188 { 189 if (info.GetCustomAttribute(typeof(FilterAttribute), true) is FilterAttribute attribute) 190 { 191 PackageMatchField field = attribute.Field; 192 string? value = info.GetValue(this, null) as string; 193 this.AddFilterToFindPackagesOptionsIfNotNull(ref options, field, match, value); 194 } 195 } 196 } 197 } 198 }