UWP.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using System;
  6  using System.Collections.Generic;
  7  using System.IO.Abstractions;
  8  using System.Linq;
  9  using System.Reflection;
 10  using System.Runtime.InteropServices;
 11  using System.Runtime.InteropServices.ComTypes;
 12  using System.Xml.Linq;
 13  
 14  using Microsoft.Plugin.Program.Logger;
 15  using Wox.Plugin.Common.Win32;
 16  using Wox.Plugin.Logger;
 17  
 18  namespace Microsoft.Plugin.Program.Programs
 19  {
 20      [Serializable]
 21      public partial class UWP
 22      {
 23          private static readonly IPath Path = new FileSystem().Path;
 24  
 25          private static readonly Dictionary<string, PackageVersion> _versionFromNamespace = new Dictionary<string, PackageVersion>
 26          {
 27              { "http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10 },
 28              { "http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81 },
 29              { "http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8 },
 30          };
 31  
 32          public string Name { get; }
 33  
 34          public string FullName { get; }
 35  
 36          public string FamilyName { get; }
 37  
 38          public string Location { get; set; }
 39  
 40          // Localized path based on windows display language
 41          public string LocationLocalized { get; set; }
 42  
 43          public IList<UWPApplication> Apps { get; private set; }
 44  
 45          public PackageVersion Version { get; set; }
 46  
 47          public static IPackageManager PackageManagerWrapper { get; set; } = new PackageManagerWrapper();
 48  
 49          public UWP(IPackage package)
 50          {
 51              ArgumentNullException.ThrowIfNull(package);
 52  
 53              Name = package.Name;
 54              FullName = package.FullName;
 55              FamilyName = package.FamilyName;
 56          }
 57  
 58          public void InitializeAppInfo(string installedLocation)
 59          {
 60              Location = installedLocation;
 61              LocationLocalized = Main.ShellLocalizationHelper.GetLocalizedPath(installedLocation);
 62              var path = Path.Combine(installedLocation, "AppxManifest.xml");
 63  
 64              var namespaces = XmlNamespaces(path);
 65              InitPackageVersion(namespaces);
 66  
 67              const uint noAttribute = 0x80;
 68              const STGM exclusiveRead = STGM.READ;
 69              var hResult = NativeMethods.SHCreateStreamOnFileEx(path, exclusiveRead, noAttribute, false, null, out IStream stream);
 70  
 71              if (hResult == HRESULT.S_OK)
 72              {
 73                  Apps = AppxPackageHelper.GetAppsFromManifest(stream).Select(appInManifest => new UWPApplication(appInManifest, this)).Where(a =>
 74                  {
 75                      var valid =
 76                      !string.IsNullOrEmpty(a.UserModelId) &&
 77                      !string.IsNullOrEmpty(a.DisplayName) &&
 78                      a.AppListEntry != "none";
 79  
 80                      return valid;
 81                  }).ToList();
 82  
 83                  if (Marshal.ReleaseComObject(stream) > 0)
 84                  {
 85                      Log.Error("AppxManifest.xml was leaked", MethodBase.GetCurrentMethod().DeclaringType);
 86                  }
 87              }
 88              else
 89              {
 90                  var e = Marshal.GetExceptionForHR((int)hResult);
 91                  ProgramLogger.Exception("Error caused while trying to get the details of the UWP program", e, GetType(), path);
 92  
 93                  Apps = Array.Empty<UWPApplication>();
 94              }
 95          }
 96  
 97          // http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx
 98          private static string[] XmlNamespaces(string path)
 99          {
100              XDocument z = XDocument.Load(path);
101              if (z.Root != null)
102              {
103                  var namespaces = z.Root.Attributes().
104                      Where(a => a.IsNamespaceDeclaration).
105                      GroupBy(
106                          a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName,
107                          a => XNamespace.Get(a.Value)).Select(
108                          g => g.First().ToString()).ToArray();
109                  return namespaces;
110              }
111              else
112              {
113                  Log.Error($"Error occurred while trying to get the XML from {path}", MethodBase.GetCurrentMethod().DeclaringType);
114  
115                  return Array.Empty<string>();
116              }
117          }
118  
119          private void InitPackageVersion(string[] namespaces)
120          {
121              foreach (var n in _versionFromNamespace.Keys.Where(namespaces.Contains))
122              {
123                  Version = _versionFromNamespace[n];
124                  return;
125              }
126  
127              ProgramLogger.Exception($"|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version {FullName} from location {Location} is returned.", new FormatException(), GetType(), Location);
128  
129              Version = PackageVersion.Unknown;
130          }
131  
132          public static UWPApplication[] All()
133          {
134              var windows10 = new Version(10, 0);
135              var support = Environment.OSVersion.Version.Major >= windows10.Major;
136              if (support)
137              {
138                  var applications = CurrentUserPackages().AsParallel().SelectMany(p =>
139                  {
140                      UWP u;
141                      try
142                      {
143                          u = new UWP(p);
144                          u.InitializeAppInfo(p.InstalledLocation);
145                      }
146                      catch (Exception e)
147                      {
148                          ProgramLogger.Exception($"Unable to convert Package to UWP for {p.FullName}", e, MethodBase.GetCurrentMethod().DeclaringType, p.InstalledLocation);
149  
150                          return Array.Empty<UWPApplication>();
151                      }
152  
153                      return u.Apps;
154                  });
155  
156                  var updatedListWithoutDisabledApps = applications
157                                                          .Where(t1 => Main.Settings.DisabledProgramSources.All(x => x.UniqueIdentifier != t1.UniqueIdentifier))
158                                                          .Select(x => x);
159  
160                  return updatedListWithoutDisabledApps.ToArray();
161              }
162              else
163              {
164                  return Array.Empty<UWPApplication>();
165              }
166          }
167  
168          private static IEnumerable<IPackage> CurrentUserPackages()
169          {
170              return PackageManagerWrapper.FindPackagesForCurrentUser().Where(p =>
171              {
172                  try
173                  {
174                      var f = p.IsFramework;
175                      var path = p.InstalledLocation;
176                      return !f && !string.IsNullOrEmpty(path);
177                  }
178                  catch (Exception e)
179                  {
180                      ProgramLogger.Exception("An unexpected error occurred and unable to verify if package is valid", e, MethodBase.GetCurrentMethod().DeclaringType, "id");
181                      return false;
182                  }
183              });
184          }
185  
186          public override string ToString()
187          {
188              return FamilyName;
189          }
190  
191          [System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1309:Use ordinal string comparison", Justification = "Using CurrentCultureIgnoreCase since this is used with FamilyName")]
192          public override bool Equals(object obj)
193          {
194              if (obj is UWP uwp)
195              {
196                  // Using CurrentCultureIgnoreCase since this is used with FamilyName
197                  return FamilyName.Equals(uwp.FamilyName, StringComparison.CurrentCultureIgnoreCase);
198              }
199              else
200              {
201                  return false;
202              }
203          }
204  
205          public override int GetHashCode()
206          {
207              // Using CurrentCultureIgnoreCase since this is used with FamilyName
208              return FamilyName.GetHashCode(StringComparison.CurrentCultureIgnoreCase);
209          }
210  
211          public enum PackageVersion
212          {
213              Windows10,
214              Windows81,
215              Windows8,
216              Unknown,
217          }
218      }
219  }