Utilities.cs (8220B)
1 // ----------------------------------------------------------------------------- 2 // <copyright file="Utilities.cs" company="Microsoft Corporation"> 3 // Copyright (c) Microsoft Corporation. Licensed under the MIT License. 4 // </copyright> 5 // ----------------------------------------------------------------------------- 6 7 namespace Microsoft.WinGet.Client.Engine.Common 8 { 9 using System; 10 using System.Collections; 11 using System.Collections.Generic; 12 using System.IO; 13 using System.Management.Automation; 14 using System.Security.Principal; 15 using System.Threading; 16 using Microsoft.WinGet.Resources; 17 using Newtonsoft.Json; 18 using Newtonsoft.Json.Linq; 19 20 /// <summary> 21 /// This class contains various helper methods for this project. 22 /// </summary> 23 internal static class Utilities 24 { 25 /// <summary> 26 /// Gets a value indicating whether the current assembly is executing in an administrative context. 27 /// </summary> 28 [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] 29 public static bool ExecutingAsAdministrator 30 { 31 get 32 { 33 WindowsIdentity identity = WindowsIdentity.GetCurrent(); 34 WindowsPrincipal principal = new (identity); 35 return principal.IsInRole(WindowsBuiltInRole.Administrator); 36 } 37 } 38 39 /// <summary> 40 /// Gets a value indicating whether the current assembly is executing as a SYSTEM user. 41 /// </summary> 42 [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] 43 public static bool ExecutingAsSystem 44 { 45 get 46 { 47 return WindowsIdentity.GetCurrent().IsSystem; 48 } 49 } 50 51 /// <summary> 52 /// Gets a value indicating whether the current execution context will use in-proc winget. 53 /// </summary> 54 public static bool UsesInProcWinget 55 { 56 get 57 { 58 return ExecutingAsSystem; 59 } 60 } 61 62 /// <summary> 63 /// Gets a value indicating whether the current thread is executing as STA. 64 /// </summary> 65 [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] 66 public static bool ThreadIsSTA 67 { 68 get 69 { 70 return Thread.CurrentThread.GetApartmentState() == ApartmentState.STA; 71 } 72 } 73 74 /// <summary> 75 /// Gets the windows app path for local app data. 76 /// </summary> 77 public static string LocalDataWindowsAppPath 78 { 79 get 80 { 81 return Environment.ExpandEnvironmentVariables(@"%LOCALAPPDATA%\Microsoft\WindowsApps"); 82 } 83 } 84 85 /// <summary> 86 /// Gets the windows app path for program files. 87 /// </summary> 88 public static string ProgramFilesWindowsAppPath 89 { 90 get 91 { 92 return Environment.ExpandEnvironmentVariables(@"%PROGRAMFILES%\WindowsApps"); 93 } 94 } 95 96 /// <summary> 97 /// Throws if not running as admin. 98 /// </summary> 99 public static void VerifyAdmin() 100 { 101 if (!Utilities.ExecutingAsAdministrator) 102 { 103 throw new PSNotSupportedException(Resources.RequiresAdminMessage); 104 } 105 } 106 107 /// <summary> 108 /// Adds the WindowsApp local app data path to the user environment path. 109 /// </summary> 110 public static void AddWindowsAppToPath() 111 { 112 var scope = EnvironmentVariableTarget.User; 113 string? envPathValue = Environment.GetEnvironmentVariable(Constants.PathEnvVar, scope); 114 if (string.IsNullOrEmpty(envPathValue) || !envPathValue.Contains(Utilities.LocalDataWindowsAppPath)) 115 { 116 Environment.SetEnvironmentVariable( 117 Constants.PathEnvVar, 118 $"{envPathValue};{Utilities.LocalDataWindowsAppPath}", 119 scope); 120 } 121 } 122 123 /// <summary> 124 /// This is based of https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs. 125 /// So we can convert JSON to Hashtable for Windows PowerShell and PowerShell Core. 126 /// </summary> 127 /// <param name="content">String content.</param> 128 /// <returns>The hashtable.</returns> 129 public static Hashtable ConvertToHashtable(string content) 130 { 131 if (string.IsNullOrEmpty(content)) 132 { 133 return new Hashtable(); 134 } 135 136 var obj = JsonConvert.DeserializeObject( 137 content, 138 new JsonSerializerSettings 139 { 140 // This TypeNameHandling setting is required to be secure. 141 TypeNameHandling = TypeNameHandling.None, 142 MetadataPropertyHandling = MetadataPropertyHandling.Ignore, 143 MaxDepth = 1024, 144 }); 145 146 // It only makes sense that the deserialized object is a dictionary to start. 147 return obj switch 148 { 149 JObject dictionary => PopulateHashTableFromJDictionary(dictionary), 150 _ => throw new InvalidDataException() 151 }; 152 } 153 154 private static Hashtable PopulateHashTableFromJDictionary(JObject entries) 155 { 156 Hashtable result = new (entries.Count); 157 foreach (var entry in entries) 158 { 159 switch (entry.Value) 160 { 161 case JArray list: 162 { 163 // Array 164 var listResult = PopulateHashTableFromJArray(list); 165 result.Add(entry.Key, listResult); 166 break; 167 } 168 169 case JObject dic: 170 { 171 // Dictionary 172 var dicResult = PopulateHashTableFromJDictionary(dic); 173 result.Add(entry.Key, dicResult); 174 break; 175 } 176 177 case JValue value: 178 { 179 result.Add(entry.Key, value.Value); 180 break; 181 } 182 } 183 } 184 185 return result; 186 } 187 188 private static ICollection<object?> PopulateHashTableFromJArray(JArray list) 189 { 190 var result = new object?[list.Count]; 191 192 for (var index = 0; index < list.Count; index++) 193 { 194 var element = list[index]; 195 196 switch (element) 197 { 198 case JArray array: 199 { 200 // Array 201 var listResult = PopulateHashTableFromJArray(array); 202 result[index] = listResult; 203 break; 204 } 205 206 case JObject dic: 207 { 208 // Dictionary 209 var dicResult = PopulateHashTableFromJDictionary(dic); 210 result[index] = dicResult; 211 break; 212 } 213 214 case JValue value: 215 { 216 result[index] = value.Value; 217 break; 218 } 219 } 220 } 221 222 return result; 223 } 224 } 225 }