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 }